chapter 3-Char Device Driver chenbo2008@ustc.edu.cn 中国科学技术大学软件学院
ls -l /dev
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 shell编程是以"#"为注释,但对"#!/bin/sh"却不是。"#!/bin/sh"是对shell的声明,说明你所用的是那种类型的shell及其路径所在。如果没有声明,则脚本将在默认的shell中执行,默认shell是由用户所在的系统定义为执行shell脚本的shell.如果脚本被编写为在Kornshell ksh中运行,而默认运行shell脚本的为C shell csh,则脚本在执行过程中很可能失败。
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 /sbin/insmod ./$module.ko $* || exit 1 ?
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
The script to load the built modules 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/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-bit quantity 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) { cd->next = *cp; *cp = cd; mutex_unlock(&chrdevs_lock); return cd; out: 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 usually 0. count and name: work like those given to register_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. 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, the major number assigned to the module is unknown and unable to create the device nodes (files) in advance. Using information inside of /proc/devices However, since once the number has been assigned, it is registered in the /proc/devices. Consequently, using a simple shell script can retrieving the major number, and create the corresponding nodes in /dev. Take script scull_load for example.
Installation of a driver with dynamic allocated majornumbers
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]
The scull do it both ways The scull provides both ways: the major –minor number can be either – Dynamically allocated or load/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); } In scull.h line 47-49 #ifndef SCULL_MAJOR #define SCULL_MAJOR 0 /* dynamic major by default */ #endif
The scull do it both ways 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); } In scull.h line 47-49 #ifndef SCULL_MAJOR #define SCULL_MAJOR 0 /* dynamic major by default */ #endif
The scull do it both ways 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); } 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 own set of functions (by including a field called f_op that points to a file_operations structure).
File Operations
File Operations The scull device driver implements only the most important 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, };
Difference between structure definition
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 */
The initialization of the module
thank you !