Linux device-driver issues Devices as ‘special’ files • • • • • Unix programs treat most devices as files Provides a familiar programming interface Standard C functions: open(), read(), etc But such devices need to have ‘filenames’ Device files are located in ‘/dev’ directory Example: our ‘led’ device • • • • • • We created a ‘char’ device-driver: ‘led.c’ It operated a standard keyboard’s LEDs It was a ‘write-only’ character device Applications saw it as a file: ‘/dev/led’ We tested it using: $ echo 7 > /dev/led That command ‘turned on’ all three LEDs Example C++ application int main( void ) { int fd = open( “/dev/led”, O_WRONLY ); if ( fd < 0 ) { perror( “open” ); exit(1); } char indicator = 7; write( fd, &indicator, 1 ); close( fd ); } Kernel uses different ID-scheme • • • • • • Kernel uses number-pairs (major,minor) The ‘major’ number identifies the driver The ‘minor’ number identifies the device One driver can control multiple devices Range for ‘major’ numbers is 0..255 Certain of these values are ‘reserved’ Assigning ‘major’ numbers • • • • • Driver-author can select a major number Kernel is told during driver ‘registration’ But author must be careful: no duplication! Registration fails if number already used View currently used major numbers with $ cat /proc/devices ‘Dynamic’ module loading • Linux lets module be loaded ‘on demand’ • This could cause ‘contention’ for numbers • • • • Example: your driver uses major=6 But line-printer driver (‘lp.c’) uses major=6 During printing your module won’t install And printing fails if your module is installed ‘Official’ device-numbers • There is a ‘registry’ of device-numbers • See file ‘devices.txt’ in kernel sources • Look in: /usr/src/linux/Documentation • Maintaining this registry is a ‘big hassle’ (e.g., delays, arguments, too few numbers) • So some alternative solution was needed Dynamic assignment • Module author can let kernel choose major • This is why major-number 0 is never used • If programmer requests major-number 0, kernel assigns an available major-number • Kernel informs driver during ‘registration’ Driver registration • int register_chrdev( unsigned int major, const char *driver_name, struct file_operations *fops ); • Returns: major-number (or error-code) • Using 0 as first argument (‘major’) tells kernel to pick an unused major-number ‘Chicken-and-Egg’ problem? • A driver’s device-file(s) must be created • Creator must know device major-number • (Also creator will need ‘root’ privileges!) • Example: root# mknod /dev/led c 15 0 • Creates a character device-node having major-number=15 and minor-number=0 Obstacles for us • How to we find out what major-number the kernel dynamically assigned to our driver? • How can we create special files in ‘/dev’ that allow applications to use our driver? • How to we set the ‘file permissions’ so a normal program can open, read/write to our devices? Overcoming those obstacles • • • • • • • Our driver will know its major-number ‘init_module()’ will ‘register’ our driver Return-value will be the major-number We could use ‘printk()’ to display its value Then a user could create the device-file BUT: will the user be allowed to do it? ‘mknod’ and ‘chmod’ need root privileges One convenient solution • Let our module setup its own device-file(s) • Our module will know the major-number and our module has ‘root’ privileges BUT • Can modules execute ‘mknod’? ‘chmod’? Kernel System Calls • • • • • • • Kernel function is named ‘sys_mknod’ In kernel 2.4.20 this ‘symbol’ isn’t exported Module loader can’t link our module to it Which kernel symbols ARE exported? Use: $ cat /proc/ksyms Ugh! Hundreds of exported kernel symbols Better: $ grep sys_mknod /proc/ksyms ‘sys_call_table’ is exported • • • • • • Try: $ cat sys_call_table /proc/ksyms We CAN link our with ‘sys_call_table’ Declare: extern void *sys_call_table[]; I.e., ‘sys_call_table’ is an array of pointers A pointer to ‘sys_mknod’ is in this array! But where? Header-file: ‘asm/unistd.h’ • Kernel-header defines symbolic constants • Examples: #define __NR_mknod 14 #define __NR_chmod 15 • These are indexes into ‘sys_call_table’ • So function-pointers can be ‘looked up’ Programming Syntax • Declare static function-pointer variables: static int (*sys_mknod)( const char *, … ); static int (*sys_chmod)( const char *, … ); • Initialize these function-pointer variables: sys_mknod = sys_call_table[ __NR_mknod]; sys_chmod = sys_call_table[ __NR_chmod]; One further ‘gotcha’ • • • • • • System-call expect user-space arguments E.g., filename is a string from user-space Kernel will check for an “illegal’ argument A system-call from kernel-space will fail! PAGE_OFFSET is origin of kernel-space Normally PAGE_OFFSET is 0xC0000000 Raising the ‘user-space’ roof • • • • • • • Top of user-space is a task-variable Each task has its own local copy Kept in the ‘struct task_struct’ structure Assigned during task-creation (e.g., fork() ) Kernel can change this variable’s value! Syntax: set_fs( get_ds() ); Needs header: #include <asm/uaccess.h> ‘init_module’ algorithm char nm = “led”; struct file_operations fops = { write: write, }; int major = register_chrdev(0, nm, &fops ); Dev_t dev_id = MKDEV( major, minor ); sys_mknod = sys_call_table[ __NR_mknod]; set_fs( get_ds() ); sys_mknod( “/dev/led”, S_IFCHR, dev_id ); How to remove a device-file • • • • Another ‘privileged’ command Example: root# unlink /dev/led We can let our ‘cleanup_module()’ do it But ‘cleanup’ and ‘init’ are different tasks: root# /sbin/insmod led.o root# /sbin/rmmod led • ‘insmod’ will call our init_module() • ‘rmmod’ will call our cleanup_module() Algorithm for ‘cleanup’ const char modname[] = “led”; unregister_chrdev( major, modname ); sys_unlink = sys_call_table[ __NR_unlink ]; set_fs( get_ds() ); const char devname[] = “/dev/led”; sys_unlink( devname ); ‘pseudo-code’ versus C • Previous slides showed algorithm-steps • BUT C language has special requirement • Within each C program-block: all of block’s local variables are declared (and, optionally, initialized) BEFORE any executable-statements appear • This differs from C++ (which is less strict) Now: an in-class exercise • • • • • • • See online version of our ‘stash.c’ driver Accessible on our class webpage http://nexus.cs.usfca.edu/~cruse/cs635/ It was written and tested for kernel 2.4.18 That kernel exported system-call functions ‘sys_call_table[]’ lookups weren’t needed Can you modify ‘stash.c’ for 2.4.20?