实现ioctl
  ioctl是Linux专门为用户层控制设备设计的系统调用接口,这个接口具有极大的灵活性,我们的设备打算让用户通过哪些命令实现哪些功能,都可以通过它来实现,ioctl在操作方法集中对应的函数指针是 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); ,其中的命令和参数完全由驱动指定,Linux建议如图所示的方式定义ioctl()命令
  设备类型    序列号     方向      数据尺寸
  8bit        8bit    2bit    13/14bit
  这里,设备类型字段为一个幻数,可以是0~0xff之间的数,内核中的" ioctl-number.txt "给出了一个推荐的和已经被使用的幻数(但是已经好久没人维护了),新设备驱动定义幻数的时候要避免与其冲突。命令码的方向字段为2bit,表示数据的传输方向,可能的值是: _IOC_NONE , _IOC_READ , _IOC_WRITE 和 _IOC_READ|_IOC_WRITE 。命令码的数据字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13或14位。内核还定义了 _IO() , _IOR() , _IOW() , _IOWR() 这4个宏来辅助生成这种格式的命令。这几个宏的作用是根据传入的type(设备类型字段),nr(序列号字段)和size(数据长度字段)和宏名??行的方向字段移位组合生成命令码。内核中还预定义了一些I/O控制命令,如果某设备驱动中包含了与预定义命令一样的命令码,这些命令会被当做预定义命令被内核处理而不是被设备驱动处理,有如下4种:
  FIOCLEX:即file ioctl close on exec 对文件设置专用的标志,通知内核当exec()系统带哦用发生时自动关闭打开的文件
  FIONCLEX:即file ioctl not close on exec,清除由FIOCLEX设置的标志
  FIOQSIZE:获得一个文件或目录的大小,当用于设备文件时,返回一个ENOTTY错误
  FIONBIO:即file ioctl non-blocking I/O 这个调用修改flip->f_flags中的O_NONBLOCK标志
  我们可以将驱动设计的命令包含在一个头文件中,记录用户程序和驱动程序的命令约定,下面是一个简单的例子
//mycmd.h
...
#include <asm/ioctl.h>
#define CMDT 'A'
#define KARG_SIZE 36
struct karg{
int kval;
char kbuf[KARG_SIZE];
};
#define CMD_OFF _IO(CMDT,0)
#define CMD_ON  _IO(CMDT,1)
#define CMD_R   _IOR(CMDT,2,struct karg)
#define CMD_W   _IOW(CMDT,3,struct karg)
...
//chrdev.c
static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
static struct karg karg = {
.kval = 0,
.kbuf = {0},
};
struct karg *usr_arg;
switch(cmd){
case CMD_ON:
/* 开灯 */
break;
case CMD_OFF:
/* 关灯 */
break;
case CMD_R:
if(_IOC_SIZE(cmd) != sizeof(karg)){
return -EINVAL;
}
usr_arg = (struct karg *)arg;
if(copy_to_user(usr_arg, &karg, sizeof(karg))){
return -EAGAIN;
}
break;
case CMD_W:
if(_IOC_SIZE(cmd) != sizeof(karg)){
return -EINVAL;
}
usr_arg = (struct karg *)arg;
if(copy_from_user(&karg, usr_arg, sizeof(karg))){
return -EAGAIN;
}
break;
default:
;
};
return 0;
}
  创建设备文件
  插入的设备模块,我们可以使用 cat /proc/devices 命令查看当前系统注册的设备,但是我们还没有创建相应的设备文件,用户也不能通过文件访问这个设备。设备文件的inode应该是包含了这个设备的设备号,操作方法集指针等信息,这样我们可以通过设备文件找到相应的inode进而访问设备。创建设备文件的方法有两种, 手动创建 或 自动创建 , 手动创建设备文件 是使用 mknod /dev/xxx 设备类型 主设备号 次设备号 的命令创建,所以首先需要使用 cat /proc/devices 查看设备的主设备号并通过源码找到设备的次设备号,需要注意的是,理论上设备文件可以放置在任何文件加夹,但是放到 "/dev" 才符合Linux的设备管理机制,这里面的devtmpfs是专门设计用来管理设备文件的文件系统。设备文件创建好之后会和创建时指定的设备绑定,即使设备已经被卸载了,如要删除设备文件,只需要像删除普通文件一样 rm 即可。理论上模块名(lsmod),设备名(/proc/devices),设备文件名(/dev)并没有什么关系,完全可以不一样,但是原则上还是建议将三者进行统一,便于管理。
  除了使用蹩脚的手动创建设备节点的方式,我们还可以在设备源码中使用相应的措施使设备一旦被加载 自动创建设备文件 ,自动创建设备文件需要我们在编译内核的时候或制作根文件系统的时候好相应的配置:
  Device Drivers --->
  Generic Driver Options --->
  [*]Maintain a devtmpfs filesystem to mount at /dev
  [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs
  OR
  制作根文件系统的启动脚本写入
  mount -t sysfs none sysfs /sys
  mdev -s //udev也行
  有了这些准备,只需要导出相应的设备信息到 "/sys" 可以按照我们的要求自动创建设备文件。内核给我们提供了相关的API
  class_create(owner,name);
  struct device *device_create_vargs(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, va_list vargs);
  void class_destroy(struct class *cls);
  void device_destroy(struct class *cls, dev_t devt);
  有了这几个函数,我们可以在设备的 xxx_init() 和 xxx_exit() 中分别填写以下的代码可以实现自动的创建删除设备文件
/* 在/sys中导出设备类信息 */
cls = class_create(THIS_MODULE,DEV_NAME);
/* 在cls指向的类中创建一组(个)设备文件 */
for(i= minor;i<(minor+cnt);i++){
devp = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEV_NAME,i);
}
/* 在cls指向的类中删除一组(个)设备文件 */
for(i= minor;i<(minor+cnt);i++){
device_destroy(cls,MKDEV(major,i));
}
/* 在/sys中删除设备类信息 */
class_destroy(cls);           //一定要先卸载device再卸载class
  完成了这些工作,一个简单的字符设备驱动搭建完成了,现在可以写一个用户程序进行测试了^ - ^