Linux device-driver issues

advertisement
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?
Download