ECEN 449: Microprocessor System Design Department of Electrical and Computer Engineering Texas A&M University Prof. Sunil P Khatri (Lab exercise created and tested by Ramu Endluri, He Zhou and Sunil P Khatri) Laboratory Exercise #6 An Introduction to Linux Device Driver Development Objective The purpose of lab this week is to create device drivers in an embedded Linux environment. Device drivers are a part of the operating system kernel which serves 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 ARM 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 ARM Processor. Obviously, other hardware/software interactions exist in our system, but Figure 1 focuses on that which you will provide. Also 1
2 Laboratory Exercise #6 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 multiplication peripheral software hardware 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. (b) Navigate to the modules directory and ensure that the Makefile points to the Linux kernel directory. 2 ECEN 449
Laboratory Exercise #6 3 (c) Create a new kernel module source file called multiply.c. (d) Copy the xparameters.h and xparameters ps.h files in to modules directory (e) Copy the following skeleton code into multiply.c : # i n c l u d e <l i n u x / module. h> / Needed by a l l modules / # i n c l u d e <l i n u x / k e r n e l. h> / Needed f o r KERN and p r i n t k / # i n c l u d e <l i n u x / i n i t. h> / Needed f o r i n i t and e x i t macros / # i n c l u d e <asm / i o. h> / Needed f o r IO r e a d s and w r i t e s / # i n c l u d e x p a r a m e t e r s. h / needed f o r 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 / / from x p a r a m e t e r s. h / # d e f i n e PHY ADDR XPAR MULTIPLY 0 S00 AXI 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 o f p h y s i c a l a d d r e s s range f o r m u l t i p l y / # d e f i n e MEMSIZE XPAR MULTIPLY 0 S00 AXI HIGHADDR XPAR MULTIPLY 0 S00 AXI BASEADDR+1 void v i r t a d d r ; / / v i r t u a l a d d r e s s p o i n t i n g t o m u l t i p l i e r / T h i s f u n c t i o n i s run upon module load. T h i s i s where you s e t u p data 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 used by t h e module. / s t a t i c i n t 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 o f 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 / w r i t e 7 t o 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 a d d r e s s + o f f s e t / W r i t e 2 t o 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, \\ use 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 loaded. return 0 ; / T h i s f u n c t i o n i s run j u s t p r i o r t o t h e module s removal from 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 used by your module h ere ( 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 m y e x i t ( 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 ( ( void ) v i r t a d d r ) ; / These 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 modinfo / MODULE LICENSE( GPL ) ; MODULE AUTHOR( ECEN449 S t u d e n t ( and o t h e r s ) ) ; MODULE DESCRIPTION( Simple m u l t i p l i e r module ) ; / Here we d e f i n e which f u n c t i o n s we want t o use 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 / m o d u l e i n i t ( m y i n i t ) ; m o d u l e e x i t ( m y e x i t ) ; (f) 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 ARM processor, 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 clean-up function for ioremap. (g) Add a printk statement to your initialization routine that prints the physical and virtual addresses of your multiplication peripheral to the kernel message buffer. (h) Edit your Makefile to compile multiply.c. Compile your kernel module. Do not for get to source the settings64.sh file. (i) Load the multiply.ko module onto the SD card and mount the card within the ZYBO Linux system using /mnt as the mount point. Consult Lab 5 if this is unclear. (j) Use insmod to load the multiply.ko kernel module into the ZYBO 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 /home/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=arm CROSS COMPILE=arm-xilinx-linux-gnueabiin the terminal to create the appropriate kernel module. (c) As with multiply.ko, load multiplier.ko into your ZYBO 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. ECEN 449 5
6 Laboratory Exercise #6 (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 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: # i n c l u d e <s y s / t y p e s. h> # i n c l u d e <s y s / s t a t. h> # i n c l u d e < f c n t l. h> # i n c l u d e <s t d i o. h> # i n c l u d e <u n i s t d. h> # i n c l u d e < s t d l i b. h> i n t main ( ) { unsigned i n t r e s u l t ; i n t fd ; / f i l e d e s c r i p t o r / i n t i, j ; / 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 / / use open t o open / dev / m u l t i p l i e r / / h a n d l e e r r o r opening f i l e / i f ( fd == 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 ) ; return 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 / / use 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 / / read i, j, and r e s u l t u s i n g char dev / / use read t o read from p e r i p h e r a l / / p r i n t u n s i g n e d i n t s t o s c r e e n / 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 ) ; / v a l i d a t e r e s u l t / i f ( r e s u l t == ( i j ) ) p r i n t f ( R e s u l t C o r r e c t! ) ; e l s e p r i n t f ( R e s u l t I n c o r r e c t! ) ; 6 ECEN 449
Laboratory Exercise #6 7 / read from t e r m i n a l / i n p u t = g e t c h a r ( ) ; c l o s e ( 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: >arm-xilinx-linux-gnueabi-gcc -o devtest devtest.c (e) Copy the executable file, devtest, on to the SD card and execute the application by typing the following in the terminal within the SD card mount point directory: >./devtest (f) Examine the output as you hit the enter key from the 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 picocom terminal for parts 2 through 4 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