ECEN 449: Microprocessor System Design Department of Electrical and Computer Engineering Texas A&M University Prof. Sunil Khatri TA: Monther Abusultan (Lab exercise created by A Targhetta and P Gratz) Laboratory Exercise #6 An Introduction to Linux Device Driver Development Objective The purpose of lab this week is to experience device driver creation in an embedded Linux environment. Device drivers are a part of the operating system kernel which serve as the bridge between user applications and hardware devices. Device drivers facilitate access and sharing of hardware devices under the control of the operating system. Similar to Lab 5, you will create a kernel module, which prints messages to the kernel’s message buffer. However, this kernel module will also moderate user access to the multiplication peripheral created in Lab 3. You will then extend the capabilities of your kernel module, thereby creating a complete character device driver. To test your multiplication device driver, you will also develop a simple Linux application which utilizes the device driver, providing the same functionality as seen in Lab 3. System Overview The hardware system you will use this week is that which was built in Lab 4. Figure 1 depicts a simplified view of the hardware and software you will create in this lab. Please note the hardware/software boundary in the figure below. Above this boundary, the blocks represent compiled code executing within the MicroBlaze processor. Below this boundary, the blocks represent hardware attached to the microprocessor. In our particular case, the hardware is a multiplication peripheral attached to the MicroBlaze via the PLB bus. 1 2 Laboratory Exercise #6 Obviously, other hardware/software interactions exist in our system, but Figure 1 focuses on that which you will provide. Also notice the existence of the kernel-space/user-space boundary. The kernel module represents the character device driver executing in kernel space, while the user application represents the code executing in user space, reading and writing to the device file, ‘/dev/multiplier’. The device file is not a standard file in the sense that it does not reside on a disk, rather it provides an interface for user applications to interact with the kernel. User Application /dev/multiplier user space kernel space kernel module software hardware multiplication peripheral Figure 1: Hardware/Software System Diagram Procedure 1. Compile a kernel module that reads and writes to the multiplication peripheral and prints the results to the kernel message buffer. (a) Create a lab6 directory and copy the ‘modules’ directory from your lab5 directory to your lab6 directory. 2 ECEN 449 Laboratory Exercise #6 3 (b) Navigate to the ‘modules’ directory and ensure the ‘Makefile’ points to the ‘linux-2.6.35.7’ kernel directory. (c) Create a new kernel module source file called ‘multiply.c’. (d) Copy the following skeleton code into ‘multiply.c’: # include # include # include # include <l i n u x / module . h> <l i n u x / k e r n e l . h> <l i n u x / i n i t . h> <asm / i o . h> /∗ /∗ /∗ /∗ Needed Needed Needed Needed by a l l m o d u l e s ∗ / f o r KERN ∗ and p r i n t k ∗ / for i n i t and e x i t macros ∗ / f o r IO r e a d s and w r i t e s ∗ / # include ” x p a r a m e t e r s . h” /∗ needed f o r p h y s i c a l address of m u l t i p l i e r ∗/ / ∗ from x p a r a m e t e r s . h ∗ / # d e f i n e PHY ADDR XPAR MULTIPLY 0 BASEADDR / / p h y s i c a l a d d r e s s o f m u l t i p l i e r /∗ s i z e of p h y s i c a l address range f o r m u l t i p l y ∗/ # d e f i n e MEMSIZE XPAR MULTIPLY 0 HIGHADDR − XPAR MULTIPLY 0 BASEADDR+1 void ∗ v i r t a d d r ; / / v i r t u a l address p o i n t i n g to m u l t i p l i e r / ∗ T h i s f u n c t i o n i s r u n upon module l o a d . T h i s i s where you s e t u p d a t a s t r u c t u r e s and r e s e r v e r e s o u r c e s u s e d by t h e module . ∗ / static int i n i t m y i n i t ( void ) { /∗ Linux k e r n e l ’ s v e r s i o n of p r i n t f ∗/ p r i n t k ( KERN INFO ” Mapping v i r t u a l a d d r e s s . . . \ n ” ) ; / ∗ map v i r t u a l a d d r e s s t o m u l t i p l i e r p h y s i c a l a d d r e s s ∗ / / / use ioremap /∗ write 7 to r e g i s t e r 0 ∗/ p r i n t k ( KERN INFO ” W r i t i n g a 7 t o r e g i s t e r 0\ n ” ) ; i o w r i t e 3 2 ( 7 , v i r t a d d r +0); / / base address + o f f s e t /∗ Write 2 to r e g i s t e r 1 ∗/ p r i n t k ( KERN INFO ” W r i t i n g a 2 t o r e g i s t e r 1\ n ” ) ; / / use i o w r i t e 3 2 p r i n t k ( ” Read %d from r e g i s t e r 0\ n ” , i o r e a d 3 2 ( v i r t a d d r + 0 ) ) ; p r i n t k ( ” Read %d from r e g i s t e r 1\ n ” , \\ u s e i o r e a d 3 2 ) ; p r i n t k ( ” Read %d from r e g i s t e r 2\ n ” , i o r e a d 3 2 ( v i r t a d d r + 8 ) ) ; / / A non 0 r e t u r n means i n i t m o d u l e f a i l e d ; module can ’ t be l o a d e d . return 0; } / ∗ T h i s f u n c t i o n i s r u n j u s t p r i o r t o t h e module ’ s r e m o v a l f r o m t h e s y s t e m . You s h o u l d r e l e a s e ALL r e s o u r c e s u s e d by y o u r module h e r e ( o t h e r w i s e be p r e p a r e d f o r a r e b o o t ) . ∗ / s t a t i c void e x i t my exit ( void ) ECEN 449 3 4 Laboratory Exercise #6 { p r i n t k ( KERN ALERT ” unmapping v i r t u a l a d d r e s s s p a c e . . . . \ n ” ) ; iounmap ( ( v o i d ∗ ) v i r t a d d r ) ; } / ∗ T h e s e d e f i n e i n f o t h a t can be d i s p l a y e d by m o d i n f o ∗ / MODULE LICENSE ( ”GPL” ) ; MODULE AUTHOR( ”ECEN449 S t u d e n t ( and o t h e r s ) ” ) ; MODULE DESCRIPTION ( ” S i m p l e m u l t i p l i e r module ” ) ; / ∗ Here we d e f i n e w h i c h f u n c t i o n s we want t o u s e f o r i n i t i a l i z a t i o n and c l e a n u p ∗ / module init ( my init ); module exit ( my exit ) ; (e) Some required function calls have been left out of the code above. Fill in the necessary code. Consult Linux Device Drivers, 3rd Edition for help. Note: When running Linux on the MicroBlaze, your code is operating in virtual memory, and ‘ioremap’ provides the physical to virtual address translation required to read and write to hardware from within virtual memory. ‘iounmap’ reverses this mapping and is essentially the cleanup function for ‘ioremap’. (f) Add a ‘printk’ statement to your initialization routine that prints the physical and virtual addresses of your multiplication peripheral to the kernel message buffer. (g) Compile your kernel module. Do not for get to source the ‘compile settings.csh’ file. (h) Load the ‘multiply.ko’ module onto the CF card and mount the card within the XUP Linux system using ‘/lib/modules/2.6.35.7’ as the mount point. Consult Lab 5 if this is unclear. (i) Use ‘insmod’ to load the ‘multiply.ko’ kernel module into the XUP Linux kernel. Demonstrate your progress to the TA. 2. With a functioning kernel module that reads and writes to the multiplication peripheral, you are now ready to create a character device driver that provides applications running in user space access to your multiplication peripheral. (a) From within the ‘modules’ directory, create a character device driver called ‘multiplier.c’using the following guidelines: • Take a moment to examine ‘my chardev.c’, ‘my chardev mem.c’ and the appropriate header files within ‘/homes/faculty/shared/ECEN449/module examples/’. Use the character device driver examples provided in Linux Device Drivers, 3rd Edition and the laboratory directory as a starting point for your device driver. 4 ECEN 449 Laboratory Exercise #6 5 • Use the ‘multiply.c’ kernel module created in Section 2 of this lab as a baseline for reading and writing to the multiplication peripheral and mapping the multiplication peripheral’s physical address to virtual memory. • Within the initialization routine, you must register your character device driver after the virtual memory mapping. The name of your device should be ‘multiplier’. Let Linux dynamically assign your device driver a major number and specify a minor number of 0. Print the major number to the kernel message buffer exactly as done in the examples provided. Be sure to handle device registration errors appropriately. You will be graded on this! • In the exit routine, unregister the device driver before the virtual memory unmapping. • For the open and close functions, do nothing except print to the kernel message buffer informing the user when the device is opened and closed. • Read up on ‘put user’ and ‘get user’ in Linux Device Drivers, 3rd Edition as you will be using these system calls in your custom read and write functions. • For the read function, your device driver must read bytes 0 through 11 within your peripheral’s address range and put them into the user space buffer. Note that one of the parameters for the read function, ‘length’, specifies the number of bytes the user is requesting. Valid values for this parameter include 0 through 12. Your function should return the number of bytes actually transfered to user space (i.e. into the buffer pointed to by char* buf). You may use ‘put user’ to transfer more than 1 byte at a time. • For the write function, your device driver must copy bytes from the user buffer to kernel space using the system call ‘get user’ to do the transfer and the variable ‘length’ as a specification of how many bytes to transfer. Furthermore, the device driver must write those bytes to the multiplication peripheral . The return value of your write function should be similar to that of your read function, returning the number of bytes successfully written to your multiplication peripheral. Only write to valid memory locations (i.e. 0 through 7) within your peripheral’s address space. (b) Modify the ‘Makefile’ to compile ‘multiplier.c’ and run ‘make ARCH=microblaze’ in the terminal to create the appropriate kernel module. (c) As with ‘multiply.ko’, load ‘multiplier.ko’ into your XUP Linux system. (d) Use ‘dmesg’ to view the output of the kernel module after it has been loaded. Follow the instructions provided within the provided example kernel modules to create the ‘/dev/multiplier’ device node. Demonstrate your progress to the TA. 3. At this point, we need to create a user application that reads and writes to the device file, ‘/dev/multiplier’ to test our character device driver and provides the required functionality similar to that seen in Lab 3. (a) View the man pages on ‘open’, ‘close’, ‘read’, and ‘write’ from within the CentOS workstation. You will use these system calls in your application code to read and write to the ‘/dev/multiplier’ ECEN 449 5 6 Laboratory Exercise #6 device file. Accessing the man pages can be done via the terminal. For example, to view the man pages on ‘open’, type ‘man open’ in a terminal window. (b) Within the ‘modules’ directory, create a source file called ‘devtest.c’ and copy the following starter text into that file: # include # include # include # include # include # include <s y s / t y p e s . h> <s y s / s t a t . h> < f c n t l . h> < s t d i o . h> <u n i s t d . h> < s t d l i b . h> i n t main ( ) { unsigned i n t r e s u l t ; in t fd ; int i , j ; /∗ f i l e descriptor ∗/ /∗ loop v a r i a b l e s ∗/ char i n p u t = 0 ; / ∗ open d e v i c e f i l e f o r r e a d i n g and w r i t i n g ∗ / / ∗ u s e ” open ” t o open ‘ / d e v / m u l t i p l i e r ’ ∗ / /∗ handle error opening f i l e ∗/ i f ( f d == −1){ p r i n t f ( ” F a i l e d t o open d e v i c e f i l e ! \ n ” ) ; r e t u r n −1; } while ( i n p u t != ’ q ’ ){ / ∗ c o n t i n u e u n l e s s u s e r e n t e r e d ’ q ’ ∗ / f o r ( i = 0 ; i <=16; i ++){ f o r ( j = 0 ; j <=16; j ++){ / ∗ w r i t e v a l u e s t o r e g i s t e r s u s i n g char dev ∗ / / ∗ u s e w r i t e t o w r i t e i and j t o p e r i p h e r a l ∗ / / ∗ r e a d i , j , and r e s u l t u s i n g c h a r d e v ∗ / / ∗ use read t o read from p e r i p h e r a l ∗ / /∗ p r i n t unsigned i n t s to screen ∗/ p r i n t f ( ”%u ∗ %u = %u ” , r e a d i , r e a d j , r e s u l t ) ; /∗ validate r e s u l t ∗/ i f ( r e s u l t == ( i ∗ j ) ) p r i n t f ( ” Result Correct ! ” ) ; else p r i n t f ( ” Result Incorrect ! ” ) ; / ∗ read from t e r m i n a l ∗ / input=getchar ( ) ; 6 ECEN 449 Laboratory Exercise #6 7 } } } close ( fd ) ; return 0; } (c) Complete the skeleton code provided above using the specified system calls. (d) Compile ‘devtest.c’ by executing the following command in the terminal window under the ‘modules’ directory: >mb-linux-gcc -o devtest devtest.c (e) Copy the executable file, ‘devtest’, on to the CF card and execute the application by typing the following in the XUP terminal within the CF mount point directory: >./devtest (f) Examine the output as you hit the ‘enter’ key from the XUP terminal. Demonstrate your progress to the TA. Deliverables 1. [5 points.] Demo the working kernel module and device driver to the TA. Submit a lab report with the following items: 2. [5 points.] Correct format including an Introduction, Procedure, Results, and Conclusion. 3. [4 points.] Commented C files. 4. [2 points.] The output of the XUP terminal (kermit) for parts 1 through 3 of lab. 5. [4 points.] Answers to the following questions: (a) Given that the multiplier hardware uses memory mapped I/O (the processor communicates with it through explicitly mapped physical addresses), why is the ioremap command required? (b) Do you expect that the overall (wall clock) time to perform a multiplication would be better in part 3 of this lab or in the original Lab 3 implementation? Why? (c) Contrast the approach in this lab with that of Lab 3. What are the benefits and costs associated with each approach? (d) Explain why it is important that the device registration is the last thing that is done in the initialization routine of a device driver. Likewise, explain why un-registering a device must happen first in the exit routine of a device driver. ECEN 449 7