Dr A Sahu Dept of Comp Sc & Engg. IIT Guwahati
Writing/Registering to /proc Character Device Driver – Characteristics and functionality – Basic IO functions – Examples (ADC, DAC, Printer, Test data generator) Create 30S Delay SR using CMOS Data – Real Time CMOS Clock – 30S Delay
application standard “runtime” libraries standard “runtime” libraries call ret user spacekernel space Operating System kernel Operating System kernel syscall sysret module Linux allows us to write our own installable kernel modules and add them to a running system call ret
We can write code to implement our own ‘pseudo’ files, located in ‘/proc’ directory We do this by adding a ‘payload’ function to a Linux Kernel Module, and by including calls to special kernel-functions within our module- init and our module-exit routines These special kernel-functions serve to ‘register’, and ‘unregister’, our payload
Your module-initialization function should ‘register’ the module’s ‘get_info()’ function: create_proc_info_entry( modname, 0, NULL, get_info ); Your cleanup should do an ‘unregister’: remove_proc_entry( modname, NULL ); the name for your proc file the file-access attributes (0=default) directory where file will reside (NULL=default) function-pointer to your module’s ‘callback’ routine file’s name directory
A device driver is a kernel module responsible for managing low-level I/O operations for a particular hardware device. VFS Block drivers: Physically addressable media (disks) All other devices are considered character devices Line printer, ADC, DAC, …
System Call Interface VFS File System Buffer Cache Buffer Cache Block Device Driver Block Device Driver Character Device Driver Character Device Driver Network Device Driver Network Device Driver Network Protocol Network Protocol Socket Hardware
All drivers are required to implement the loadable module entry points – init () // (load) – finalize () //unload – info () // Gather information of device Drivers should allocate and initialize any global resources in init() and release their resources in finalize().
Device Properties – can’t be randomly accessed – can’t be buffered – usually are slow devices Export Interface – file_operations Data Flow in read/write
init exit fops function... Device-driver LKM layout registers the ‘fops’ unregisters the ‘fops’ module’s ‘payload’ is a collection of callback-functions having prescribed prototypes AND a ‘package’ of function-pointers the usual pair of module-administration functions
Character device drivers normally perform I/O in a byte stream. Examples of devices using character drivers include tape drives and serial ports. Character device drivers can also provide additional interfaces not present in block drivers, – I/O control (ioctl) commands – memory mapping – device polling.
FunctionMeanings Lseekto change the current read/write position in a file Readto retrieve data from the device WriteSends data to the devic ReaddirNULL for device files; reading dirs & only useful to FS. Pollback end of two system calls, poll and select, used to inquire a device is readable or writable or in some special state Ioctlto issue device-specific commands Mmapto request a mapping of device mem to a process's address space Openfirst operation performed on the device file Flush.. Lock.. Release.. Fsync.. Fasync..
Block drivers are required to support strategy, while character drivers can choose to implement whatever mix of – read, write, ioctl, mmap, or devmap – These entry points as appropriate for the type of device. Character drivers can also support a polling interface through – ch_poll – as well as asynchronous I/O through aread and awrite.
To appreciate the considerations that have motivated the over-all Linux driver’s design requires an understanding of how normal application-programs get their access to services that the operating system offers This access is indirect – through specially protected interfaces (i.e., system calls) – usually implemented as ‘library’ functions
int open( char *pathname, int flags, … ); int read( int fd, void *buf, size_t count ); int write( int fd, void *buf, size_t count ); int lseek( int fd, loff_t offset, int whence ); int close( int fd ); (and other less-often-used file-I/O functions)
UNIX systems treat hardware-devices as special files, so that familiar functions can be used by application programmers to access devices (e.g., open, read, close) But a System Administrator has to create these device-files (in the ‘/dev’ directory) Or alternatively (as we’ve seen), an LKM could create these necessary device-files # mknod /dev/cmos c 70 0
Breaks link between file and file-descriptor Returns 0 on success, or -1 if an error #include int close( int fd ); #include int close( int fd );
Attempts to write up to ‘count’ bytes Bytes are taken from ‘buf’ memory-buffer Returns the number of bytes written Or returns -1 if some error occurred Return-value 0 means no data was written #include int write( int fd, void *buf, size_t count); #include int write( int fd, void *buf, size_t count);
Attempts to read up to ‘count’ bytes Bytes are placed in ‘buf’ memory-buffer Returns the number of bytes read Or returns -1 if some error occurred Return-value 0 means ‘end-of-file’ #include int read( int fd, void *buf, size_t count ); #include int read( int fd, void *buf, size_t count );
These functions have (as a “side-effect”) the advancement of a file-pointer variable They return a negative function-value of -1 if an error occurs, indicating that no actual data could be transferred; otherwise, they return the number of bytes read or written The ‘read()’ function normally does not return 0, unless ‘end-of-file’ is reached
Modifies the file-pointer variable, based on the value of whence Returns the new value of the file-pointer (or returns -1 if any error occurred) #include off_t lseek(int fd, off_t offset, int whence ); enum { SEEK_SET, SEEK_CUR, SEEK_END }; #include off_t lseek(int fd, off_t offset, int whence ); enum { SEEK_SET, SEEK_CUR, SEEK_END };
For normal files, your application can find out how many bytes belong to a file using the ‘lseek()’ function: int filesize=lseek(fd,0,SEEK_END); But afterward you need to ‘rewind’ the file if you want to read its data: lseek( fd, 0, SEEK_SET );
LKM implements a simple character-mode device-driver For RT Clock's non-volatile memory(128 bytes). It should fully supports read() and lseek() access, Its support restricted access to – write(), only clock-and-calendar locations – The non-configuration data. root# mknod /dev/cmos c 70 0
We implement three callback functions: – llseek:// sets file-pointer’s position – read:// inputs a byte from CMOS – write:// outputs a byte to CMOS We omit other callback functions, such as: – open:// we leave this function-pointer NULL – release:// we leave this function-pointer NULL The kernel has its own ‘default’ implementation for any function with NULL as its pointer-value
init exit fops function... Device-driver LKM layout registers the ‘fops’ unregisters the ‘fops’ module’s ‘payload’ is a collection of callback-functions having prescribed prototypes AND a ‘package’ of function-pointers the usual pair of module-administration functions
The GNU C-compiler supports a syntax for ‘struct’ field-initializations that lets you give your field-values in any convenient order: struct file_operations my_fops = { llseek:my_llseek, write:my_write, read:my_read, }; struct file_operations my_fops = { llseek:my_llseek, write:my_write, read:my_read, };
#include // for printk() #include // for register_chrdev() #include // for put_user(), get_user() #include // for inb(), outb() char modname[] = "cmosram";// name of this kernel module char devname[] = "cmos";// name for the device's file intmy_major = 70;// major ID-number for driver intcmos_size = 128;// total bytes of cmos memory intwrite_max = 9;// largest 'writable' address #include // for printk() #include // for register_chrdev() #include // for put_user(), get_user() #include // for inb(), outb() char modname[] = "cmosram";// name of this kernel module char devname[] = "cmos";// name for the device's file intmy_major = 70;// major ID-number for driver intcmos_size = 128;// total bytes of cmos memory intwrite_max = 9;// largest 'writable' address
ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos ) { unsigned char datum; if ( *pos >= cmos_size ) return 0; outb( *pos, 0x70 ); datum = inb( 0x71 ); if ( put_user( datum, buf ) ) return –EFAULT; *pos += 1; return 1; } ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos ) { unsigned chardatum; if ( *pos >= cmos_size ) return 0; if ( *pos > write_max ) return –EPERM; if ( get_user( datum, buf ) ) return –EFAULT; outb( *pos, 0x70 ); outb( datum, 0x71 ); *pos += 1; return 1; } ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos ) { unsigned char datum; if ( *pos >= cmos_size ) return 0; outb( *pos, 0x70 ); datum = inb( 0x71 ); if ( put_user( datum, buf ) ) return –EFAULT; *pos += 1; return 1; } ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos ) { unsigned chardatum; if ( *pos >= cmos_size ) return 0; if ( *pos > write_max ) return –EPERM; if ( get_user( datum, buf ) ) return –EFAULT; outb( *pos, 0x70 ); outb( datum, 0x71 ); *pos += 1; return 1; }
loff_t my_llseek( struct file *file, loff_t pos, int whence ) { loff_tnewpos = -1; switch ( whence ) { case 0:newpos = pos; break;// SEEK_SET case 1: newpos = file->f_pos + pos; break;// SEEK_CUR case 2: newpos = cmos_size + pos; break;// SEEK_END } if (( newpos cmos_size )) return –EINVAL; file->f_pos = newpos; returnnewpos; } loff_t my_llseek( struct file *file, loff_t pos, int whence ) { loff_tnewpos = -1; switch ( whence ) { case 0:newpos = pos; break;// SEEK_SET case 1: newpos = file->f_pos + pos; break;// SEEK_CUR case 2: newpos = cmos_size + pos; break;// SEEK_END } if (( newpos cmos_size )) return –EINVAL; file->f_pos = newpos; returnnewpos; }
struct file_operations my_fops = { owner:THIS_MODULE, llseek:my_llseek, write:my_write, read:my_read, }; static int __init my_init( void ) { printk( " \nInstalling \'%s\' module ", devname ); printk( "(major=%d) \n", my_major ); returnregister_chrdev( my_major, devname, &my_fops ); } static void __exit my_exit(void ) { unregister_chrdev( my_major, devname ); printk( " Removing \'%s\' module\n", devname ); } module_init( my_init ); module_exit( my_exit ); MODULE_LICENSE("GPL"); struct file_operations my_fops = { owner:THIS_MODULE, llseek:my_llseek, write:my_write, read:my_read, }; static int __init my_init( void ) { printk( " \nInstalling \'%s\' module ", devname ); printk( "(major=%d) \n", my_major ); returnregister_chrdev( my_major, devname, &my_fops ); } static void __exit my_exit(void ) { unregister_chrdev( my_major, devname ); printk( " Removing \'%s\' module\n", devname ); } module_init( my_init ); module_exit( my_exit ); MODULE_LICENSE("GPL");
#include // for printf(), perror() #include // for open() #include // for read() int main( int argc, char **argv ) { int status = 0; int fd = open( "/dev/cmos", O_RDONLY ); if ( fd < 0 ) { perror( "/dev/cmos" ); return -1; } // Repeatedly reads Status_Reg until its bit has 'flipped‘ 30 times for (int i = 0; i < 30; i++) { do { // do busy-wait until UpdateInProgress is 'true' lseek( fd, 10, SEEK_SET ); read( fd, &status, 1 ); } while ( ( status & 0x80 ) == 0x00 ); do{ // do busy-wait until UpdateInProgress is 'false’ lseek( fd, 10, SEEK_SET ); read( fd, &status, 1 ); } while ( ( status & 0x80 ) == 0x80 ); printf( " %d Second Elapsed\n", i+1 ); } #include // for printf(), perror() #include // for open() #include // for read() int main( int argc, char **argv ) { int status = 0; int fd = open( "/dev/cmos", O_RDONLY ); if ( fd < 0 ) { perror( "/dev/cmos" ); return -1; } // Repeatedly reads Status_Reg until its bit has 'flipped‘ 30 times for (int i = 0; i < 30; i++) { do { // do busy-wait until UpdateInProgress is 'true' lseek( fd, 10, SEEK_SET ); read( fd, &status, 1 ); } while ( ( status & 0x80 ) == 0x00 ); do{ // do busy-wait until UpdateInProgress is 'false’ lseek( fd, 10, SEEK_SET ); read( fd, &status, 1 ); } while ( ( status & 0x80 ) == 0x80 ); printf( " %d Second Elapsed\n", i+1 ); }
Download mmake.cpp and cmosram.c Course Website, tested on Fedora 12 Compile mmake.cpp using ‘g++’ Then compile cmosram.c using ‘make’ Install ‘cmos.ko’ (and see printk-message) See $cat /proc/cmos Compile ThirtySec.cpp and execute..