250 likes | 268 Views
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.
E N D
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?