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 Linux allows us to write our own installable kernel modules and add them to a running system application module call call ret standard “runtime” libraries user space syscall ret Operating System kernel sysret kernel space • 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 moduleinit 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 ); 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 • Your cleanup should do an ‘unregister’: remove_proc_entry( modname, NULL ); 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 Socket File System Network Protocol Buffer Cache Block Character Device Driver Device Driver Hardware Network Device Driver • All drivers are required to implement the loadable module entry points – init () – finalize () – info () // (load) //unload // 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 Device-driver LKM layout module’s ‘payload’ is a collection of callback-functions having prescribed prototypes function function function fops ... the usual pair of module-administration functions AND a ‘package’ of function-pointers init registers the ‘fops’ exit unregisters the ‘fops’ • 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. Function Meanings Lseek to change the current read/write position in a file Read to retrieve data from the device Write Sends data to the devic Readdir NULL for device files; reading dirs & only useful to FS. Poll back end of two system calls, poll and select, used to inquire a device is readable or writable or in some special state Ioctl to issue device-specific commands Mmap to request a mapping of device mem to a process's address space Open first 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) # mknod /dev/cmos c 70 0 • Or alternatively (as we’ve seen), an LKM could create these necessary device-files #include <unistd.h> int close( int fd ); • Breaks link between file and file-descriptor • Returns 0 on success, or -1 if an error #include <unistd.h> int write( int fd, void *buf, size_t count); • • • • • 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 <unistd.h> int read( 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’ • 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 #include <unistd.h> off_t lseek(int fd, off_t offset, int whence ); enum { SEEK_SET, SEEK_CUR, SEEK_END }; • 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) • 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: – read: – write: // sets file-pointer’s position // inputs a byte from CMOS // outputs a byte to CMOS • We omit other callback functions, such as: – open: – release: // we leave this function-pointer NULL // we leave this function-pointer NULL • The kernel has its own ‘default’ implementation for any function with NULL as its pointer-value Device-driver LKM layout module’s ‘payload’ is a collection of callback-functions having prescribed prototypes function function function fops ... the usual pair of module-administration functions AND a ‘package’ of function-pointers init registers the ‘fops’ exit unregisters the ‘fops’ • 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: write: read: }; ={ my_llseek, my_write, my_read, #include <linux/module.h> #include <linux/fs.h> register_chrdev() #include <asm/uaccess.h> get_user() #include <asm/io.h> // for printk() // for // for put_user(), // for inb(), outb() char modname[] = "cmosram"; // module char devname[] = "cmos"; // device's file int my_major = 70; // driver int cmos_size = 128; // total memory name of this kernel name for the major ID-number for bytes of cmos 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 char datum; 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_t newpos = -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 < 0 )||( newpos > cmos_size )) return –EINVAL; file->f_pos = newpos; return newpos; struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, write: my_write, read: my_read, }; static int __init my_init( void ) { printk( "<1>\nInstalling \'%s\' module ", devname ); printk( "(major=%d) \n", my_major ); return register_chrdev( my_major, devname, &my_fops ); } static void __exit my_exit(void ) { unregister_chrdev( my_major, devname ); printk( "<1>Removing \'%s\' module\n", devname ); } #include <stdio.h> // for printf(), perror() #include <fcntl.h> // for open() #include <unistd.h> // 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..