分类: LINUX
2012-08-03 17:48:45
1、IOCTL简介
大部分驱动除了需要具备读写设备的能力外,还需要具备对硬件控制的能力。例如,要求设备报告错误信息,改变波特率,这些操作常常通过 ioctl方法来实现。
ioctl方法原型如下:
n int (*ioctl)(struct inode *inode,struct file*filp,unsigned int cmd,unsigned long arg)
驱动函数ioctl的一般状态
n int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
//cmd有效的校验
//检查arg上传送的用户内存是否合法
switch(cmd)
{
case 分辨常数1:处理例程1
break;
………….
}
return 0;
}
cmd参数从用户空间传下来,可选的参数arg 以一个unsigned long 的形式传递,不管它是一个整数或一个指针。如果cmd命令不涉及数据传输,则第 3 个参数arg的值无任何意义。
2、编写ioctl控制驱动程序
1) 首先需要定义命令
ioctl 命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数),序号,传送方向,参数的大小。Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了的幻数。
内核提供了下列宏来帮助定义命令:
n _IO(type,nr)没有参数的命令
n _IOR(type,nr,datatype)从驱动中读数据
n _IOW(type,nr,datatype)写数据到驱动
n _IOWR(type,nr,datatype)双向传送,type 和 number 成员作为参数被传递。
定义命令(范例)
#define MEM_IOC_MAGIC ‘m’ //定义幻数
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC, 0, int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)
2) ioctl函数的实现
通常是根据命令执行的一个switch语句。但是,当命令号不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)。
如何使用ioctl中的arg参数?
如果是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需进行正确的检查。
n int access_ok(int type, const void *addr, unsigned long size)
第一个参数是 VERIFY_READ 或者 VERIFY_WRITE,用来表明是读用户内存还是写用户内存。addr 参数是要操作的用户内存地址,size 是操作的长度。如果 ioctl 需要从用户空间读一个整数,那么size参数等于 sizeof(int)。access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题),如果该函数返回失败, 则Ioctl应当返回–EFAULT 。
ioctl函数实现(参数检查)
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg,
_IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg,
_IOC_SIZE(cmd));
if (err)
return -EFAULT;
3、位操作
Linux内核提供了操纵处理器端口位的函数,具体在#include
中进行定义 。常使用的有如下三个:
n s3c2410_gpio_cfgpin配置某端口某位
n s3c2410_gpio_setpin 设置某端口某位为高或低
n s3c2410_gpio_pullup设置某端口某位是否上拉
设置实例:
s3c2410_gpio_cfgpin(S3C2410_GPA0, S3C2410_GPA0_ADDR0);//将S3C2410的A组端口的0号引脚设置为地址总线的0号引脚功能。
s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);//将S3C2410的E组端口ude8号引脚设置为SDDAT总线的第1号引脚
设置是否上拉实例:
s3c2410_gpio_pullup(S3C2410_GPB0, 0);//使能B组端口的0号引脚的上拉功能。
s3c2410_gpio_pullup(S3C2410_GPE8, 0);//使能E组端口的8号引脚的上拉功能。
1 => disable the pull-up
0 => enable the pull-up
其中:
S3C2410_GPA0_ADDR0
S3C2410_GPE8_SDDAT1
在linux/include/asm/hardware/s3c2410/regs-gpio.h 下定义
4、IOCTL结合位操作方式控制LED实例:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPIO_IOCTL_MAGIC 'G'
#define LED_D09_SWT _IOW(GPIO_IOCTL_MAGIC, 0, unsigned int)
#define LED_D10_SWT _IOW(GPIO_IOCTL_MAGIC, 1, unsigned int)
#define LED_D11_SWT _IOW(GPIO_IOCTL_MAGIC, 2, unsigned int)
#define LED_D12_SWT _IOW(GPIO_IOCTL_MAGIC, 3, unsigned int)
#define GPIO_IOC_MAXNR 12
#define LED_SWT_ON 0
#define LED_SWT_OFF 1
#define VIRTUALMEM_MAJOR 0 /*预设的ioport的主设备号*/
static unsigned int gpfcon_old = 0;
static unsigned int gpfdat_old = 0;
static unsigned int gpfup_old = 0;
static int Virtualmem_major = VIRTUALMEM_MAJOR;
struct resource *IO_port_resource;
/*Virtualmem设备结构体*/
struct Virtualmem_dev
{
struct cdev cdev; /*cdev结构体*/
};
struct Virtualmem_dev *Virtualmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int Virtualmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = Virtualmem_devp;
return 0;
}
/*文件释放函数*/
int Virtualmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*读函数*/
static ssize_t Virtualmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
int ret = 0;
struct Virtualmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
return ret;
}
/*写函数*/
static ssize_t Virtualmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
int ret = 0;
struct Virtualmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
return ret;
}
static int gpio_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
{ /*ioctl函数接口:主要接口的实现 */
unsigned int swt = (unsigned int)arg;
int err = 0;
if (_IOC_TYPE(cmd) != GPIO_IOCTL_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > GPIO_IOC_MAXNR) return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
switch (cmd)
{
case LED_D09_SWT:
{
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
s3c2410_gpio_setpin(S3C2410_GPF7, swt);
break;
}
case LED_D10_SWT:
{
s3c2410_gpio_setpin(S3C2410_GPF6, swt);
break;
}
case LED_D11_SWT:
{
s3c2410_gpio_setpin(S3C2410_GPF5, swt);
break;
}
case LED_D12_SWT:
{
s3c2410_gpio_setpin(S3C2410_GPF4, swt);
break;
}
default:
{
printk("Unsupported command\n");
break;
}
}
return 0;
}
/*文件操作结构体*/
static const struct file_operations Virtualmem_fops =
{
.owner = THIS_MODULE,
.read = Virtualmem_read,
.write = Virtualmem_write,
.open = Virtualmem_open,
.release = Virtualmem_release,
.ioctl = gpio_ioctl, /* 实现主要控制功能*/
};
struct class *my_class= NULL;
/*初始化并注册cdev*/
static void Virtualmem_setup_cdev(struct Virtualmem_dev *dev, int index)
{
int err, devno = MKDEV(Virtualmem_major, index);
cdev_init(&dev->cdev, &Virtualmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &Virtualmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_ALERT "Error %d adding Virtualmem%d", err, index);
my_class =class_create(THIS_MODULE, "ioctldev");
if(IS_ERR(my_class)) {
printk("Err: failed in creating class.\n");
return ;
}
class_device_create(my_class,NULL,devno,NULL,"ioctldev");
}
/*设备驱动模块加载函数*/
int Virtualmem_init(void)
{
int result;
dev_t devno = MKDEV(Virtualmem_major, 0);
/* 申请设备号*/
if (Virtualmem_major)
result = register_chrdev_region(devno, 1, "ioctldev");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 1, "ioctldev");
Virtualmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
Virtualmem_devp = kmalloc(sizeof(struct Virtualmem_dev), GFP_KERNEL);
if (!Virtualmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail;
}
memset(Virtualmem_devp, 0, sizeof(struct Virtualmem_dev));
Virtualmem_setup_cdev(Virtualmem_devp, 0);
if ((IO_port_resource=request_region((unsigned long)S3C2410_GPFCON, 0x0c,"ioctldev"))==NULL)
goto fail;
else{
printk("In the init process! \n");
gpfcon_old = (unsigned int) inl((unsigned long)S3C2410_GPFCON);
gpfdat_old = (unsigned int) inl((unsigned long)S3C2410_GPFDAT);
gpfup_old = (unsigned int) inl((unsigned long)S3C2410_GPFUP);
printk("S3C2410_GPFCON is %8X\n",gpfcon_old);
printk("S3C2410_GPFUP is %8X\n",gpfup_old);
printk("S3C2410_GPFDAT is %8X\n",gpfdat_old);
//全部熄灭 LED
s3c2410_gpio_setpin(S3C2410_GPF4, 1);
s3c2410_gpio_setpin(S3C2410_GPF5, 1);
s3c2410_gpio_setpin(S3C2410_GPF6, 1);
s3c2410_gpio_setpin(S3C2410_GPF7, 1);
return 0;
}
fail: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void Virtualmem_exit(void)
{
if (IO_port_resource!=NULL) release_region((unsigned long)S3C2410_GPFCON, 0x0c);
cdev_del(&Virtualmem_devp->cdev); /*注销cdev*/
kfree(Virtualmem_devp); /*释放设备结构体内存*/
//全部点亮 LED
s3c2410_gpio_setpin(S3C2410_GPF4, 0);
s3c2410_gpio_setpin(S3C2410_GPF5, 0);
s3c2410_gpio_setpin(S3C2410_GPF6, 0);
s3c2410_gpio_setpin(S3C2410_GPF7, 0);
class_device_destroy(my_class, MKDEV(Virtualmem_major, 0));
class_destroy(my_class);
unregister_chrdev_region(MKDEV(Virtualmem_major, 0), 1); /*释放设备号*/
}
MODULE_AUTHOR("AK-47");
MODULE_LICENSE("Dual BSD/GPL");
module_param(Virtualmem_major, int, S_IRUGO);
module_init(Virtualmem_init);
module_exit(Virtualmem_exit);
5、测试代码
#include
#include
#include
#include
#include
#include
#include
#define GPIO_IOCTL_MAGIC 'G'
#define LED_D09_SWT _IOW(GPIO_IOCTL_MAGIC, 0, unsigned int)
#define LED_D10_SWT _IOW(GPIO_IOCTL_MAGIC, 1, unsigned int)
#define LED_D11_SWT _IOW(GPIO_IOCTL_MAGIC, 2, unsigned int)
#define LED_D12_SWT _IOW(GPIO_IOCTL_MAGIC, 3, unsigned int)
#define GPIO_IOC_MAXNR 12
#define LED_SWT_ON 0
#define LED_SWT_OFF 1
#define DEVICE_FILENAME "/dev/ioctldev"
int main( )
{
int dev;
dev = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
if(dev >= 0)
{
sleep(1);
ioctl(dev, LED_D09_SWT,LED_SWT_ON);
sleep(1);
ioctl(dev, LED_D09_SWT,LED_SWT_OFF);
sleep(1);
ioctl(dev, LED_D10_SWT,LED_SWT_ON);
sleep(1);
ioctl(dev, LED_D10_SWT,LED_SWT_OFF);
sleep(1);
ioctl(dev, LED_D11_SWT,LED_SWT_ON);
sleep(1);
ioctl(dev, LED_D11_SWT,LED_SWT_OFF);
sleep(1);
ioctl(dev, LED_D12_SWT,LED_SWT_ON);
sleep(1);
ioctl(dev, LED_D12_SWT,LED_SWT_OFF);
}
close(dev);
return 0;
}
6、测试驱动程序
加载驱动程序后,由于采用了4.1.10和4.1.11节所述的自动创建设备节点的方法,省去了手动创建的麻烦。创建设备节点,然后运行测试程序,会观察到4个发光二级管依次点亮。