Review from previous classes Three Types: Block, Character, and Network Interface Device Drivers MAJOR & MINOR numbers assigned register_chrdev_region(), alloc_chrdev_region(), unregister_chrdev_region() Examined Install and Cleanup Scripts struct file_operations, struct cdev, struct inode, and struct file open(), read(), write(), and release() Kernel Primitives for Memory Access: copy_to_user(), copy_from_user(), put_user(), and get_user(). request_region(), release_region() outb(), inb() ndelay(), udelay(), mdelay() 2010-10-20 1
One Module for Multiple Devices App Driver DEV 1 DEV 2 The module will register multiple minor device numbers, one for each hardware device it plans to control. Consider COM ports. One common driver for all COM ports. This concept leads to critical region management. What s a critical region of code? 2010-10-20 2
Case Study: Stepper Motor via Parallel Port App L R Driver? Motor 0 Motor 1 Out of the parallel port (0x378) bit 7 6 5 4 3 2 1 0 winding D C B A D C B A motor ------------- ------------- motor 1 motor 0 Rotate Pattern: A AB B BC C CD D DA and repeat 2010-10-20 3
Case Study: Stepper Motor via Parallel Port Our general algorithm for write() would be static int pos = 0; static char motor_table[8] = { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09 }; int module4_write (struct file *fp, const char user *buffer, size_t len, loff_t *offset) { int bytes_written = 0; char ch; const char *tmp; printk (KERN_INFO "module4: module4_write... \n"); tmp = buffer + len - 1; copy_from_user (&ch, tmp, 1); bytes_written = 1; 2010-10-20 4
Case Study: Stepper Motor via Parallel Port switch (ch) { case 'L': pos++; if (pos > 7) pos = 0; break; case 'R': } pos--; if (pos < 0) pos = 7; break; ch = motor_table[pos]; outb (ch, MODULE4_PORT); module4_lastbytewritten = ch; return bytes_written; } /* end module4_write */ 2010-10-20 5
Case Study: Stepper Motor via Parallel Port static char motor_table[8] = { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09 }; Bit pattern is only for motor0 Same pattern can be used for motor1 Can we use one module to control both motors? What do we need to keep track of? 2010-10-20 6
Case Study: Stepper Motor via Parallel Port We need to treat the parallel port data as two separate instances of data (low 4 bits for one motor, high 4 bits for the second) We need to extend our module, so that we can create two nodes within the /dev folder - one for motor0 and one for motor1 We have already seen how to allocate and register a cdev structure for an existing node we wish to install in /dev. Now we allocate two minor numbers, and register two cdev structures for, one for each minor number Let s examine the initialization process 2010-10-20 7
Case Study: Stepper Motor via Parallel Port // we now need two separate cdev structures for the two nodes static int motor0_cdev; static int motor1_cdev; static struct semaphore motor_semaphore; // discussed later! static int init initialize_motor(void) { int rc; printk(kern_info "motor: initialize_motor() called.\n"); // allocate a major number for our module, along with 2 // minor numbers this time! "motor_minor" is our first of // two minor numbers. Thus, we'll have minor numbers // 0 and 1 for this driver. >> rc = alloc_chrdev_region (&motor_devicenumber, motor_minor, 2, motor_name); if (rc < 0) { printk(kern_info "motor: unable to allocate device region.\n"); goto init_error_0; } /* endif */ 2010-10-20 8
Case Study: Stepper Motor via Parallel Port motor_major = MAJOR (motor_devicenumber); printk (KERN_INFO "motor: during INIT... major: %d minor: %d\n", motor_major, motor_minor); // initialize and register our character driver's file operations // we'll do this TWICE... once for minor 0, once for minor 1 >> cdev_init (&motor0_cdev, &motor_fops); motor0_cdev.owner = THIS_MODULE; motor0_cdev.ops = &motor_fops; >> rc = cdev_add (&motor0_cdev, MKDEV(motor_major, motor_minor), 1); if (rc) { printk(kern_info "motor: unable to add cdev struct.\n"); goto init_error_1; } /* endif */ 2010-10-20 9
Case Study: Stepper Motor via Parallel Port >> cdev_init (&motor1_cdev, &motor_fops); motor1_cdev.owner = THIS_MODULE; motor1_cdev.ops = &motor_fops; >> rc = cdev_add (&motor1_cdev, MKDEV(motor_major, motor_minor + 1), 1); if (rc) { printk(kern_info "motor: unable to add cdev struct.\n"); goto init_error_2; } /* endif */ 2010-10-20 10
Case Study: Stepper Motor via Parallel Port // now, initialize our access to I/O Port hardware // for this example, we're going to try to connect to // parallel port. Note that even though we have two // "logical" motors to support, there's only ONE parallel // port that they're controlled with! motor_region = request_region (MOTOR_PORT, 1, motor_name); if (motor_region == NULL) { printk (KERN_INFO "motor: unable to gain exclusive access to parallel port... " "but we'll use it anyways!\n"); // uncomment the next 2 lines if you wish to exit with // an error condition // rc = -EIO; // goto init_error_3; } /* endif */ 2010-10-20 11
Case Study: Stepper Motor via Parallel Port // initialize semaphore to control critical regions init_mutex (&motor_semaphore); // discussed later printk (KERN_INFO "motor: successfully completed init!\n"); return 0; // common error support for initialization init_error_3: cdev_del (&motor1_cdev); init_error_2: cdev_del (&motor0_cdev); init_error_1: unregister_chrdev_region (motor_devicenumber, 2); init_error_0: return rc; } /* end initialize_motor */ 2010-10-20 12
Two Motors with Common Handler Now let s look at the WRITE process again Select the appropriate value out of the table, and write the value to the parallel port. However, the work for motor1 is slightly different We need to shift the motor table value up by 4 bits, and then feed it out the parallel port. To avoid altering the positional information for motor0, We need to OR the data for motor1 with the existing data for motor0. And of course, vice versa if we're controlling motor0 2010-10-20 13
Two Motors with Common Handler int motor_write (struct file *fp, const char user *buffer, size_t len, loff_t *offset) We're missing a crucial piece of information - the inode structure We can't dereference this data during a write() operation, and hence, it appears we're destined to implement two separate drivers for the two motors. So, what do we do? 2010-10-20 14
Two Motors with Common Handler int motor_open (struct inode *i, struct file *fp) The solution is within the file structure during the OPEN The file structure has a member called private_data, and is usually set to NULL to indicate "no private data". We allocate our own relative information to this pointer and we ll have access to it during the write process. 2010-10-20 15
Two Motors with Common Handler #include <asm/semaphore.h> typedef struct MOTOR_DATA { int pos; int minor; struct semaphore *sem; } MOTOR_DATA; // discussed later We dynamically allocate one of these structures extract the minor number from the user's request to open a node initialize the positional data member of this structure, connect the address of this structure to the private data member of the file structure passed down to the write() support (as well as read() and other functions) In release(), we deallocate the structure. 2010-10-20 16
Two Motors with Common Handler As usual, the KERNEL has versions of memory allocation and deallocation. kmalloc() for allocation of memory kfree() for release of memory 2010-10-20 17
Two Motors with Common Handler // note: two flags, one for each motor, to ensure that only // one top level app has a given motor open at one time static int motor0_flagopen = 0; static int motor1_flagopen = 0; int motor_open (struct inode *i, struct file *fp) { int minor; MOTOR_DATA *pmotordata; // dereference device number from inode structure, // and extract the minor number. There are two macros, // imajor() and iminor(), which are used to extract the // major and minor values out of an inode structure! >> minor = iminor (i); printk (KERN_INFO "motor: motor_open for %d starting... \n", minor); 2010-10-20 18
Two Motors with Common Handler // we will limit out char driver to only allow one // // Hence, return an error code (EBUSY in this case) >> switch (minor) { case 0: if (motor0_flagopen) { return -EBUSY; } /* endif */ motor0_flagopen++; break; case 1: if (motor1_flagopen) { return -EBUSY; } /* endif */ motor1_flagopen++; break; default: // a fault... invalid minor device number! return -EFAULT; } /* end switch */ 2010-10-20 19
Two Motors with Common Handler // at this point, either motor0 or motor1 has been // opened, and is now in use once and only once. // now we can allocate private data for this request // and store away in the file structure for later use. // NOTE: you MUST use kmalloc() and kfree() to support // kernel level memory allocation. The normal user level // malloc() and free() cannot be used! >> pmotordata = (MOTOR_DATA *)kmalloc (sizeof (MOTOR_DATA), GFP_KERNEL); if (pmotordata == NULL) { return -EFAULT; } /* endif */ // init our private data appropriately, and store the // pointer in our file structure pmotordata->minor = minor; pmotordata->pos = 0; pmotordata->sem = &motor_semaphore; // discussed later >> fp->private_data = (void *)pmotordata; 2010-10-20 20
Update to the write() Support Now we have a mechanism for accessing the specific motor How do we use it? 2010-10-20 21
Update to the write() Support int motor_write (struct file *fp, const char user *buffer, size_t len, loff_t *offset) { int bytes_written = 0; char ch; const char *tmp; MOTOR_DATA *pmotordata; // fetch our private motor data structure >> pmotordata = (MOTOR_DATA *)fp->private_data; printk (KERN_INFO "motor: motor_write %d... \n", pmotordata->minor); 2010-10-20 22
Update to the write() Support tmp = buffer + len - 1; copy_from_user (&ch, tmp, 1); bytes_written = 1; >> switch (ch) { case 'L': pmotordata->pos++; if (pmotordata->pos > 7) pmotordata->pos = 0; break; case 'R': } pmotordata->pos--; if (pmotordata->pos < 0) pmotordata->pos = 7; break; >> ch = motor_table[pmotordata->pos]; 2010-10-20 23
Update to the write() Support // ensure we combine the motor table data found above, // // mask appropriately. // AAA >> switch (pmotordata->minor) { case 0: motor_lastbytewritten = motor_lastbytewritten & 0xF0; motor_lastbytewritten = motor_lastbytewritten ch; break; case 1: motor_lastbytewritten = motor_lastbytewritten & 0x0F; motor_lastbytewritten = motor_lastbytewritten (ch << 4); break; } /* end switch */ >> outb (motor_lastbytewritten, MOTOR_PORT); // BBB return bytes_written; } /* end motor_write */ 2010-10-20 24
Concurrency A module may in fact be rescheduled to run at a later time, if the Linux kernel deems something more worthy needs to go first. When we're altering the value in our global variable motor_lastbytewritten, and since we can have two opened motors concurrently, there exists a race condition during this adjustment. So, what do we do? 2010-10-20 25
Concurrency We solve this problem with either a semaphore or a mutex Within the write() code (between AAA and BBB) we have our CRITICAL REGION semaphore needs to be initialized static struct semaphore motor_semaphore;... // inside the initialization function... init_mutex (&motor_semaphore); 2010-10-20 26
Concurrency After initialization, we add the reference to the private_data structure // init our private data appropriately, and store the // pointer in our file structure pmotordata->minor = minor; pmotordata->pos = 0; >> pmotordata->sem = &motor_semaphore; fp->private_data = (void *)pmotordata; 2010-10-20 27
Concurrency To use the mutex within the write() /* * insert in place of // AAA */ >> if (down_interruptible (pmotordata->sem)) return -ERESTARTSYS; /* * insert in place of // BBB */ >> up (pmotordata->sem); 2010-10-20 28
Concurrency The down_interruptible() function manipulates the kernel semaphore cause a module to wait until the semaphore becomes available. This is essentially using the semaphore as a mutex object (supporting our required mutual exclusion). To release a semaphore, use the companion up() function. For more info: 1. Do a Google search for down_interruptible() to find out other ways to set up a mutex! 2. LDD #3 Chapter 5 Definitely take a look at Completions, SpinLocks, and seqlocks 2010-10-20 29
In-Class Exercises Notes for Week 6 Student exercise to complete the CLEAR and RELEASE parts of today s class Exercise: Work on Assignment #3 Instead: Review Assignment #3 in class and discuss how to implement based upon the information reviewed in class. ioctl() will be reviewed next class. Need to know Project Choice by everyone by end of class. 2010-10-20 30
For next class Read the notes for Weeks 1 to 6 Read Linux Device Drivers Chapters 1, 2, 3, 5, & 9 Reminders: Mid-Term in 2 weeks October 28 th Projects Due: December 9 th 2010-10-20 31