linux device-driver issues
DESCRIPTION
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. - PowerPoint PPT PresentationTRANSCRIPT
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’) tellskernel 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 havingmajor-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?