380 likes | 644 Views
Character Device Driver (Delay Routine with CMOS RAM example). Dr A Sahu Dept of Comp Sc & Engg . IIT Guwahati. Outline. Writing/Registering to /proc Character Device Driver Characteristics and functionality Basic IO functions Examples (ADC, DAC, Printer, Test data generator)
E N D
Character Device Driver(Delay Routine with CMOS RAM example) Dr A Sahu Dept of Comp Sc & Engg. IIT Guwahati
Outline • 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 Kernel Modules Linux allows us to write our own installable kernel modules and add them to a running system application module ret call call Operating System kernel ret syscall standard “runtime” libraries sysret user space kernel space
Creating our own ‘/proc’ files • 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
Register/unregister • 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 directory file’s name
Device Driver (Block/Character) • 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, …
Overall Architecture (cont.) System Call Interface Socket VFS File System Network Protocol Buffer Cache Block Device Driver Character Device Driver Network Device Driver Hardware
Loadable Module Entry Points • 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().
Character Driver • Device Properties • can’t be randomly accessed • can’t be buffered • usually are slow devices • Export Interface • file_operations • Data Flow in read/write
Basic char-driver components Device-driver LKM layout module’s ‘payload’ is a collection of callback-functions having prescribed prototypes function function function AND a ‘package’ of function-pointers fops . . . the usual pair of module-administration functions init registers the ‘fops’ exit unregisters the ‘fops’
Character Device Drivers • 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.
Character and Block Driver Entry Points • 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.
Background • 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
Standard File-I/O 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)
Special ‘device’ files • 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
The ‘close’ function • Breaks link between file and file-descriptor • Returns 0 on success, or -1 if an error #include <unistd.h> int close( intfd );
The ‘write’ function #include <unistd.h> int write( intfd, 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
The ‘read’ function #include <unistd.h> int read( intfd, 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’
Notes on ‘read()’ and ‘write()’ • 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
The ‘lseek’ function #include <unistd.h> off_tlseek(intfd, 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)
Getting the size of a file • For normal files, your application can find out how many bytes belong to a file using the ‘lseek()’ function: intfilesize=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 );
Implement a Simple Char Driver for CMOS RTC non-volatile memory • 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
Example: ‘cmosram.c’ driver • 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
Basic char-driver components Device-driver LKM layout module’s ‘payload’ is a collection of callback-functions having prescribed prototypes function function function AND a ‘package’ of function-pointers fops . . . the usual pair of module-administration functions init registers the ‘fops’ exit unregisters the ‘fops’
The ‘fops’ syntax • The GNU C-compiler supports a syntax for ‘struct’ field-initializations that lets you give your field-values in any convenient order: structfile_operationsmy_fops = { llseek: my_llseek, write: my_write, read: my_read, };
Implement a Simple Char Driver for CMOS RTC non-volatile memory #include <linux/module.h> // for printk() #include <linux/fs.h> // for register_chrdev() #include <asm/uaccess.h> // for put_user(), get_user() #include <asm/io.h> // 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
Character driver : Read() & Write() ssize_tmy_read( struct file *file, char *buf, size_tlen, 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_tmy_write( struct file *file, const char *buf, size_tlen, 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; }
Character driver : Lseek() loff_tmy_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 < 0 )||( newpos > cmos_size )) return –EINVAL; file->f_pos = newpos; return newpos; }
Character driver : init(), exit(), ops structfile_operationsmy_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 ); } module_init( my_init ); module_exit( my_exit ); MODULE_LICENSE("GPL");
30 second loop: ThirtySec.cpp #include <stdio.h> // for printf(), perror() #include <fcntl.h> // for open() #include <unistd.h> // for read() int main( intargc, char **argv ) { int status = 0; intfd = 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 (inti = 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 ); } }
Lab Exercise • Download mmake.cpp and cmosram.c • Course Website, tested on Fedora 12 • Compile mmake.cpp using ‘g++’ • Then compile cmosram.cusing ‘make’ • Install ‘cmos.ko’ (and see printk-message) • See $cat /proc/cmos • Compile ThirtySec.cpp and execute..