Chinaunix首页 | 论坛 | 博客
  • 博客访问: 434895
  • 博文数量: 111
  • 博客积分: 4290
  • 博客等级: 上校
  • 技术积分: 1301
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-24 14:22
个人简介

努力工作,建立一个幸福的家庭。

文章分类

全部博文(111)

文章存档

2015年(4)

2013年(9)

2012年(6)

2011年(17)

2010年(69)

2009年(6)

分类: LINUX

2010-04-29 12:36:43

Linux设备驱动学习(4)-高级字符驱动程序操作<1>

一、 Ioctl

大部分设备除了读写能力,还可进行超出简单的数据传输之外的操作,所以设备驱动也必须具备进行各
种硬件控制操作的能力. 这些操作常常通过 ioctl 方法来支持:

在用户空间,ioctl有如下原型:
int ioctl(int handle, int cmd,[int *argdx, int argcx]);
参考:
后面的[]代表可变参数。但是在实际系统中,ioctl必须有精确的原型定义,因为应用程序必须通过硬件才
可以与硬件通信,所谓的硬件门即驱动驱动程序。

驱动程序的ioctl方法原型与用户空间有所不同;
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值,与传给open方法的参数一样。
注意:无论如何,可选参数arg都以unsigned long的形式传送,如果应用程序未传递arg,那么arg处于未定义
状态。由于对该参数的类型检查关闭了,故应用程序传递一个非法参数编译器也是不会报警的,这样,相关联
的程序错误就很难发现。

选择ioctl命令
为了防止向错误的设备使用正确的命令,命令号应该在系统范围内唯一。为方便程序员创建唯一的 ioctl 命令
代号, 每个命令号被划分为多个位字段。要按 Linux 内核的约定方法为驱动选择 ioctl 的命令号, 应该首先
看看 include/asm/ioctl.h 和 Documentation/ioctl-number.txt。 要使用的位字段符号定义在
  :
type(幻数):8 位宽(_IOC_TYPEBITS),参考ioctl-number.txt选择一个数,并在整个驱动中使用它。
number(序数):顺序编号,8 位宽(_IOC_NRBITS)。
direction(数据传送的方向):可能的值是 _IOC_NONE(没有数据传输)、_IOC_READ、_IOC_WRITE和
 _IOC_READ|_IOC_WRITE (双向传输数据)。该字段是一个位掩码(两位), 因此可使用 AND 操作来抽取
_IOC_READ 和 _IOC_WRITE。
size(数据的大小):宽度与体系结构有关,ARM为14位.可在宏 _IOC_SIZEBITS 中找到特定体系的值. 
  中包含的 定义了一些构造命令编号的宏:
  //构造无参数的命令编号   #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)   //构造从驱动程序中读取数据的命令编号   #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))   //用于向驱动程序写入数据命令   #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))   //用于双向传输   #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
/*type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到*/
这个头文件还定义了用来解开这个字段的宏:
  //从命令参数中解析出数据方向,即写进还是读出   #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)   //从命令参数中解析出幻数type   #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)   //从命令参数中解析出序数number   #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)   //从命令参数中解析出用户数据大小   #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
返回值
POSIX 标准规定:如果使用了不合适的 ioctl 命令号,应当返回-ENOTTY 。这个错误码被 C 库解释为"不合
适的设备 ioctl。然而,它返回-EINVAL仍是相当普遍的。
预定义命令
有一些ioctl命令是由内核识别的,当这些命令用于自己的设备时,他们会在我们自己的文件操作被调用之前
被解码. 因此, 如果你选择一个ioctl命令编号和系统预定义的相同时,你永远不会看到该命令的请求,而且
因为ioctl 号之间的冲突,应用程序的行为将无法预测。预定义命令分为 3 类:
(1)用于任何文件(常规, 设备, FIFO和socket) 的命令
(2)只用于常规文件的命令
(3)特定于文件系统类型的命令
下列 ioctl 命令是预定义给任何文件,包括设备特定文件:
FIOCLEX :设置 close-on-exec 标志(File IOctl Close on EXec)。
FIONCLEX :清除 close-no-exec 标志(File IOctl Not CLose on EXec)。
FIOQSIZE :这个命令返回一个文件或者目录的大小; 当用作一个设备文件, 但是, 它返回一个 ENOTTY
错误。
FIONBIO:"File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一节中描述)。 
使用ioctl参数
在使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,,就必须小
心。当用一个指针引用用户空间, 我们必须确保用户地址是有效的,其校验(不传送数据)由函数access_ok 实
现,定义在 :
 int access_ok(int type, const void *addr, unsigned long size);
 第一个参数应当是 VERIFY_READ(读)或VERIFY_WRITE(读写);addr 参数为用户空间地址,size 为字节
数,可使用sizeof()。access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题)。如果
它返回假,驱动应当返回 -EFAULT 给调用者。
注意:首先, access_ok不做校验内存存取的完整工作; 它只检查内存引用是否在这个进程有合理权限的
内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用 access_ok,而直
接使用put_user(datum, ptr)和get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存
地址,成功时返回 0, 并且在错误时返回 -EFAULT.。 put_user(datum, ptr)
__put_user(datum, ptr)
get_user(local, ptr)
__get_user(local, ptr)
这些宏它们相对copy_to_user 和copy_from_user快, 并且这些宏已被编写来允许传递任何类型的指针,只要
它是一个用户空间地址. 传送的数据大小依赖 prt 参数的类型, 并且在编译时使用 sizeof 和 typeof 等编
译器内建宏确定。他们只传送1、2、4或8 个字节。如果使用以上函数来传送一个大小不适合的值,结果常常
是一个来自编译器的奇怪消息,如"coversion to non-scalar type requested". 在这些情况中,必须使用
 copy_to_user 或者 copy_from_user。
__put_user和__get_user 进行更少的检查(不调用 access_ok), 但是仍然能够失败如果被指向的内存对用户
是不可写的,所以他们应只用在内存区已经用 access_ok 检查过的时候。作为通用的规则:当实现一个 read
 方法时,调用 __put_user 来节省几个周期, 或者当你拷贝几个项时,因此,在第一次数据传送之前调用
 access_ok 一次。
权能与受限操作
Linux 内核提供了一个更加灵活的系统, 称为权能(capability)。内核专为许可管理上使用权能并导
出了两个系统调用 capget 和 capset,这样可以从用户空间管理权能,其定义在
中。对设备驱动编写者有意义的权能如下: CAP_DAC_OVERRIDE /*越过在文件和目录上
的访问限制(数据访问控制或 DAC)的能力。*/
CAP_NET_ADMIN /*进行网络管理任务的能力, 包括那些能够影响网络接口的任务*/
CAP_SYS_MODULE /*加载或去除内核模块的能力*/
CAP_SYS_RAWIO /*进行 "raw"(裸)I/O 操作的能力. 例子包括存取设备端口或者直接和 USB 设备通讯*/
CAP_SYS_ADMIN /*截获的能力, 提供对许多系统管理操作的途径*/
CAP_SYS_TTY_CONFIG /*执行 tty 配置任务的能力*/
在进行一个特权操作之前, 一个设备驱动应当检查调用进程有合适的能力,检查是通过 capable 函数来进行的
(定义在 )范例如下: if (! capable (CAP_SYS_ADMIN))
 return -EPERM;
二、          定位设备(llseek实现)
llseek是修改文件中的当前读写位置的系统调用。内核中的缺省的实现进行移位通过修改 filp->f_pos, 这是
文件中的当前读写位置。对于 lseek 系统调用要正确工作,读和写方法必须通过更新它们收到的偏
移量来配合。如果设备是不允许移位的,你不能只制止声明 llseek 操作,因为缺省的方法允许移位。应当在
你的 open 方法中,通过调用 nonseekable_open 通知内核你的设备不支持 llseek :
int nonseekable_open(struct inode *inode; struct file *filp);
完整起见, 你也应该在你的 file_operations 结构中设置 llseek 方法到一个特殊的帮助函数
no_llseek(定义在 )。
--------------------------------------------------------------------------------------------------------------------
源代码:
1、驱动程序
/* * Filename:ioctl.c * Function:the advanced character test driver * Author:dengwei * Time:2010-4-28 */ #include #include #include #include /*size_t, dev_t*/ #include /* register_chrdev_region*/ #include #include #include /* O_ACCMODE */ #include /* struct cdev */ #include /* kfree */ #include /* contander_of */ #include /* error codes */ #include /* cli(), *_flags */ #include /* copy_*_user */ #include /* /proc */ #include /* seq_file */ #include "ioctl.h" static dev_t dev; static int scull_major = SCULL_MAJOR; static int scull_minor = SCULL_MINOR; static int scull_nr_devs = SCULL_NR_DEVS; static int scull_quantum = SCULL_QUANTUM; static int scull_qset = SCULL_QSET; static struct class *scull_class; static struct scull_dev *scull_devices; /*allocated in scull_init_module*/ static char __initdata info[] = "This is the advanced character test driver!\n"; /* * Empty out the scull device; must be called with the device * semaphore held. */ int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset = dev->qset; /* "dev" is not-null */ int i; for (dptr = dev->data; dptr; dptr = next) { /* all the list items */ if (dptr->data) { for (i = 0; i < qset; i++) kfree(dptr->data[i]); kfree(dptr->data); dptr->data = NULL; } next = dptr->next; kfree(dptr); } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0; } #ifdef SCULL_DEBUG /* use proc only if debugging */ /* * The proc filesystem: function to read and entry */ int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data) { int i, j, len = 0; int limit = count - 80; /* Don't print more than this */ for (i = 0; i < scull_nr_devs && len <= limit; i++) { struct scull_dev *d = &scull_devices[i]; struct scull_qset *qs = d->data; if (down_interruptible(&d->sem)) return -ERESTARTSYS; len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n",i, d->qset,\
 d->quantum, d->size); for (; qs && len <= limit; qs = qs->next) { /* scan the list */ len += sprintf(buf + len, " item at %p, qset at %p\n",qs, qs->data); if (qs->data && !qs->next) /* dump only the last item */ for (j = 0; j < d->qset; j++) { if (qs->data[j]) len += sprintf(buf + len,"% 4i: %8p\n",j,\
 qs->data[j]); } } up(&scull_devices[i].sem); } *eof = 1; return len; } /* * For now, the seq_file implementation will exist in parallel. The * older read_procmem function should maybe go away, though. */ /* * Here are our sequence iteration methods. Our "position" is * simply the device number. */ static void *scull_seq_start(struct seq_file *s, loff_t *pos) { if (*pos >= scull_nr_devs) return NULL; /* No more to read */ return scull_devices + *pos; } static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos) { (*pos)++; if (*pos >= scull_nr_devs) return NULL; return scull_devices + *pos; } static void scull_seq_stop(struct seq_file *s, void *v) { /* Actually, there's nothing to do here. Because that is scull */ } /* * 内核调用show来将实际的数据传送到用户空间 */ static int scull_seq_show(struct seq_file *s, void *v) { struct scull_dev *dev = (struct scull_dev *) v; struct scull_qset *d; int i; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",(int) (dev - scull_devices),\
 dev->qset,dev->quantum, dev->size); for (d = dev->data; d; d = d->next) { /* scan the list */ seq_printf(s, " item at %p, qset at %p\n", d, d->data); if (d->data && !d->next) /* dump only the last item */ for (i = 0; i < dev->qset; i++) { if (d->data[i]) seq_printf(s, " % 4i: %8p\n",i, d->data[i]); } } up(&dev->sem); return 0; } /* * scull将这些函数打包并和/proc中某个文件连接起来 */ static struct seq_operations scull_seq_ops = { .start = scull_seq_start, .next = scull_seq_next, .stop = scull_seq_stop, .show = scull_seq_show }; /* * Now to implement the /proc file we need only make an open * method which sets up the sequence operators. */ static int scull_proc_open(struct inode *inode, struct file *file) { return seq_open(file, &scull_seq_ops); } /* * Create a set of file operations for our proc file. */ static struct file_operations scull_proc_ops = { .owner = THIS_MODULE, .open = scull_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; /* * Actually create (and remove) the /proc file(s). */ static void scull_create_proc(void) { struct proc_dir_entry *entry; create_proc_read_entry("scullmem", 0 /* default mode */, NULL /* parent dir */, scull_read_procmem, NULL /* client data */); entry = create_proc_entry("scullseq", 0, NULL);
//dw: 是否有两个“scullmen and scullseq”? if (entry) entry->proc_fops = &scull_proc_ops; } static void scull_remove_proc(void) { /* no problem if it was not registered */ remove_proc_entry("scullmem", NULL /* parent dir */);
//dw: why we are create two devices "scullmen and scullseq" ? remove_proc_entry("scullseq", NULL); } #endif /* * open and close scull */ int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; /*device infomation*/ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /*for other methods*/ /* now trim to 0 the length of the device if open was write_only */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY){ scull_trim(dev); } return 0; } int scull_release(struct inode *inode, struct file *filp) { return 0; } /* *Follow the list */ struct scull_qset *scull_follow(struct scull_dev *dev, int n) { struct scull_qset *qs = dev->data; /* Allocate first qset explicitly if need be */ if (! qs) { qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs == NULL) return NULL; /* Never mind */ memset(qs, 0, sizeof(struct scull_qset)); } /* Then follow the list */ while (n--) { if (!qs->next) { qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs->next == NULL) return NULL; /* Never mind */ memset(qs->next, 0, sizeof(struct scull_qset)); } qs = qs->next; continue; } return qs; } /* * Data management read and write data */ ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; /*第一个链表项*/ int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; /* 该链表项有多少字节*/ int item, s_pos, q_pos, rest; ssize_t retval = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->size) goto out; if (*f_pos + count >= dev->size ) count = dev->size - *f_pos; /*在量子集中寻找链表项目、qset索引以及偏移量*/ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if (dptr == NULL || !dptr->data || !dptr->data[s_pos]) goto out; /* don't fill holes*/ /* 读取该量子的数据直到结尾 */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){ retval = -EFAULT; goto out; } *f_pos += count; retval = count; out: up(&dev->sem); return retval; } ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* find listitem, qset index and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; /* follow the list up to the right position */ dptr = scull_follow(dev, item); if (dptr == NULL) goto out; if (!dptr->data) { dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); if (!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); } if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; } /* write only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; /* update the size */ if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; } /* * The ioctl() implementation */ int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0, tmp; int retval = 0; /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
//#define SCULL_IOC_MAXNR 12 //_IOC_NR() : 读取基数域值 (bit0~ bit7) /* * the direction is a bitmask, and VERIFY_WRITE catches R/W * transfers. `Type' is user-oriented, while * access_ok is kernel-oriented, so the concept of "read" and * "write" is reversed */ if (_IOC_DIR(cmd) & _IOC_READ) //_IOC_DIR : 获取读写属性域值 (bit30 ~ bit31) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
//_IOC_SIZE : 读取数据大小域值 (bit16 ~ bit29) else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; /* * 调用access_ok后,驱动程序可以安全的进行实际的数据传送了。 */ //传递设备的参数配置及“unsigned long arg”的各种情况 switch(cmd) { case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; retval = __get_user(scull_quantum, (int __user *)arg); break; // __get_user(local,ptr):从用户空间接收一个值保存在local中 case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_quantum = arg; break; case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ retval = __put_user(scull_quantum, (int __user *)arg); break; // __put_user(datum,ptr):把datum写到用户空间 case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */ return scull_quantum; case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; retval = __get_user(scull_quantum, (int __user *)arg); if (retval == 0) retval = __put_user(tmp, (int __user *)arg); break; case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; scull_quantum = arg; return tmp; case SCULL_IOCSQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; retval = __get_user(scull_qset, (int __user *)arg); break; case SCULL_IOCTQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_qset = arg; break; case SCULL_IOCGQSET: retval = __put_user(scull_qset, (int __user *)arg); break; case SCULL_IOCQQSET: return scull_qset; case SCULL_IOCXQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; retval = __get_user(scull_qset, (int __user *)arg); if (retval == 0) retval = put_user(tmp, (int __user *)arg); break; case SCULL_IOCHQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; scull_qset = arg; return tmp; /* * The following two change the buffer size for scullpipe. * The scullpipe device uses this same ioctl method, just to * write less code. Actually, it's the same driver, isn't it? */ #if 0 case SCULL_P_IOCTSIZE: scull_p_buffer = arg; break; case SCULL_P_IOCQSIZE: return scull_p_buffer; #endif default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY; } return retval; } /* * The "extended" operations -- only seek * DW:定位设备(《ldd3》_P172) */ loff_t scull_llseek(struct file *filp, loff_t off, int whence) { struct scull_dev *dev = filp->private_data; loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos = off; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + off; break; case 2: /* SEEK_END */ newpos = dev->size + off; break; default: /* can't happen */ return -EINVAL; } if (newpos < 0) return -EINVAL; filp->f_pos = newpos; return newpos; } struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, //定位设备(《ldd3》_P172) .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release, }; /* * Finally, the module stuff */ /* * The cleanup function is used to handle initialization failures as well. * Thefore, it must be careful to work correctly even if some of the items * have not been initialized */ void scull_cleanup_module(void) { int i; dev_t devno = MKDEV(scull_major, scull_minor); /* Get rid of our char dev entries */ if (scull_devices) { for (i = 0; i < scull_nr_devs; i++) { scull_trim(scull_devices + i); cdev_del(&scull_devices[i].cdev); } kfree(scull_devices); } #ifdef SCULL_DEBUG /* use proc only if debugging */ //when that module wan deleted ,"/proc 入口项 " should be deleted scull_remove_proc(); #endif /* cleanup_module is never called if registering failed */ unregister_chrdev_region(devno, scull_nr_devs); device_destroy(scull_class, dev); class_destroy(scull_class); } static int scull_setup_cdev(struct scull_dev *scull_dev, int index) { int err; dev_t devno = MKDEV(scull_major,scull_minor + index); cdev_init(&scull_dev->cdev, &scull_fops); /*cdev 的初始化*/ scull_dev->cdev.owner = THIS_MODULE; scull_dev->cdev.ops = &scull_fops; err = cdev_add(&scull_dev->cdev, devno, 1); if (err) { printk(KERN_NOTICE "Error %d adding scull!", err); return err; } return 0; } /*scull 设备注册*/ static int __init scull_init(void) { int ret = -1; int i; printk(info); /* 动态分配设备号*/ if (scull_major){ dev = MKDEV(scull_major,scull_minor ); ret = register_chrdev_region(dev, scull_nr_devs, "scull"); } else{ ret = alloc_chrdev_region(&dev, scull_minor,scull_nr_devs,"scull"); scull_major = MAJOR(dev); } if (ret < 0){ printk(KERN_WARNING "scull: can't get major %d\n",scull_major); return ret; } else{ printk(DEVICE_NAME "major = %d\n",scull_major); } // struct cdev *sdev = cdev_alloc(); /*分配一个独立得cdev结构*/ scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); if (!scull_devices) { ret = -ENOMEM; goto fail; /* Make this more graceful 不严密需要修改*/ } memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); /* void *memset(void *s,int c,size_t n)总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c */ /* Initialize each device. */ for (i = 0; i < scull_nr_devs; i++) { scull_devices[i].quantum = scull_quantum; scull_devices[i].qset = scull_qset; init_MUTEX(&scull_devices[i].sem); ret = scull_setup_cdev(&scull_devices[i], i); if (ret < 0){ printk(DEVICE_NAME "can't register major module!\n"); return ret; } } #ifdef SCULL_DEBUG /* only when debugging */ scull_create_proc(); #endif /*注册一个类,使得mdev能在/dev下创建设备节点??????*/ scull_class = class_create(THIS_MODULE,DEVICE_NAME); if(IS_ERR(scull_class)) { printk(KERN_ALERT "Err:faile in scull_class!\n"); return -1; } /*创建设备节点,名字为DEVICE_NAME ,主设备号用上面动态生成的dev*/ device_create(scull_class, NULL, dev, NULL, DEVICE_NAME); printk(DEVICE_NAME"initializa\n"); return 0; fail: scull_cleanup_module(); return ret; } module_init(scull_init); module_exit(scull_cleanup_module); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("CHARACTER DEVICE DRVER"); MODULE_AUTHOR("dengwei"); --------------------------------------------------------------------------------/* * ioctl.h -- definitions for the char module */ #ifndef _SCULL_H_ #define _SCULL_H_ #include /* needed for the _IOW etc stuff used later */ /* * Macros to help debugging */ #undef PDEBUG /* undef it, just in case */ #ifdef SCULL_DEBUG #ifdef __KERNEL__ /* This one if debugging is on, and kernel space */ #define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args) #else /* This one for user space */ #define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) #endif #else #define PDEBUG(fmt, args...) /* not debugging: nothing */ #endif /******************************************************************** * 要修改老版本的Makefile * 加上下面的语句 DEBUG = y ifeq ($(DEBUG),y) DEBFLAGS = -O -g -DSCULL_DEBUG else DEBFLAGS = -O2 endif EXTRA_CFLAGS += $(DEBFLAGS) *******************************************************************/ #undef PDEBUGG #define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ #ifndef SCULL_MAJOR #define SCULL_MAJOR 0 /* dynamic major by default */ #endif #ifndef SCULL_MINOR #define SCULL_MINOR 0 #endif #ifndef SCULL_NR_DEVS #define SCULL_NR_DEVS 4 /* scull0 through scull3 */ #endif #ifndef SCULL_P_NR_DEVS #define SCULL_P_NR_DEVS 4 /* scullpipe0 through scullpipe3 */ #endif #define DEVICE_NAME "scull_advanced" /* * The bare device is a variable-length region of memory. * Use a linked list of indirect blocks. * * "scull_dev->data" points to an array of pointers, each * pointer refers to a memory area of SCULL_QUANTUM bytes. * * The array (quantum-set) is SCULL_QSET long. */ #ifndef SCULL_QUANTUM #define SCULL_QUANTUM 4000 #endif #ifndef SCULL_QSET #define SCULL_QSET 1000 #endif /* * The pipe device is a simple circular buffer. Here its default size */ #ifndef SCULL_P_BUFFER #define SCULL_P_BUFFER 4000 #endif /* * Representation of scull quantum sets. */ struct scull_qset { void **data; struct scull_qset *next; }; struct scull_dev { struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; /* amount of data stored here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ }; /* * Split minors in two parts */ #define TYPE(minor) (((minor) >> 4) & 0xf) /* high nibble */ #define NUM(minor) ((minor) & 0xf) /* low nibble */ #if 0 /* * The different configurable parameters */ extern int scull_major; /* main.c */ extern int scull_nr_devs; extern int scull_quantum; extern int scull_qset; #endif //extern int scull_p_buffer; /* pipe.c */ /* * Prototypes for shared functions */ #if 0 int scull_p_init(dev_t dev); void scull_p_cleanup(void); int scull_access_init(dev_t dev); void scull_access_cleanup(void); #endif int scull_trim(struct scull_dev *dev); ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); #if 0 loff_t scull_llseek(struct file *filp, loff_t off, int whence); #endif int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); /* * Ioctl definitions */ /* Use 'k' as magic number */ #define SCULL_IOC_MAGIC 'k' /* Please use a different 8-bit number in your code */ #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0) /* * S means "Set" through a ptr, * T means "Tell" directly with the argument value * G means "Get": reply by setting through a pointer * Q means "Query": response is on the return value * X means "eXchange": switch G and S atomically * H means "sHift": switch T and Q atomically */ #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) #if 0 /* * The other entities only have "Tell" and "Query", because they're * not printed in the book, and there's no need to have all six. * (The previous stuff was only there to show different ways to do it. */ #define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13) #define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14) /* ... more to come */ #define SCULL_IOC_MAXNR 14 #endif #define SCULL_IOC_MAXNR 12 //#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) #endif
#include #include #include #include #include "scull.h" int main() { char buffer1[20]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}; char buffer2[20]={0}; int sculltest; int code,i; if ((sculltest = open("/dev/scull_advanced",O_RDWR )) < 0) { printf("open error! \n"); exit(1); } printf("open scull_advanced ! \n"); /**********************************tekkaman************************************/ code = 10; if ( ioctl( sculltest , SCULL_IOCSQUANTUM , &code ) < 0) { printf("ioctl SCULL_IOCSQUANTUM error! \n"); exit(1); } /* * #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) * 用于向驱动程序写入数据命令 * #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size)) */ printf("SCULL_IOCSQUANTUM-SCULL_IOCQQUANTUM : scull_quantum=%d \n" , ioctl( sculltest , SCULL_IOCQQUANTUM , NULL ) ); // show: SCULL_IOCSQUANTUM-SCULL_IOCQQUANTUM : scull_quantum=10 if ( ioctl( sculltest , SCULL_IOCTQUANTUM , 6 ) < 0) { printf("ioctl SCULL_IOCTQUANTUM error! \n"); exit(1); } if ( ioctl( sculltest , SCULL_IOCGQUANTUM , &code ) < 0) { printf("ioctl SCULL_IOCGQUANTUM error! \n"); exit(1); } printf("SCULL_IOCTQUANTUM-SCULL_IOCGQUANTUM : scull_quantum=%d \n", code); // show: SCULL_IOCTQUANTUM-SCULL_IOCGQUANTUM : scull_quantum=6 code = 10; if ( ioctl( sculltest , SCULL_IOCXQUANTUM , &code ) < 0) { printf("ioctl SCULL_IOCXQUANTUM error! \n"); exit(1); } printf("SCULL_IOCXQUANTUM : scull_quantum=%d --> %d\n" , code ,
ioctl( sculltest , SCULL_IOCQQUANTUM , NULL ) ); // show:SCULL_IOCXQUANTUM : scull_quantum=6 --> 10 printf("SCULL_IOCHQUANTUM : scull_quantum=%d --> %d\n" ,
ioctl( sculltest , SCULL_IOCHQUANTUM , 6 ) , ioctl( sculltest , SCULL_IOCQQUANTUM , NULL ) ); // show:SCULL_IOCHQUANTUM : scull_quantum=10 --> 10 dw: why? /**********************************tekkaman************************************/ code = 2; if ( ioctl( sculltest , SCULL_IOCSQSET , &code ) < 0) { printf("ioctl SCULL_IOCSQQSET error! \n"); exit(1); } printf("SCULL_IOCSQSET-SCULL_IOCQQSET : scull_qset=%d \n" ,
 ioctl( sculltest , SCULL_IOCQQSET , NULL ) ); // show:SCULL_IOCSQSET-SCULL_IOCQQSET : scull_qset=2 if ( ioctl( sculltest , SCULL_IOCTQSET , 4 ) < 0) { printf("ioctl SCULL_IOCTQSET error! \n"); exit(1); } if ( ioctl( sculltest , SCULL_IOCGQSET , &code ) < 0) { printf("ioctl SCULL_IOCGQSET error! \n"); exit(1); } printf("SCULL_IOCTQSET-SCULL_IOCGQSET : scull_qset=%d \n", code); // show:SCULL_IOCTQSET-SCULL_IOCGQSET : scull_qset=4 code = 2; if ( ioctl( sculltest , SCULL_IOCXQSET , &code ) < 0) { printf("ioctl SCULL_IOCXQSET error! \n"); exit(1); } printf("SCULL_IOCXQSET : scull_qset=%d --> %d\n" , code ,
ioctl( sculltest , SCULL_IOCQQSET , NULL ) ); // show:SCULL_IOCXQSET : scull_qset=4 --> 2 printf("SCULL_IOCHQSET : scull_qset=%d --> %d\n" ,
ioctl( sculltest , SCULL_IOCHQSET , 4 ) , ioctl( sculltest , SCULL_IOCQQSET , NULL ) ); // show:SCULL_IOCHQSET : scull_qset=2 --> 2 dw:why? /**********************************tekkaman************************************/ printf("before reset : scull_quantum=%d scull_qset=%d \n" ,
ioctl( sculltest , SCULL_IOCQQUANTUM , NULL ) , ioctl( sculltest , SCULL_IOCQQSET , NULL ) ); // show:before reset : scull_quantum=6 scull_qset=4 close(sculltest); printf("close scull_advanced ! \n"); if ((sculltest = open("/dev/scull_advanced",O_RDWR )) < 0) { printf("open error! \n"); exit(1); } printf("reopen scull_advanced ! \n"); printf("reopen : scull_quantum=%d scull_qset=%d \n" ,
 ioctl( sculltest , SCULL_IOCQQUANTUM , NULL ) , ioctl( sculltest , SCULL_IOCQQSET , NULL ) ); for ( i=20 ; i>0 ; i-=code) { if ((code=write(sculltest , &buffer1[20-i] , i)) != i)
 printf("write error! code=%d i=%d \n",code,i); else printf("write ok! code=%d \n",code); } if((code = lseek(sculltest , 0 , SEEK_SET)) != 0)
printf("llseek error! code=%d \n",code); printf("lseek scull SEEK_SET-->0 ! \n"); for ( i=20 ; i>0 ; i-=code) { if ((code=read(sculltest , &buffer2[20-i] , i)) != i)
 printf("read error! code=%d i=%d \n",code,i); else printf("read ok! code=%d \n",code); } for(i=0;i<20;i+=5) printf("[%d]=%d [%d]=%d [%d]=%d [%d]=%d [%d]=%d\n",i,buffer2[i],i+1,
buffer2[i+1],i+2,buffer2[i+2],i+3,buffer2[i+3],i+4,buffer2[i+4]); //#if 0 /**********************************tekkaman************************************/ if ( ioctl( sculltest , SCULL_IOCRESET , NULL ) < 0) { printf("ioctl SCULL_IOCRESET error! \n"); exit(1); } printf("SCULL_IOCRESET \n" ); printf("after reset : scull_quantum=%d scull_qset=%d \n" ,
 ioctl( sculltest , SCULL_IOCQQUANTUM , NULL ) , ioctl( sculltest , SCULL_IOCQQSET , NULL ) ); close(sculltest); printf("close scull_advanced ! \n"); if ((sculltest = open("/dev/scull_advanced",O_RDWR )) < 0) { printf("open error! \n"); exit(1); } printf("reopen scull_advanced ! \n"); for ( i=20 ; i>0 ; i-=code) { if ((code=write(sculltest , &buffer1[20-i] , i)) != i)
printf("write error! code=%d i=%d \n",code,i); else printf("write ok! code=%d \n",code); } if((code = lseek(sculltest , -10 , SEEK_CUR)) != 10)
printf("llseek error! code=%d \n",code); printf("lseek scull SEEK_CUR-10-->10 ! \n"); for ( i=10 ; i>0 ; i-=code) { if ((code=read(sculltest , &buffer2[10-i] , i)) != i)
printf("read error! code=%d i=%d \n",code,i); else printf("read ok! code=%d \n",code); } for(i=0;i<10;i+=5) printf("[%d]=%d [%d]=%d [%d]=%d [%d]=%d [%d]=%d\n",i,buffer2[i],i+1,\
buffer2[i+1],i+2,buffer2[i+2],i+3,buffer2[i+3],i+4,buffer2[i+4]); if((code = lseek(sculltest , -20 , SEEK_END)) != 0)
printf("llseek error! code=%d \n",code); printf("lseek scull SEEK_END-20-->0 ! \n"); for ( i=20 ; i>0 ; i-=code) { if ((code=read(sculltest , &buffer2[20-i] , i)) != i)
printf("read error! code=%d i=%d \n",code,i); else printf("read ok! code=%d \n",code); } for(i=0;i<20;i+=5) printf("[%d]=%d [%d]=%d [%d]=%d [%d]=%d [%d]=%d\n",i,buffer2[i],i+1,\
buffer2[i+1],i+2,buffer2[i+2],i+3,buffer2[i+3],i+4,buffer2[i+4]); close(sculltest); printf("close scull ! \n"); printf("\n"); exit(0); } /* * Ioctl definitions */ /* Use 'k' as magic number */ #define SCULL_IOC_MAGIC 'k' /* Please use a different 8-bit number in your code */ #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0) /* * S means "Set" through a ptr, * T means "Tell" directly with the argument value * G means "Get": reply by setting through a pointer * Q means "Query": response is on the return value * X means "eXchange": switch G and S atomically * H means "sHift": switch T and Q atomically */ #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) //作为测试程序 ,上面的这些宏定义与驱动程序中的宏定义相对应。 //#define SCULL_IOC_MAXNR 12
小结:
本函数是用来控制输入/输出设备的,请见下表: ____________________________________________________________________
│cmd值 │功能 |
├───┼────────────────────────────--┤ │ 0 │取出设备信息 │ 1 │设置设备信息 | │ 2 │把argcx字节读入由argdx所指的地址 │ │ 3 │在argdx所指的地址写argcx字节 │ │ 4 │除把handle当作设备号(0=当前,1=A,等)之外,均和cmd=2时一样 │ │ 5 │除把handle当作设备号(0=当前,1=A,等)之外,均和cmd=3时一样 │ │ 6 │取输入状态 | │ 7 │取输出状态 | │ 8 │测试可换性;只对于DOS 3.x │ │ 11 │置分享冲突的重算计数;只对DOS 3.x │ └───┴────────────────────────────—|
2、cmd参数怎么得出   这里确实要说一说,cmd参数在用户程式端由一些宏根据设备类型、序列号、传送方向、   ioctl关键是理解cmd命令码是怎么在用户程式里生成
阅读(1694) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~