自定义ioctl命令
ioctl ---> kernel ---> cdev.fops->unlocked_ioctl(...)
系统调用ioctl函数的作用:用户进程用于通过相应的设备驱动来获取或者设置硬件状态。
在字符设备驱动里,unlock_ioctl函数原形:
long (*unlocked_ioctl) (struct file *fl, unsigned int cmd, unsigned long arg);
//cmd参数需要与应用程序调用ioctl时的参数约定,才可以表示一种功能
//cmd的值不能为2,内核里保留此值
//man 2 ioctl_list:可以查看系统里的ioctl关于cmd的参数值
cmd是32位的数,分成以下四个部分:
1.最高两位表示方向:读/写/读写(输出/输入/输出输入)
2.第16位至第29位表示ioctl的第三个参数的大小(unlocked_ioctl的arg)
3.第8位至第15位表示ioctl命令的类型
4.最低8位表示ioctl命令类型里的第几个命令
include/asm-generic/ioctl.h:
'k'
_IOC_DIRBITS << 30 | _IOC_SIZEBITS << 16 | _IOC_TYPEBITS << 8 | _IOC_NRBITS
#define _IOC_NRBITS 8 //顺序号 0 --- 7
#define _IOC_TYPEBITS 8 //类型 8 --- 15
#define _IOC_SIZEBITS 14 //ioctl第三个参数的大小 16 --- 29
#define _IOC_DIRBITS 2 //方向, 有没有参数, 读/写 30 --- 31
//方向位
# define _IOC_NONE 0U
# define _IOC_WRITE 1U
# define _IOC_READ 2U
用于生成一个ioctl的命令的宏定义:
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
如:#define LED_ON _IOC(_IOC_WRITE, 'L', 99, 0);
//方向, 类型, 第99个命令, ioctl的第三个参数大小为0(即没有第三个参数)
定义一个没有指定方向,没有第三个参数,只指定ioctl命令的类型及命令类型里的序号。
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOC_TYPECHECK(t) \
((sizeof(t) == sizeof(t[1]) && \
sizeof(t) < (1 << _IOC_SIZEBITS)) ? \
sizeof(t) : __invalid_size_argument_for_IOC)
定义一个驱动里输出参数值(用户进程读),指定ioctl命令的类型及命令类型里的序号及第三个参数的大小。
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
用于获取ioctl命令里方向,类型等信息:
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
_IOC_DIR(nr) //获取nr命令里的方向值
_IOC_TYPE(nr) //获取nr命令里的类型
_IOC_NR(nr) //获取nr命令里的顺序号
_IOC_SIZE(nr) //获取nr命令里的第三个参数大小
用户进程是不可以直接操作硬件,只能通过调用设备驱动,让设备驱动来操作硬件。
设备驱动又可以实现一个字符设备驱动接口让用户进程来调用。
控制LED(test.c):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#define MYMA 1234
#define MYMI 5500
#define COUNT 1
#define LED_GPIO GPIOA(15)
dev_t devid;
struct cdev mycdev;
long my_unlocked_ioctl(struct file *fl, unsigned int cmd, unsigned long arg)
{
if(cmd)
gpio_set_value(LED_GPIO, 1);
else
gpio_set_value(LED_GPIO, 0);
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = my_unlocked_ioctl,
};
static int __init test_init(void)
{
int ret;
devid = MKDEV(MYMA, MYMI);
ret = register_chrdev_region(devid, COUNT, "mydev");
if(ret < 0)
{
printk("register dev num failed\n");
return ret;
}
cdev_init(&mycdev, &fops);
mycdev.owner = THIS_MODULE;
ret = cdev_add(&mycdev, devid, COUNT);
if(ret < 0)
{
unregister_chrdev_region(devid, COUNT);
printk("add mycdev failed\n");
return ret;
}
ret = gpio_request(LED_GPIO, "myled");
if(ret < 0)
{
unregister_chrdev_region(devid, COUNT);
cdev_del(&mycdev);
printk("request myled failed\n");
return ret;
}
gpio_direction_output(LED_GPIO, 0);
printk("init success\n");
return 0;
}
static void __exit test_exit(void)
{
gpio_set_value(LED_GPIO, 1);
gpio_free(LED_GPIO);
unregister_chrdev_region(devid, COUNT);
cdev_del(&mycdev);
printk("exited\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
用户功能测试(app_test.c):
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#define LED_ON 1
#define LED_OFF 0
int main(void)
{
int fd;
fd = open("/dev/mydev", O_RDWR);
if(fd < 0)
return fd;
printf("this is the led ioctl to toggle\n");
while(1)
{
ioctl(fd, LED_ON);
sleep(1);
ioctl(fd, LED_OFF);
sleep(1);
}
return 0;
}
Makefile文件:
obj-m += test.o
KSRC := /目录路径/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-
all :
make -C $(KSRC) modules M=`pwd`
.PHONY : clean
clean :
make -C $(KSRC) modules clean M=`pwd`
编译加载驱动模块后,需创建设备文件后,才能运行功能测试程序:
mknod /dev/mydev c 1234 5500
通过ioctl获取烟雾传感器的例子(test.c):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <linux/ioctl.h> //生成ioctl命令的宏定义
#define MYMA 1234
#define MYMI 3344
#define COUNT 1
//烟雾传感器接在PA(7), 感应到烟雾时输出低电平,正常高电平
#define DETECT_IO GPIOA(7)
自定义的ioctl命令
#define DETECTOR_MAGIC 0xAF
#define DETECT_RET (_IOR(DETECTOR_MAGIC, 0x1, int))
dev_t devid; //用于存放设备号
struct cdev mycdev;
//如有第三个参数,则arg的值为用户进程ioctl调用时传进来的地址
long myioctl(struct file *fl, unsigned int cmd, unsigned long arg)
{
if (DETECTOR_MAGIC != _IOC_TYPE(cmd))
return -EINVAL;
if (DETECT_RET == cmd)
*(int *)arg = gpio_get_value(DETECT_IO);
return 0; //返回值表示操作是成功与否
}
struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = myioctl,
};
static int __init test_init(void)
{
int ret;
devid = MKDEV(MYMA, MYMI); //生成一个设备号
ret = register_chrdev_region(devid, COUNT, "mydev");
if (ret < 0)
goto err0;
cdev_init(&mycdev, &fops);
mycdev.owner = THIS_MODULE;
ret = cdev_add(&mycdev, devid, COUNT);
if (ret < 0)
goto err1;
gpio_request(DETECT_IO, "mydev"); //请求gpio口
gpio_direction_input(DETECT_IO); //配置gpio口为输入
return 0;
err1:
unregister_chrdev_region(devid, COUNT);
err0:
return ret;
}
static void __exit test_exit(void)
{
unregister_chrdev_region(devid, COUNT);
cdev_del(&mycdev);
gpio_free(DETECT_IO); //释放gpio
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
app_test.c:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/ioctl.h>
自定义的ioctl命令
#define DETECTOR_MAGIC 0xAF
#define DETECT_RET (_IOR(DETECTOR_MAGIC, 0x1, int))
int main(void)
{
int fd, ret, val;
fd = open("/dev/mydev", O_RDWR);
if (fd < 0)
{
perror("open dev");
return 1;
}
while (1)
{
ret = ioctl(fd, DETECT_RET, &val);
if (ret < 0)
break;
if (!val) //有烟雾感应到了
{
printf("smoke detected ...\n");
break;
}
}
close(fd);
return 0;
}