/* * main.c * * Character Device * r/w /dev/helloc0 char device that stores its date using a linked list * * To insert module and call mknod run: * # ./hello_load.sh * * To remove module and cleanup run: * # ./hello_unload.sh * * # cat /dev/helloc0 * # echo -n foobar > /dev/helloc0 * * Notes: * - most notes are handwritten in my book, Device Drivers 3rd * - ssize_t is an int, size_t is unsigned int, be careful * - compile kernel with frame pointers to get better stack readout in case of an oops * - use strace to trace read() and write() syscalls to your driver, take note * of the number of bytes requested to be r/w and what they got back * */ #include #include #include #include #include #include /* used for kmalloc */ #include #include #include /* copy_*_user */ #include #include #include /* major and minor numbers for character device under /dev/ */ int hello_major = HELLO_MAJOR; int hello_minor = 0; char *magicstr = "Tesla coil is a hi freq transformer"; char *hellomsg = "photon is the energy carrier particle for EM waves within light spectrum"; struct dentry *debugfs_dir = NULL; struct dentry *debugfs_file = NULL; /* * Size of a buffer that each node of a linked list holds, this list resides in * hello_device struct */ int hello_node_chunk_sz = HELLO_NODE_CHUNK_SIZE; /* allow arguments to be passed with insmod or modprobe to modfiy globals */ module_param(hello_major, int, S_IRUGO); module_param(hello_minor, int, S_IRUGO); module_param(hello_node_chunk_sz, int, S_IRUGO); MODULE_AUTHOR("Kyle K"); MODULE_LICENSE("GPL"); /* * Be careful with this shared variable, it can be accessed and modified * concurrently, e.g in case multiple reads or writes on /dev/helloc0 coming * from userspace, and if we're not careful it can result in a race condition, * where the thread that was the last to make a modification wins by overwriting * previous state. * * Since to access to this variable will not be made from IRQ handler or other * async context, a sempahore will work well. * * Operations should also be atomic, meaning that the entire operations happens * at once as far as other threads of execution are concerned. So if we had a * ptr that needed memory to be allocated we would like to do that only once. */ struct hello_dev *hello_device; /* * Use proc filesystem only when we are debugging, since it is discouraged in * favor of sys filesystem */ #ifdef HELLO_DEBUG /* * The proc filesystem is there to allow us to tap into driver's internal data * structure within a kernel, in our case it will be struct hello_buffer */ static void hello_create_proc(void) { /* below function returns a proc_dir_entry that we will ignore, it's ok * proc_root in fs/proc/root.c:249 is a static variable that will have a * link to our raed entry */ create_proc_read_entry("hellomem", 0 /* default mode */, NULL /* parent dir */, hello_read_procmem /* function ptr to our read method */, hello_device /* our dev struct that kernel will pass for us that we will access */); } static void hello_remove_proc(void) { remove_proc_entry("hellomem", NULL); } #endif static int hello_create_debugfs(void) { int ret = 0; /* modifies passed in ptrs */ ret = hello_debugfs_init(&debugfs_dir, &debugfs_file); if (ret) printk(KERN_WARNING "[hello] debugfs might not have been mounted\n"); return ret; } static int hello_trim(struct hello_dev *dev) { struct hello_ll *pnode = NULL; struct list_head *pos, *n /* tmp storage */; PDEBUG("trim() called, dev->mylist is at: %p\n", dev->mylist); if (dev->mylist) { list_for_each_safe(pos, n, &dev->mylist->list) { /* computes offset of data member list within struct hello_ll and casts * pos ptr to point lower address by amount of offset */ pnode = list_entry(pos, struct hello_ll, list); list_del(pos); kfree(pnode->chunk); } } dev->ll_size = 0; dev->chunk_sz = hello_node_chunk_sz; dev->mylist = NULL; return 0; } /* * Returns address to the nth node, it also allocates memory for nodes if they * don't exist at such position */ static struct hello_ll *hello_follow(struct hello_dev *dev, int n) { struct hello_ll *nptr = dev->mylist, *tmp; PRDEBUG("follow() called with n: %i\n", n); /* allocate first node explicitly if it needs to be */ if (!nptr) { PDEBUG("allocating first node\n"); nptr = dev->mylist = kmalloc(sizeof(struct hello_ll), GFP_KERNEL); if (!nptr) return NULL; memset(nptr, 0, sizeof(struct hello_ll)); INIT_LIST_HEAD(&nptr->list); /* segfault strcpy(nptr->chunk, "write to me\n"); */ } /* follow the list */ while (n--) { /* almost like testing for NULL, remember this is circular list */ PRDEBUG("while loop: n is: %i, nptr->list is at: %p, ntpr->list.next is at %p\n", n, /* &nptr->list */ &dev->mylist->list, nptr->list.next); /* there was a bug below, took 2 hours to track down */ if (nptr->list.next == /* &nptr->list */ &dev->mylist->list) { PRDEBUG("allocating additional node"); tmp = kmalloc(sizeof(struct hello_ll), GFP_KERNEL); if (!tmp) return NULL; memset(tmp, 0, sizeof(struct hello_ll)); list_add(&tmp->list, &nptr->list); } nptr = list_entry(nptr->list.next, struct hello_ll, list); } return nptr; } /* * Reads at most dev->chunk_sz bytes */ static ssize_t hello_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos) { struct hello_dev *dev = filp->private_data; ssize_t ret = 0; /* end of file, also in case of out of bound */ struct hello_ll *cptr = NULL; /* node where right chunk is */ int chunk_pos = 0; PRDEBUG("read() called\n"); if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->ll_size) goto out; /* out of bound read */ if (*f_pos + count > dev->ll_size) count = dev->ll_size - *f_pos; /* truncate the read */ /* offset to char array of a chunk */ chunk_pos = *f_pos % dev->chunk_sz; /* seek within hello_ll to the right node */ cptr = hello_follow(dev, *f_pos / dev->chunk_sz); if (!cptr || !cptr->chunk) goto out; /* don't read nothingness */ /* * I had a bug here because I didn't modify value of count and all the hell * broke loose since I was reading past the chunk's memory since programs * such as cat by default try to read 65536 bytes. At least count at this * point was at most the amount of dev->ll_size - *f_pos * * We can write to user starting from chunk_pos all the way to the end of * that chunk */ if (count > dev->chunk_sz - chunk_pos) count = dev->chunk_sz - chunk_pos; /* write to userspace memory by reading only up to the end of chunk */ if (copy_to_user(buff, cptr->chunk + chunk_pos, count)) { ret = -EFAULT; goto out; } *f_pos += count; /* yes we have to keep track */ ret = count; out: up(&dev->sem); return ret; } static ssize_t hello_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos) { struct hello_dev *dev = filp->private_data; ssize_t ret = -ENOMEM; struct hello_ll *cptr = NULL; /* node where right chunk is */ int chunk_pos = 0; PRDEBUG("write() called, user buffer is at: %p, f_pos: %lli, c: %lu\n", buff, *f_pos, count); if (down_interruptible(&dev->sem)) return -ERESTARTSYS; chunk_pos = *f_pos % dev->chunk_sz; cptr = hello_follow(dev, *f_pos / dev->chunk_sz); if (!cptr) goto out; if (!cptr->chunk) { cptr->chunk = kmalloc(dev->chunk_sz * sizeof(char *), GFP_KERNEL); if (!cptr->chunk) goto out; memset(cptr->chunk, 0, dev->chunk_sz * sizeof(char *)); } if (count > dev->chunk_sz - chunk_pos) count = dev->chunk_sz - chunk_pos; if (copy_from_user(cptr->chunk + chunk_pos, buff, count)) { ret = -EFAULT; goto out; } *f_pos += count; ret = count; /* update the size */ if (dev->ll_size < *f_pos) dev->ll_size = *f_pos; out: up(&dev->sem); return ret; } /* * The ioctl() implementation */ long hello_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; 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) != HELLO_IOCTL_BASE) return -ENOTTY; if (_IOC_NR(cmd) > HELLO_IOCTL_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)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *) arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; switch (cmd) { case HELLO_IOCTL_RESET: hello_node_chunk_sz = HELLO_NODE_CHUNK_SIZE; break; case HELLO_IOCTL_SCHUNK: if (!capable (CAP_SYS_ADMIN)) return -EPERM; retval = __get_user(hello_node_chunk_sz, (int __user *) arg); break; case HELLO_IOCTL_TCHUNK: if (!capable (CAP_SYS_ADMIN)) return -EPERM; hello_node_chunk_sz = arg; break; case HELLO_IOCTL_GCHUNK: if (!capable (CAP_SYS_ADMIN)) return -EPERM; retval = __put_user(hello_node_chunk_sz, (int __user *) arg); break; case HELLO_IOCTL_QCHUNK: return hello_node_chunk_sz; default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY; } return retval; } /* * There's a count in filp that gets incremented every time our char device gets * opened, but note that this function does not get called multiple times, * instead copies are made, think fork(), or just simple ptr? */ static int hello_open(struct inode *inode, struct file *filp) { struct hello_dev *dev; PDEBUG("open() called\n"); dev = container_of(inode->i_cdev, struct hello_dev, cdev); filp->private_data = dev; /* our only chance to do that is here in open(), place to save our dev struct */ /* 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; PDEBUG("write-only open, about to call trim()"); hello_trim(dev); /* ignore errors */ up(&dev->sem); } PDEBUG("open() left\n"); return 0; } static int hello_release(struct inode *inode, struct file *filp) { PDEBUG("release() called\n"); return 0; } struct file_operations hello_fops = { .owner = THIS_MODULE, .read = hello_read, .write = hello_write, .unlocked_ioctl = hello_ioctl, .open = hello_open, .release = hello_release }; /* * __init is a hint that the func is only used at initialization time, then module * loader drops the function after module is loaded making its memory available for other uses */ static int __init hello_init(void) { int ret = 0; dev_t devnum = 0; char *hellomsg_copy = NULL; printk("[hello] module inserted\n"); /* little hello msg below */ hellomsg_copy = (char *) kmalloc_array(128, sizeof(char), GFP_KERNEL /* kernel ram */); strcpy(hellomsg_copy, hellomsg); printk(KERN_INFO "%s\n", hellomsg_copy); kfree(hellomsg_copy); /* device numbers, major will be picked for us */ if (hello_major) { devnum = MKDEV(hello_major, hello_minor); ret = register_chrdev_region(devnum, 1, "helloc"); /* last param name will be reflected in /proc/devices */ } else { ret = alloc_chrdev_region(&devnum, 0 /* first minor */, 1, "helloc"); hello_major = MAJOR(devnum); hello_minor = MINOR(devnum); PDEBUG("[hello] major: %d, minor: %d\n", hello_major, hello_minor); } if (ret < 0) { printk(KERN_WARNING "[hello] can't get the major %d number\n", hello_major); return ret; } /* allocate hello device and set it up */ hello_device = kmalloc(sizeof(struct hello_dev), GFP_KERNEL); if (!hello_device) { ret = -ENOMEM; goto fail; } memset(hello_device, 0, sizeof(struct hello_dev)); hello_device->devnum = devnum; sema_init(&hello_device->sem, 1); hello_device->chunk_sz = hello_node_chunk_sz; /* setup char_dev structure */ cdev_init(&hello_device->cdev, &hello_fops); hello_device->cdev.owner = THIS_MODULE; if ((ret = cdev_add(&hello_device->cdev, devnum, 1)) < 0) { printk(KERN_WARNING "[hello] failed to add char device\n"); goto fail; } hello_create_debugfs(); #ifdef HELLO_DEBUG hello_create_proc(); #endif return 0; fail: unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1); return ret; } static void __exit hello_exit(void) { if (hello_device) { hello_trim(hello_device); kfree(hello_device); } unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1); hello_debugfs_destroy(debugfs_dir, debugfs_file); #ifdef HELLO_DEBUG hello_remove_proc(); #endif printk(KERN_INFO "[hello] module removed\n"); } module_init(hello_init); module_exit(hello_exit);