400 likes | 411 Views
Learn to load & unload scull modules with a simple character utility script. Download the example at http://examples.oreilly.com/linuxdrive3/ to understand how to create a character device driver efficiently.
E N D
chapter 3-Char Device Driver chenbo2008@ustc.edu.cn 中国科学技术大学软件学院
Example from the text You can download example for the text at http://examples.oreilly.com/linuxdrive3/ 加载 卸载
The script to load the built modules scull_load Remember: make it executable first. Runs it with superuser’s privilege. #!/bin/sh # $Id: scull_load,v 1.4 2004/11/03 06:19:49 rubini Exp $ module="scull" device="scull" mode="664" # Group: since distributions do it differently, look for wheel or use staff if grep -q '^staff:' /etc/group; then group="staff" else group="wheel" fi simple character utility for loading localities
The script to load the built modules # invoke insmod with all arguments we got # and use a pathname, as insmod doesn't look in . by default /sbin/insmod ./$module.ko $* || exit 1 # retrieve major number major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices) # Remove stale nodes and replace them, then give gid and perms # Usually the script is shorter, it's scull that has several devices in it. rm -f /dev/${device}[0-3] mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 ln -sf ${device}0 /dev/${device} chgrp $group /dev/${device}[0-3] chmod $mode /dev/${device}[0-3] rm -f /dev/${device}pipe[0-3] mknod /dev/${device}pipe0 c $major 4 mknod /dev/${device}pipe1 c $major 5 mknod /dev/${device}pipe2 c $major 6 mknod /dev/${device}pipe3 c $major 7 ln -sf ${device}pipe0 /dev/${device}pipe chgrp $group /dev/${device}pipe[0-3] chmod $mode /dev/${device}pipe[0-3] Scull_load
The script to load the built modules rm -f /dev/${device}single mknod /dev/${device}single c $major 8 chgrp $group /dev/${device}single chmod $mode /dev/${device}single rm -f /dev/${device}uid mknod /dev/${device}uid c $major 9 chgrp $group /dev/${device}uid chmod $mode /dev/${device}uid rm -f /dev/${device}wuid mknod /dev/${device}wuid c $major 10 chgrp $group /dev/${device}wuid chmod $mode /dev/${device}wuid rm -f /dev/${device}priv mknod /dev/${device}priv c $major 11 chgrp $group /dev/${device}priv chmod $mode /dev/${device}priv Scull_load
lrwxrwxrwx 1 root root 6 2007-03-27 06:52 scull -> scull0 crw-rw-r-- 1 root wheel 254, 0 2007-03-27 06:52 scull0 crw-rw-r-- 1 root wheel 254, 1 2007-03-27 06:52 scull1 crw-rw-r-- 1 root wheel 254, 2 2007-03-27 06:52 scull2 crw-rw-r-- 1 root wheel 254, 3 2007-03-27 06:52 scull3 lrwxrwxrwx 1 root root 10 2007-03-27 06:52 scullpipe -> scullpipe0 crw-rw-r-- 1 root wheel 254, 4 2007-03-27 06:52 scullpipe0 crw-rw-r-- 1 root wheel 254, 5 2007-03-27 06:52 scullpipe1 crw-rw-r-- 1 root wheel 254, 6 2007-03-27 06:52 scullpipe2 crw-rw-r-- 1 root wheel 254, 7 2007-03-27 06:52 scullpipe3 crw-rw-r-- 1 root wheel 254, 11 2007-03-27 06:52 scullpriv crw-rw-r-- 1 root wheel 254, 8 2007-03-27 06:52 scullsingle crw-rw-r-- 1 root wheel 254, 9 2007-03-27 06:52 sculluid crw-rw-r-- 1 root wheel 254, 10 2007-03-27 06:52 scullwuid The script to load the built modules
The script to load/unload the built modules scull_unload Again, make it executable and run as superuser. #!/bin/sh module="scull" device="scull" # invoke rmmod with all arguments we got /sbin/rmmod $module $* || exit 1 # Remove stale nodes rm -f /dev/${device} /dev/${device}[0-3] rm -f /dev/${device}priv rm -f /dev/${device}pipe /dev/${device}pipe[0-3] rm -f /dev/${device}single rm -f /dev/${device}uid rm -f /dev/${device}wuid
The script to load the built modules • Being a character device, you can read/write character string to it. • #echo “This is a test” > /dev/scull • #cat < /dev/scull • The text use the example scull to illustrate all necessary ingredients in developing character device driver. How to create an character device and hook it up the a device file? Involves two passes of operation • To the kernel, initiates the device and get/announce the major-minor number. • To the user program, creates a file inside the directory /dev.
dev_t Within the kernel, device numbers is of dev_t type(defined in <include/linux/types.h> ). As of Version 2.6.0 of the kernel, dev_t is a 32-bitquantity with 12 bits set aside for the major number and 20 bits for the minor number. A prior to 2.6.x, size of major and minor are both 8bits.
Device Number type dev_t Macros in retrieving device numbers: MAJOR(dev_t dev); MINOR(dev_t dev); Macros in wraping device numbers: MKDEV(int major, int minor); Defined in <linux/kdev_t.h>. <linux/kdev_t.h> 3 #ifdef __KERNEL__ 4 #define MINORBITS 20 5 #define MINORMASK ((1U << MINORBITS) - 1) 6 7 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) 8 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) 9 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
Allocating and Freeing Device Numbers Adding a driver to the system you will have to first register it to the kernel.dev_t is required for registration.How to get the value? Statically and dynamically. Using following functions: Defined as function in: fs/char_dev.c Defined as function prototype in: include/linux/fs.h * fs/char_dev.c */ 1289 extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); 1290 extern int register_chrdev_region(dev_t, unsigned, const char *); 1291 extern int register_chrdev(unsigned int, const char *, 1292 struct file_operations *); 1293 extern int unregister_chrdev(unsigned int, const char *); 1294 extern void unregister_chrdev_region(dev_t, unsigned);
Allocating and Freeing Device Numbers Having a static device number, you can assign the major number during the module’s initialization. using register_chrdev() or register_chrdev_region() • int register_chrdev(unsigned int major, const * char name,operation *fops); • major -The major number for the driver. • name -The name of the driver (as seen in /proc/devices). • fops -The &file_operations structure pointer. • Noticed, no minor number is required. • int register_chrdev_region(dev_t first,unsigned int count, char *name); • first : the beginning device number of the range. • count : the requesting number of contiguous devices. • name : the name of the device (will appear in/proc/devices and sysfs.) • Return 0 if successful. register_chrdev_region(dev_num,2,"my_dev")
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && ( ((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)) )) break; /* Check for overlapping minor ranges. */ if (*cp && (*cp)->major == major) { int old_min = (*cp)->baseminor; int old_max = (*cp)->baseminor + (*cp)->minorct - 1; int new_min = baseminor; int new_max = baseminor + minorct - 1; /* New driver overlaps from the left. */ if (new_max >= old_min && new_max <= old_max) { ret = -EBUSY; goto out; } /* New driver overlaps from the right. */ if (new_min <= old_max && new_min >= old_min) { ret = -EBUSY; goto out; } } cd->next = *cp; *cp = cd; mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); } static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { struct char_device_struct *cd, **cp; int ret = 0; int i; cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd == NULL) return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); if (major == 0) { for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) if (chrdevs[i] == NULL) break; if (i == 0) { ret = -EBUSY; goto out; } major = i; ret = major; } cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strncpy(cd->name,name, 64); i = major_to_index(major);
Allocating and Freeing Device Numbers • Linux kernel dynamically allocates a major number using function: alloc_chrdev_region() alloc_chrdev_region() int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name); dev: return the first number in your allocated range on successful completion. firstminor: the requested first minor number to use; it is usually0. count and name: work like those giventoregister_chrdev_region. int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
Allocating and Freeing Device Numbers • Device numbers are freed by: • unregister_chrdev_region()or • unregister_chrdev(). • Call these functions in your module’s cleanup function. • unregister_chrdev(). • int unregister_chrdev (unsigned int major, const char * name); • major : major number for the driver. • name : name of the driver (as seen in /proc/devices). void unregister_chrdev(unsigned int major, const char *name) { struct char_device_struct *cd; cd = __unregister_chrdev_region(major, 0, 256); if (cd && cd->cdev) cdev_del(cd->cdev); kfree(cd); }
Allocating and Freeing Device Numbers • unregister_chrdev_region(). • int unregister_chrdev_region(dev_t from, unsigned count, const char *name) • from: the requested first minor number to use, it is usually 0. • count and name: work like those given to register_chrdev_region. void unregister_chrdev_region(dev_t from, unsigned count) { dev_t to = from + count; dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); if (next > to) next = to; kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } }
__unregister_chrdev_region static struct char_device_struct * __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct) { struct char_device_struct *cd = NULL, **cp; int i = major_to_index(major); mutex_lock(&chrdevs_lock); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major == major && (*cp)->baseminor == baseminor && (*cp)->minorct == minorct) break; if (*cp) { cd = *cp; *cp = cd->next; } mutex_unlock(&chrdevs_lock); return cd; }
Installation of a driver with dynamic allocated majornumbers • The problem: since it is dynamical-allocated, themajor number assigned to the module is unknownand unable to create the device nodes (files) inadvance. • Using information inside of /proc/devices • However, since once the number has been assigned, it isregistered in the/proc/devices. Consequently, using asimple shell script can retrieving the major number, andcreate the corresponding nodes in /dev. • Take script scull_load for example.
Installation of a driver with dynamic allocated majornumbers
The script to creates nodes in /dev #!/bin/sh module="scull" device="scull" mode="664" # invoke insmod with all arguments we got # and use a pathname, as newer modutils don't look in . by default # this will generate new entries in /proc/devices. The $* is the positional # parameter to pass command line parameters to insmod. /sbin/insmod ./$module.ko $* || exit 1 # remove stale nodes rm -f /dev/${device}[0-3] Installation of a driver with dynamic allocated majornumbers
The script to creates nodes in /dev • # Retrieve major number form /proc/devices using awk • # Noticed that the script form the text has bugs, i.e. it should be \$2 instead of • # \\$2, same to the • # entry \$1. The awk scan /proc/devices for pattern “\$module\”, • # if it find it, the first item of the scanned line is “printed.” • major=$(awk "\$2= =\"$module\" {print \$1}" /proc/devices) • # Use mknod to create corresponding devices in /dev. • # Since a total of 4 scull character “c” devices are created, mknod is invoked 4 times. • mknod /dev/${device}0 c $major 0 • mknod /dev/${device}1 c $major 1 • mknod /dev/${device}2 c $major 2 • mknod /dev/${device}3 c $major 3 Installation of a driver with dynamic allocated majornumbers
The script to creates nodes in /dev • # give appropriate group/permissions, and change the group. • # Not all distributions have staff, some have "wheel" instead. • # It doesn’t have to be staff or whell, it could be anything you want. • group="staff“ • # If “staff” is un-defined, use wheel instead. • grep -q '^staff:' /etc/group || group="wheel“ • # File name expansion of class [0-3] is performed here. • chgrp $group /dev/${device}[0-3] • chmod $mode /dev/${device}[0-3] Installation of a driver with dynamic allocated majornumbers
The scull provides both ways: the major –minornumber can be either – Dynamically allocated orload/compile time assigned In line 615 of main.c int scull_init_module(void) : if (scull_major) { /*value of scull_major is assigned at scull.h to be 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); } The scull do it both ways
By setting the major number in scull.h Compile time In line 615 of main.c int scull_init_module(void) : if (scull_major) { /*value of scull_major is assigned at scull.h to be 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); } The scull do it both ways In scull.h line 47-49 #ifndef SCULL_MAJOR #define SCULL_MAJOR 0 /* dynamic major by default */ #endif
By setting the major number in scull.h Compile time In line 42 of main.c int scull_major = SCULL_MAJOR; : In line 615 of main.c int scull_init_module(void) : if (scull_major) { /*value of scull_major is assigned at scull.h to be 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); } The scull do it both ways In scull.h line 47-49 #ifndef SCULL_MAJOR #define SCULL_MAJOR 0 /* dynamic major by default */ #endif
load time : scull_load scull_major=10 By setting the major number an module parameter in main.c In line 615 of main.c int scull_init_module(void) : if (scull_major) { /*value of scull_major is assigned at scull.h to be 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); } The scull do it both ways In main.c 47-51 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);
Some Important Data Structures Most of the fundamental driver operations involve some important kernel data structures, called file_operations, file, and inode. cdev
File Operations • The file_operations structure sets up operations • that applies to the reserved device numbers. • It is • defined in <linux/fs.h>, • a collection of function pointers. • 关联设备(号)及在其设备上的操作。这些操作主要用来实现系统调用,命名为open、read 等。
Example from the text Each opened file is associated with its ownset of functions (byincluding a field calledf_op that points to a file_operations structure).
File Operations The scull devicedriver implements only the mostimportant device operations. Its file_operations structure is as follows: Not all operations given in file have to be supported. 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, };
The cdev The functions hook the device’s file_operations to the cdev when adding the character device structure. 5 struct cdev { 6 struct kobject kobj; 7 struct module *owner; 8 struct file_operations *ops; 9 struct list_head list; 10 dev_t dev; 11 unsigned int count; 12 };
The initialization of the module struct scull_qset { void **data; struct scull_qset *next; }; 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 */ };