Linux Device Driver Lab4part - B

Download as pdf or txt
Download as pdf or txt
You are on page 1of 6
At a glance
Powered by AI
The document discusses writing device drivers for custom hardware and running them on Linux. Device drivers allow the system to interact with hardware and can be implemented as built-in or loadable kernel modules. A character device driver is written as an example.

Device drivers can be implemented as built-in static kernel code or as dynamic loadable modules. The document discusses writing a driver as a loadable kernel module.

The steps discussed are creating a Makefile, writing init and exit functions, registering the driver using register_chrdev, creating a /dev file, and writing read and write functions to interface with the driver.

Lab 4B.

Linux boot-up on XUP board and writing Device Drivers for Custom-IP
Objective In this week, we will write the device driver for a Custom IP (the multiplier that we learnt to create in Lab 3) and run it on the Linux OS. Device Drivers In the kernel, everything that the system interacts with is represented by a device driver. Drivers can be implemented in two different ways: as a built-in piece of static kernel code, or as a dynamic piece of code known as a module. A built-in driver is, as the name suggests, compiled in statically to the kernel, and is always ready to be called. A kernel module, however, is a separate piece of code which can be loaded and unloaded from the kernel on the fly. While you must load a module to be able to use it, the advantage is that it doesn't take up memory when you don't need it. We shall be using the modules approach. A kernel module (drivers are kernel modules) must be compiled as a ".o" file (not linked to an executable). In this respect, it is different from conventional programs. Rather than executing it, you install it into the running Linux kernel at runtime. The Unix way of looking at devices distinguishes between three device types. Each module usually implements one of these types, and thus is classifiable as a char module, a block module, or a network module. We shall be writing the driver for a character (char) device. A char device is one that can be accessed as a stream of bytes (like a file). An useful resource for this lab is OReilly Linux Device Drivers, 2nd Edition. Note that you need to look at the second edition of the book which deals with linux kernel versions 2.0 through 2.4, with focus on all the features available to device driver writers in 2.4. Please read Chapter 3 of this book (Char Drivers) to understand how to write a complete char device driver. Procedure 1. Log on to the linux machine using the ssh client with your id and password. 2. Create a directory called MODULES in your home directory. We shall be creating all subdirectories for our modules within this directory. We will start with a device driver for the simple hello world program. In your home directory, mkdir p MODULES/hello cd MODULES/hello 3. In the hello directory, create a file called hello.c with the following code:
#include <linux/kernel.h> #include <linux/module.h>

static int init_module(void) { printk("Hello world!\n"); return 0; } static void cleanup_module(void) { printk("Bye, cruel world\n"); }

Note that the kernel code uses printk instead of printf statements. 4. The Makefile to compile this code will be:
COMPILER = powerpc-linux-gcc LD = powerpc-linux-ld UCFRONT = ~/uClinux-dist/tools/ucfront-gcc COMPILER_FLAGS= COMPILER_FLAGS+= -D__KERNEL__ COMPILER_FLAGS+=-DMODULE PATH := $(PATH):~/uClinux-dist/tools:~/powerpc-elf-tools/bin

RELEASEDIR= INCLUDES=-I./. SOURCES = hello.c OUT = hello.o all: hello hello: $(SOURCES) echo "Compiling module" $(COMPILER) $(COMPILER_FLAGS) $(INCLUDES) -c -o $(OUT) (SOURCES)

5. Run make in the hello directory. It should generate hello.o file for you. This is the driver .o file that needs to be inserted in the kernel. We need to copy this .o inside our linux filesystem. To do this, change directory to uClinux-dist and open the file uClinux-dist/vendors/Xilinx/powerpcauto/Makefile. Scroll down till you find the command: $(ROMFSINST) -s bin /sbin Below this command, add the following lines: [ -d $(ROMFSDIR)/module ] || mkdir $(ROMFSDIR)/module cp ~/MODULES/hello/hello.o $(ROMFSDIR)/module/hello.o This will create a directory called module in the linux filesystem, and place the file hello.o inside it. Now type make in the uClinux-dist directory to generated the updated image.elf file. Check that a directory called module is indeed created in uClinuxdist/romfs and it contains the hello.o file.

Use this new image.elf file to create the system.ace file. Boot linux on the hyperterminal.
6. On the hyperterminal screen, type:

cd module insmod hello.o The insmod command invokes the init_module routine of your driver code. You should thus see Hello world! printed on the hyperterminal screen. Also, when you do lsmod, it should show the hello module. Remove this module by rmmod hello command. This will invoke the cleanup_module routine. You should see the print statement corresponding to cleanup_module on the hyperterminal screen.
7. Now, let us write a simple driver for the multiply Custom IP. Copy the file

multiply.h from lab4/drivers/multiply_v1_00_a/src/multiply.h on your Windows machine to Desktop (on Windows machine) and open it with Notepad/Wordpad. Find and replace all Xuint32 by u32 and then copy this file to MODULES/multiply on the linux box. For the multiplier, make the following changes in the Makefile (compared to the one for the hello module).
INCLUDES+= -I~/uClinux-dist/linux-2.4.x/include INCLUDES+= -I~/uClinux-dist/linux-2.4.x/arch/ppc INCLUDES+= -I~/uClinux-dist/linux2.4.x/arch/ppc/platforms/xilinx_ocp SOURCES = multiply.c INCLUDEDIR = multiply.h OUT = multiply.o all: multiply multiply: $(SOURCES) $(INCLUDEFILES) echo "Compiling module" $(COMPILER) $(COMPILER_FLAGS) $(INCLUDES) -c -o $(OUT) (SOURCES)

8. Add the following code to multiply.c


#include <linux/module.h> #include <linux/init.h> #include <linux/autoconf.h> defines

// This is the file from EDK with

// This file enerated by the EDK #include "multiply.h" // Declare some variables // The physical base address for the multiplier device. unsigned phy_add = CONFIG_XILINX_MULTIPLY_0_BASEADDR;

// The size of memory allocated to the multiplier device. unsigned remapSize = CONFIG_XILINX_MULTIPLY_0_HIGHADDR CONFIG_XILINX_MULTIPLY_0_BASEADDR + 1; // This will be the virtual address used to point to multiplier unsigned virt_add; // This is the code that gets executed upon loading of the module. It is called instead of init_module() because of a macro declared at the bottom of the file. int multiplierInit(void) { int res; // Map the device's addresses virt_add = (unsigned)ioremap(phy_add, remapSize); printk("MULTIPLY PA of 0x%x has mapped to a VA of 0x%x\n", phy_add, virt_add); // Write to register 0 printk("Writing a 7 to register MULTIPLY_mWriteReg(virt_add, 0, // Write to register 1 printk("Writing a 2 to register MULTIPLY_mWriteReg(virt_add, 4,

0\n"); 7); 1\n"); 2);

printk("Read %d from register 0\n", MULTIPLY_mReadReg(virt_add, 0)); printk("Read %d from register 1\n", MULTIPLY_mReadReg(virt_add, 4)); printk("Read %d from register 2\n", MULTIPLY_mReadReg(virt_add, 8)); return 0; } // This is the routine called when the module is unloaded. // essentially un-does what the initialization code did. void multiplierExit(void) { // Unmap the virtual address for the device iounmap((void *)virt_add); } // Tell compiler to not export anything at all from this module for use by others. EXPORT_NO_SYMBOLS; // Declare names of init and cleanup functions module_init(multiplierInit); module_exit(multiplierExit); It

Compile this code with the make command. Add multiply.o in your linux filesystem. When you insmod multiply.o, the multiplierInit routine will be executed. - In function calls to MULTIPLY_mWriteReg (and MULTIPLY_mReadReg), in kernel space, note that virtual address is used instead of the physical address. - Also, the size of each of the registers (declared in your Verilog Custom IP) is 32

bits (4 bytes). The offset for register0 is 0, register1 is 4 and register2 is 8. These numbers are same as the numbers you would have used in the multiplier IP of Lab 3. 9. Note that we have still not registered a device and written a driver for that. All the functionality is provided in the init_module routine itself. This is not typically how practical device drivers are written. insmod is used to register the device and obtain a virtual address for the device. The read/ write routines are then called, as required. When you do not need the device any longer, cleanup_module routine is called to unregister the device and unmap the virtual address for the device. 10. Create a directory called multiplier in your MODULES directory on the linux box (note that we are calling this module multiplier, while your last module was called multiply. This is to differentiate between the two. You will be able to insmod both your multiply and multiplier modules one after the other. Two modules cannot have the same name, two modules can however be used to drive the same device). You will now write the init, exit, read and write routines for your driver. You will also write a file called devtest.c to test your multiplier. We need to modify the Makefile to include the command to compile devtest.c. Following lines need to be added:
all: multiplier devtest devtest: devtest.c $(UCFRONT) $(COMPILER) -Os -g -fomit-frame-pointer -fnocommon -fno-builtin -Wall -DEMBED -Dlinux -D__linux__ -Dunix D__uClinux__ -Wall -O -c -o devtest.o devtest.c $(UCFRONT) $(COMPILER) devtest.o -o devtest

The modified Makefile, along with the skeleton for multiplier.c and devtest.c can be copied from /software/module/multiplier directory. 11. The skeleton files are commented to indicate what functions you need to use. Please read Chapter 3 of Linux Device Drivers, 2nd Edition to understand the arguments to these functions. Some useful information is provided below: - To link normal files with a kernel module two numbers are used: major number and minor number. The major number is the one the kernel uses to link a file with its driver. It is common for a driver to control several devices (as shown in the listing); the minor number provides a way for the driver to differentiate among them. The minor number is used by the driver for interfacing with multiple devices. - To interface the normal files with the kernel module (using major and minor numbers as discussed above), a file (which will be used to access the device

driver) must be created, by typing the following command as root: # mknod /dev/multiplier c 207 0 where c indicates that multiplier is char type device, 207 is the major number and 0 minor number. However, the mknod command is not available on our FPGA linux. In order to create this multiplier file in /dev, scroll down in uClinuxdist/vendors/Xilinx/powerpc-auto/Makefile till you see:
ifdef CONFIG_MTD for i in $(DEVICES); do \ touch $(ROMFSDIR)/dev/@$$i; \ done

Add the following line here:


touch $(ROMFSDIR)/dev/@multiplier,c,207,0

We are controlling just one device with our driver, any arbitrary value (in the valid range 0 to 255) can be assigned to the minor number. After you compile your linux filesystem (with the above mentioned addition in the romfs/dev directory) and run it on the hyperterminal, run ls l /dev/multiplier. You should see the following: crw------- 1 root 0 207, 0 Jan 1 00:00 /dev/multiplier - Within the driver, in order to link it with its corresponding /dev file in kernel space, the register_chrdev function is used. It is called with three arguments: major number, a string of characters showing the module name, and a file_operations structure which links the call with the file functions it defines. - After you do an insmod on your multiplier.o module, the module multiplier will appear in /proc/modules (and can be seen using the lsmod command). Also, the device registered (using register_chrdev) should be seen in /proc/devices. 12. Place multiplier.o and devtest in romfs/module directory. On the hyperterminal, insmod multiplier.o and run devtest as: ./devtest Look at the print statements corresponding to your code, to check that the driver and your test code is working correctly.

Deliverables Place hello.o, multiply.o, multiplier.o and devtest in the romfs/module directory. Boot Linux on the PowerPC and then demonstrate the following to the TA. 1. Demonstrate insmod and rmmod on the hello.o and multiply.o modules. [3 points] 2. Demonstrate insmod, ./devtest and rmmod multiplier.o. [4 points] 3. Submit a report with the code for multiplier.c and devtest.c. [3 points]

You might also like