/**//* * Our parameters which can be set at load time. */
int scull_major = SCULL_MAJOR; int scull_minor =0; int scull_nr_devs = SCULL_NR_DEVS; /**//* number of bare scull devices */ int scull_quantum = SCULL_QUANTUM; int scull_qset = SCULL_QSET;
/**//* * 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; return0; }
//Start: [Tag003] proc的实现,debug driver 的重要手段; #ifdef SCULL_DEBUG /**//* use proc only if debugging */ //这个是老方法实现的proc,我们在这重点看新的方法的实现; /**//* * 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," Device %i: qset %i, q %i, sz %li ", 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 ", 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 ", 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. */ //下面的是用新方法实现的; /**//* * seq_file接口假定我们正在创建的虚拟文件要顺序 * 遍历一个项目序列,而这些项目正是必须要返回给用户空间的; * * 为使用seq_file,我们必须建立一个简单的"迭代器(iterator)"对象,该 * 对象用来表示项目序列中的位置,每前进一步,该对象输出序列 * 中的一个项目. * */
if (down_interruptible(&dev->sem)) return-ERESTARTSYS; seq_printf(s, " Device %i: qset %i, q %i, sz %li ", (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 ", 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 ", i, d->data[i]); } } up(&dev->sem); return0; }
/**//* * Now to implement the /proc file we need only make an open * method which sets up the sequence operators. */ staticint scull_proc_open(struct inode *inode, struct file *file) ...{ /**//* * seq_open的调用将file结构和我们上面定义的那组迭代器操作方法 * 连接在一起; */ return seq_open(file, &scull_seq_ops); }
/**//* * Create a set of file operations for our proc file. * open是我们惟一要实现的文件操作; */ staticstruct file_operations scull_proc_ops =...{ .owner = THIS_MODULE, .open = scull_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release };
/**//* New function * name -- 文件名; * mode -- 访问保护掩码; "0" -- 表示系统默认值; * parent--父目录; "NULL" -- 表示"scullseq"文件将创建在/proc的根目录下; */ entry = create_proc_entry("scullseq", 0, NULL); if (entry) entry->proc_fops =&scull_proc_ops; }
staticvoid scull_remove_proc(void) ...{ /**//* no problem if it was not registered */ remove_proc_entry("scullmem", NULL /**//* parent dir */); remove_proc_entry("scullseq", NULL); }
#endif /* SCULL_DEBUG */ //End
/**//* 开始实现对设备操作的方法集了,关键!!! */ /**//* * Open and close */ //[Tag004] /**//* open应完成的工作有: 1.检查设备特定的错误(诸如设备未就绪或类似的硬件问题) 2.如果设备是首次打开,则对其进行初始化; 3.如有必要,更新f_op指针; 4.分配并填写filp->private_data;(在这里我们只实现这项即可) */
/**//* 以后read , write ,等操作的实现中就靠他来得到dev了; */ 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) ...{ if (down_interruptible(&dev->sem)) return-ERESTARTSYS; scull_trim(dev); /**//* ignore errors */ up(&dev->sem); } return0; /**//* success */ }
/**//* close device file, in here we do nothing */ /**//* * [Tag005] * close应完成的工作有: * 1.释放由open分配的,保存在filp->private_data中的所有内容; * 2.在最后一次关闭操作时关闭设备; * [注意:]并不是每次的close系统调用都会去调用到release. 在open时,也仅在open时才会创建 * 一个新的数据结构;在fork, dup时只是增加了这个结构中维护的一个引用计数; * 所以当这个引用计数为0时,调用的close才意味着要释放设备数据结构,此时release才会被调用; */ int scull_release(struct inode *inode, struct file *filp) ...{ return0; }
/**//* * Follow the list * * 第一次调用时用于创建链表; * 然后就是找到第n个节点; * 对编写驱动程序关系不大; */ 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; }
//与read的实现类似 ssize_t scull_write(struct file *filp, constchar __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) gotoout; if (!dptr->data) ...{ dptr->data = kmalloc(qset *sizeof(char*), GFP_KERNEL); if (!dptr->data) gotoout; 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]) gotoout; } /**//* write only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos;
int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) ...{
int err =0, tmp; int retval =0;
/**//* * 命令(cmd)参数有效性的检查; * 1.check幻数; * 2. check command length; * 3. check 传输数据方向; */ /**//* * 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;
/**//* * 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) err =!access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); elseif (_IOC_DIR(cmd) & _IOC_WRITE) err =!access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return-EFAULT;
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;
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;
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? */
case SCULL_P_IOCTSIZE: scull_p_buffer = arg; break;
case SCULL_P_IOCQSIZE: return scull_p_buffer;
default: /**//* redundant, as cmd was checked against MAXNR */ return-ENOTTY; } return retval;
}
/**//* * The "extended" operations -- only seek */
//[Tag008]模块卸载或goto fail时; /**//* * 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 */ scull_remove_proc(); #endif
/**//* cleanup_module is never called if registering failed */ unregister_chrdev_region(devno, scull_nr_devs);
/**//* and call the cleanup functions for friend devices */ scull_p_cleanup(); scull_access_cleanup();
[2]告诉内核我们新结构的信息; */ /**//* * Set up the char_dev structure for this device. */ staticvoid scull_setup_cdev(struct scull_dev *dev, int index) ...{ int err, devno = MKDEV(scull_major, scull_minor + index);
/**//*[Tag000] * 当模块加载时,调用;但是为什么要放在最后来实现他呢,看到Tag002时,你应该就明白了; */ int scull_init_module(void) ...{ int result, i; dev_t dev =0;
/**//* [Tag001] */ /**//* [1]分配设备编号 */ /**//* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */ if (scull_major) ...{ /**//* 预先自己指定了主设备号 */ dev = MKDEV(scull_major, scull_minor); /**//* 利用主设备号,找到设备编号给方法1用 */ result = register_chrdev_region(dev, scull_nr_devs, "scull"); }else...{ /**//* 动态自己生成设备编号,然后再利用设备编号得到主设备号; 记住如果用这个方法那么就要后建设备文件了,因为不能提前知道主号 当然也可以利用ldd3书中提供的脚本,巨方便&&通用 */ result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); scull_major = MAJOR(dev); } if (result <0) ...{ printk(KERN_WARNING "scull: can't get major %d ", scull_major); return result; }
/**//*[2]设备对象实例化*/ /**//* * allocate the devices -- we can't have them static, as the number * can be specified at load time */ scull_devices = kmalloc(scull_nr_devs *sizeof(struct scull_dev), GFP_KERNEL); if (!scull_devices) ...{ result =-ENOMEM; goto fail; /**//* Make this more graceful */ } memset(scull_devices, 0, scull_nr_devs *sizeof(struct scull_dev));
/**//* [3]在这里初始化设备用了2.6的新方法,在scull_setup_cdev里完成 */ /**//* Initialize each device. */ for (i =0; i < scull_nr_devs; i++) ...{ scull_devices[i].quantum = scull_quantum; /**//* 可以根据自己insmod时传参 来自己改变量子和量子集(指针数组)的大小 */ scull_devices[i].qset = scull_qset; init_MUTEX(&scull_devices[i].sem); scull_setup_cdev(&scull_devices[i], i); /**//* 在分别完主设备编号后goto Tag002 设备注册 */ }
/**//* At this point call the init function for any friend device */ dev = MKDEV(scull_major, scull_minor + scull_nr_devs); /**//* * 下面二个是另外二个driver,提供了更高级的操作 * 阻塞式的read,write的实现,异步通知的实现; * 对设备打开权限控制的实现; */ dev += scull_p_init(dev); dev += scull_access_init(dev);
#ifdef SCULL_DEBUG /**//* only when debugging */ scull_create_proc(); #endif
/**//* * scull.h -- definitions for the char module * * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet * Copyright (C) 2001 O'Reilly & Associates * * The source code in this file can be freely used, adapted, * and redistributed in source or binary form, so long as an * acknowledgment appears in derived source files. The citation * should list that the code comes from the book "Linux Device * Drivers" by Alessandro Rubini and Jonathan Corbet, published * by O'Reilly & Associates. No warranty is attached; * we cannot take responsibility for errors or fitness for use. * * $Id: scull.h,v 1.15 2004/11/04 17:51:18 rubini Exp $ */
#ifndef _SCULL_H_ #define _SCULL_H_
#include <linux/ioctl.h>/**//* 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
/**//* * 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 /* 每个指针(量子)指向一个4000字节的区域 */ #endif
/**//* 我们自己的设备(包含了基本的cdev字符设备结构) */ 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 */
/**//* * The different configurable parameters */ externint scull_major; /**//* main.c */ externint scull_nr_devs; externint scull_quantum; externint scull_qset;
externint scull_p_buffer; /**//* pipe.c */
/**//* * Prototypes for shared functions */
int scull_p_init(dev_t dev); void scull_p_cleanup(void); int scull_access_init(dev_t dev); void scull_access_cleanup(void);
/**//* 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)
/**//* * 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 */