Back to Blog

Detailed Comments on the scull Module in Chapter 3 of LDD3 for Linux Driver Development

#Linux#Struct#Module#REST#NULL#Semaphore
#include <linux/module.h>   #include <linux/moduleparam.h>   #include <linux/init.h>   #include <linux/kernel.h> /* printk() */   #include <linux/slab.h>       /* kmalloc() */   #include <linux/fs.h>     /* everything... */   #include <linux/errno.h>  /* error codes */   #include <linux/types.h>  /* size_t */   #include <linux/fcntl.h>  /* O_ACCMODE */   #include <linux/cdev.h>   #include <asm/system.h>       /* cli(), *_flags */   #include <asm/uaccess.h>  /* copy_*_user */   #include "scull.h"      /* local definitions */    /*    * Our parameters which can be set at load time.    */   // Major device number    int scull_major =   SCULL_MAJOR;    // Minor device number    int scull_minor =   0;    // Number of consecutive device numbers requested    int scull_nr_devs = SCULL_NR_DEVS;  /* number of bare scull devices */   // Quantum size    int scull_quantum = SCULL_QUANTUM;    // Quantum set size    int scull_qset =    SCULL_QSET;    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);    struct scull_dev *scull_devices;    /* allocated in scull_init_module */   /*    * Empty out the scull device; must be called with the device    * semaphore held.    */   /*    * Release the entire data area by simply traversing the list and freeing any quanta and quantum sets found.    * Called by scull_open when the file is opened for writing.    * The semaphore must be held when calling this function.    */   int scull_trim(struct scull_dev *dev)    {        struct scull_qset *next, *dptr;        // Quantum set size        int qset = dev->qset;   /* "dev" is not-null */       int i;        for (dptr = dev->data; dptr; dptr = next) { /* all the list items */           if (dptr->data) {// There is data in the quantum set                // Traverse and free each quantum in the current quantum set; the quantum set size is qset                for (i = 0; i < qset; i++)                    kfree(dptr->data[i]);                // Free the quantum array pointer                kfree(dptr->data);                dptr->data = NULL;            }            // Get the next quantum set and free the current one            next = dptr->next;            kfree(dptr);        }        // Reset variable values in struct scull_dev dev        dev->size = 0;        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);        filp->private_data = dev; /* for other methods */       /* now trim to 0 the length of the device if open was write-only */       // Truncate to 0 when file is opened in write-only mode            if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {            if (down_interruptible(&dev->sem))                return -ERESTARTSYS;            scull_trim(dev); /* ignore errors */           up(&dev->sem);        }        return 0;          /* success */   }    int scull_release(struct inode *inode, struct file *filp)    {        return 0;    }    /*    * Follow the list    */   // Return a pointer to the n-th quantum set of device dev; allocate new ones if fewer than n exist    struct scull_qset *scull_follow(struct scull_dev *dev, int n)    {        // Pointer to the first quantum set        struct scull_qset *qs = dev->data;        /* Allocate first qset explicitly if need be */       // If the current device has no quantum set, explicitly allocate the first one        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 */       // Traverse the quantum set list of the current device n steps; allocate new ones if insufficient        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, // File structure corresponding to the device                         char __user *buf,  // Buffer to read into user space                        size_t count,      // Number of bytes                        loff_t *f_pos)     // Read position, offset within private data of filp    {        struct scull_dev *dev = filp->private_data;         struct scull_qset *dptr;    /* the first listitem */       // Quantum and quantum set size        int quantum = dev->quantum, qset = dev->qset;        // Number of bytes in a quantum set        int itemsize = quantum * qset; /* how many bytes in the listitem */       int item, s_pos, q_pos, rest;        ssize_t retval = 0;        if (down_interruptible(&dev->sem))            return -ERESTARTSYS;        // Read position exceeds total data size        if (*f_pos >= dev->size)            goto out;        // If count exceeds available data, truncate count        if (*f_pos + count > dev->size)            count = dev->size - *f_pos;        /* find listitem, qset index, and offset in the quantum */       // Locate read/write position within quantum/quantum set: which quantum set, which quantum within it, and offset within the quantum        // Which quantum set        item = (long)*f_pos / itemsize;        // Offset within the quantum set        rest = (long)*f_pos % itemsize;        // Which quantum, and offset within it        s_pos = rest / quantum; q_pos = rest % quantum;        /* follow the list up to the right position (defined elsewhere) */       // Get pointer to the quantum set to be read        dptr = scull_follow(dev, item);        // Error handling for failed read        if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])            goto out; /* don't fill holes */       /* read only up to the end of this quantum */       // Only read within one quantum: if count exceeds current quantum, truncate count        if (count > quantum - q_pos)            count = quantum - q_pos;        // Copy count bytes from the target location to user space buffer buf        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;        // Quantum and quantum set size        int quantum = dev->quantum, qset = dev->qset;        // Total bytes in a quantum set        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 */       // Which quantum set        item = (long)*f_pos / itemsize;        // Offset within that quantum set        rest = (long)*f_pos % itemsize;        // Which quantum within the set, and offset within the quantum        s_pos = rest / quantum; q_pos = rest % quantum;        /* follow the list up to the right position */       // Get pointer to the quantum set        dptr = scull_follow(dev, item);        if (dptr == NULL)            goto out;        // If data in this quantum set is NULL, allocate new memory        if (!dptr->data) {            dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);            if (!dptr->data)                goto out;            memset(dptr->data, 0, qset * sizeof(char *));        }        // If the s_pos-th quantum is NULL, allocate new memory        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 */       // Only write within one quantum; if count exceeds current quantum, truncate        if (count > quantum - q_pos)            count = quantum - q_pos;        // Copy data from user space to kernel space; returns number of bytes not copied on failure, 0 on success        if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {            retval = -EFAULT;            goto out;        }        *f_pos += count;        retval = count;        /* update the size */       // Update total byte count        if (dev->size < *f_pos)            dev->size = *f_pos;      out:        up(&dev->sem);        return retval;    }       struct file_operations scull_fops = {        .owner =    THIS_MODULE,        .read =     scull_read,        .write =    scull_write,        .open =     scull_open,        .release =  scull_release,    };    /*    * Finally, the module stuff    */   /*    * The cleanup function is used to handle initialization failures as well.    * Therefore, it must be careful to work correctly even if some of the items    * have not been initialized    */   void scull_cleanup_module(void)    {        int i;        // Combine major and minor numbers into a dev_t structure, i.e., device number        dev_t devno = MKDEV(scull_major, scull_minor);        /* Get rid of our char dev entries */       if (scull_devices) {            // Iterate and free data area for each device            for (i = 0; i < scull_nr_devs; i++) {                // Free data area                scull_trim(scull_devices + i);                // Remove cdev                cdev_del(&scull_devices[i].cdev);            }            // Free scull_devices itself            kfree(scull_devices);        }        /* cleanup_module is never called if registering failed */       unregister_chrdev_region(devno, scull_nr_devs);    }       /*    * Set up the char_dev structure for this device.    */   // Set up char_dev structure    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;        // Add character device dev->cdev, takes effect immediately        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.    */       // Request device numbers; dynamically allocate if no major number specified at load time        if (scull_major) {            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;        }            /*         * allocate the devices -- we can't have them static, as the number        * can be specified at load time        */       // Allocate memory for scull_dev objects        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));            /* Initialize each device. */       for (i = 0; i < scull_nr_devs; i++) {            scull_devices[i].quantum = scull_quantum;            scull_devices[i].qset = scull_qset;            // Initialize mutex, set semaphore sem to 1            sema_init(&scull_devices[i].sem, 1);                //init_MUTEX(&scull_devices[i].sem);            // Set up char_dev structure            scull_setup_cdev(&scull_devices[i], i);            }        return 0; /* succeed */     fail:        scull_cleanup_module();        return result;    }    module_init(scull_init_module);    module_exit(scull_cleanup_module);    MODULE_AUTHOR("Tekkamanninja");    MODULE_LICENSE("Dual BSD/GPL");    This article originally comes from Linux Community (www.linuxidc.com)  Original link: http://www.linuxidc.com/Linux/2011-06/37819.htm