分类: 嵌入式
2010-08-07 22:44:27
/* * main.c -- the bare scull 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. * */ //由于编译报错,这几行是我添加的 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "scull.h" /* local definitions */ /*scull默认的scull主、次设备号,设备数量,量子大小,数组大小*/ 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; /*设置的模块参数:scull主、次设备号,设备数量,量子大小,数组大小。加载模块时可设置该变量的值*/ module_param(scull_major, int, S_IRUGO); module_param(scull_minor, int, S_IRUGO); module_param(scull_nr_devs, int, S_IRUGO); module_param(scull_quantum, int, S_IRUGO); module_param(scull_qset, int, S_IRUGO); /*模块的作者和遵循的许可申明*/ MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet"); MODULE_LICENSE("Dual BSD/GPL"); struct scull_dev *scull_devices; /* 模块的scull_dev数据结构,将会在模块初始化中分配内存*/ /*负责释放scull设备数据区*/ int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset,i; if(dev == NULL) /*这里是我添加的判断指针不为空的语句*/ return 0; qset = dev->qset; for (dptr = dev->data; dptr; dptr = next) { /*遍历所有的指针链表项 */ 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; /*恢复scull_dev结构体中量子大小、数组大小等的默认值*/ dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0; } /* * Open and close */ int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); //使用宏container_of获得指向结构体scull_dev的指针 filp->private_data = dev; /* for other methods */ //文件访问模式为只写 if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { if (down_interruptible(&dev->sem)) //等待信号量被中断,则返回ERESTARTSYS return -ERESTARTSYS; scull_trim(dev); /* ignore errors */ up(&dev->sem); //释放信号量 } return 0; /* success */ } int scull_release(struct inode *inode, struct file *filp) { return 0; } /*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; } /* * Data management: read and write */ 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) //读取位置后的数据少于read多要读取的字节数 count = dev->size - *f_pos; /* 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 (defined elsewhere) */ dptr = scull_follow(dev, item); //根据链表项序数和dev指针找到对应读取的链表项指针 if (dptr == NULL || !dptr->data || ! dptr->data[s_pos]) goto out; /* don't fill holes */ /* read only up to the end of this quantum */ 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; } //scull_write类似scull_read 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; } /*ioctl操作函数 */ int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0, tmp; int retval = 0; //检查ioctl命令的幻数是否是scull分配的字段,并检查命令是否在scull规定的范围内 if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; //使用access_ok检查用户空间地址的的合法性,若成功则返回1,否则返回0 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) { //复位scull_quantum、scull_qset为默认值 case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; //通过指针设置scull_quantum的值 case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ if (! capable (CAP_SYS_ADMIN)) //capable函数检查用户的特权级别 return -EPERM; retval = __get_user(scull_quantum, (int __user *)arg); //从用户空间读取一个值到scull_quantum break; //通过传递值来设置scull_quantum的值 case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ if (! capable (CAP_SYS_ADMIN)) //capable函数检查用户的特权级别 return -EPERM; scull_quantum = arg; //这里直接将传递过来的值复制给scull_quantum break; //通过指针获取scull_quantum的值 case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ retval = __put_user(scull_quantum, (int __user *)arg); break; //通过值获取scull_quantum的值 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; /* scull_qset 的设置同上*/ 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 */ 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; } /*scull定义的文件操作结构体*/ struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .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); /* 在这里释放设备内存时,参考LDD3 P65的图3-1 scull设备的布局*/ if (scull_devices) { for (i = 0; i < scull_nr_devs; i++) { // scull_nr_devs为设备的数量 scull_trim(scull_devices + i); //释放scull设备内存 cdev_del(&scull_devices[i].cdev); //注销字符设备 } kfree(scull_devices); //释放scull设备 } #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(); } /* * Set up the char_dev structure for this device. */ static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(KERN_NOTICE "Error %d adding scull%d", err, index); } int scull_init_module(void) { int result, i; dev_t dev = 0; /* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */ if (scull_major) { //由于这里的scull_major默认为0,将会进行动态分配设备号 dev = MKDEV(scull_major, scull_minor); //获得设备号 result = register_chrdev_region(dev, scull_nr_devs, "scull"); //注册设备号 } else { 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\n", scull_major); return result; } //分配scull设备结构体 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)); //根据量子大小等变量来初始化scull设备结构体 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); //初始化信号量 scull_setup_cdev(&scull_devices[i], i); //该函数封装了字符设备注册函数 } /* At this point call the init function for any friend device */ dev = MKDEV(scull_major, scull_minor + scull_nr_devs); dev += scull_p_init(dev); dev += scull_access_init(dev); #ifdef SCULL_DEBUG /* only when debugging */ scull_create_proc(); #endif return 0; /* succeed */ fail: //初始化模块出错处理 scull_cleanup_module(); return result; } module_init(scull_init_module); module_exit(scull_cleanup_module); |