www.pudn.com > driverII_demo.rar > scull.c
/* linux device drivers scull modified to linux-2.6.x zhengjie caolingzi@netease.com */ #include#include #include #include #include /* devfs_mk_dir() */ #include #include /* class_simple_create() */ #include /* module_param() */ #include /* access_ok() */ #include /* capable() */ #include /* CAP_SYS_ADMIN */ #include "scull.h" static int scull_major; int scull_quantum = SCULL_QUANTUM; int scull_qset = SCULL_QSET; typedef struct scull_dev { void **data; struct scull_dev *next; int quantum; int qset; size_t size; struct semaphore sem; } scull_dev; struct scull_dev *scull_devices; struct class_simple *scull_class; 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, }; static int scull_trim(struct scull_dev *dev) { int qset = dev->qset; int i; struct scull_dev *next, *dptr; for(dptr=dev; dptr; dptr=next){ if(dptr->data){ for(i=0; i data[i]) kfree(dptr->data[i]); kfree(dptr->data); dptr->data = NULL; } } next = dptr->next; if(dptr != dev) kfree(dptr); } dev->size = 0; dev->qset = SCULL_QSET; dev->quantum = SCULL_QUANTUM; dev->next = NULL; return 0; } static struct scull_dev* scull_follow(struct scull_dev *dev, int n) { while (n--) { if (!dev->next) { dev->next = kmalloc(sizeof(scull_dev), GFP_KERNEL); memset(dev->next, 0, sizeof(scull_dev)); } dev = dev->next; continue; } return dev; } static ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct scull_dev *dev, *dptr; ssize_t ret = 0; int qnum, qset, qtotal; int item, s_pos, q_pos, rest; dev = filp->private_data; qnum = dev->quantum; qset = dev->qset; qtotal = qnum*qset; if(down_interruptible(&dev->sem)) return -ERESTARTSYS; if(*ppos > dev->size) goto out; if(*ppos + count > dev->size) count = dev->size - *ppos; item = (long)*ppos / qtotal; rest = (long)*ppos % qtotal; s_pos = rest / qnum; q_pos = rest % qnum; dptr = scull_follow(dev, item); if(!dptr->data) goto out; if(!dptr->data[s_pos]) goto out; if(count > qtotal - q_pos) count = qtotal - q_pos; if(copy_to_user(buf, dptr->data[s_pos] + q_pos , count)){ ret = -EFAULT; goto out; } *ppos += count; ret = count; out: up(&dev->sem); return ret; } static ssize_t scull_write(struct file *filp, const __user char *buf, size_t count, loff_t *ppos ) { struct scull_dev *dev; struct scull_dev *dptr; ssize_t ret = 0; int qnum, qset, qtotal; int item, rest, s_pos, q_pos; dev = filp->private_data; qnum = dev->quantum; qset = dev->qset; qtotal = qnum*qset; if(down_interruptible(&dev->sem)) return -ERESTARTSYS; item = (long)*ppos / qtotal; rest = (long)*ppos % qtotal; s_pos = rest / qnum; q_pos = rest % qnum; dptr = scull_follow(dev, item); if(!dptr->data){ dptr->data = kmalloc(qset*sizeof(char), GFP_KERNEL); if(!dptr->data) goto out; } if(!dptr->data[s_pos]){ dptr->data[s_pos] = kmalloc(qnum*sizeof(char), GFP_KERNEL); if(!dptr->data[s_pos]) goto out; } if(count > qnum - q_pos) count = qnum - q_pos; if(copy_from_user(dptr->data[s_pos] + q_pos, buf, count)){ ret = -EFAULT; goto out; } *ppos += count; ret = count; out: up(&dev->sem); return ret; } static int scull_ioctl(struct inode *inode, struct file *flip, unsigned int cmd, unsigned long arg) { int err = 0; int ret = 0; char __user *argp = (char __user *) arg; if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; if(_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; // if(_IOC_DIR(cmd) & _IOC_READ) // err = access_ok(VERIFY_WRITE, (void *)argp, _IOC_SIZE(cmd)); // printk("err=%d\n", err); // if(_IOC_DIR(cmd) & _IOC_WRITE) // err = access_ok(VERIFY_READ, (void *)argp, _IOC_SIZE(cmd)); // printk("err=%d\n", err); // if(!err) return -EFAULT; switch(cmd){ case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; // case SCULL_IOCSQUANTUM: // if(!capable(CAP_SYS_ADMIN)) // return -EPERM; // ret = __get_user(scull_quantum, argp); // break; // case SCULL_IOCSQSET: // break; case SCULL_IOCTQUANTUM: if(!capable(CAP_SYS_ADMIN)) return -EPERM; ret = arg; break; case SCULL_IOCTQSET: printk("<3>Hello, Kernel-2.6.10_scull!\n"); break; // case SCULL_IOCGQUANTUM: // ret = __put_user(scull_quantum, argp); // break; // case SCULL_IOCGQSET: // break; case SCULL_IOCQQUANTUM: return scull_quantum; break; case SCULL_IOCQQSET: break; // case SCULL_IOCXQUANTUM: // if(!capable(CAP_SYS_ADMIN)) // return -EPERM; // tmp = scull_quantum; // ret = __get_user(scull_quantum, argp); // if(ret == 0) // ret = __put_user(scull_quantum, argp); // break; // case SCULL_IOCXQSET: // break; // case SCULL_IOCHQUANTUM: // if(!capable(CAP_SYS_ADMIN)) // return -EPERM; // tmp = scull_quantum; // scull_quantum = argp; // return tmp; // break; case SCULL_IOCHQSET: break; default: return -ENOTTY; } return ret; } static int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; unsigned int minor = iminor(inode); if(minor > 2) return -ENXIO; dev = (struct scull_dev *)filp->private_data; if(!dev){ dev = scull_devices; filp->private_data = dev; } if((filp->f_flags & O_ACCMODE) == O_WRONLY){ if(down_interruptible(&dev->sem)){ return -ERESTARTSYS; } scull_trim(dev); up(&dev->sem); } return 0; } static int scull_release(struct inode *inode, struct file *filp) { return 0; } static loff_t scull_llseek(struct file *filp, loff_t off, int whence) { 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: return -EINVAL; } if(newpos < 0) return -EINVAL; filp->f_pos = newpos; return newpos; } static int __init scull_init_module(void) { int result = 0, tmp; scull_major = register_chrdev(0, "scull", &scull_fops); if(scull_major < 0){ printk(KERN_ERR"Scull - cannot register device\n"); return scull_major; } if(scull_major == 0){ printk(KERN_ERR"Scull major didnt register into kernel\n"); return scull_major; } scull_class = class_simple_create(THIS_MODULE, "scull"); if(IS_ERR(scull_class)){ result = PTR_ERR(scull_class); goto fail_malloc; } class_simple_device_add(scull_class, MKDEV(scull_major,0), NULL, "scull"); /* Create scull devfs */ devfs_mk_dir("scull"); tmp = devfs_mk_cdev(MKDEV(scull_major, 0), S_IFCHR | S_IRUSR | S_IWUSR, "scull/0"); if(tmp) goto out; scull_devices = kmalloc(sizeof(struct scull_dev), GFP_KERNEL); if(!scull_devices){ result = -ENOMEM; goto fail_malloc; } memset(scull_devices, 0, sizeof(struct scull_dev)); scull_devices->quantum = SCULL_QUANTUM; scull_devices->qset = SCULL_QSET; sema_init(&scull_devices->sem, 1); goto out; fail_malloc: unregister_chrdev(scull_major, "scull"); return result; out: return result; } static void __exit scull_cleanup_module(void) { kfree(scull_devices); class_simple_device_remove(MKDEV(scull_major, 0)); class_simple_destroy(scull_class); unregister_chrdev(scull_major, "scull"); devfs_remove("scull"); } module_init(scull_init_module); module_exit(scull_cleanup_module); MODULE_LICENSE("GPL");