Lab 1: Loadable Kernel Modules (Lkms)
Total Page:16
File Type:pdf, Size:1020Kb
Lab 1: Loadable Kernel Modules (LKMs) Overview For this lab, we will be interfacing with an LCD screen using the Phytec board. This means we will be creating device drivers for our system to allow it to interact with our hardware. Since our board is running Linux which is a pretty advanced operating system for most embedded devices we will want to accomplish this by appending additional functionality to the working kernel. This is done using loadable kernel modules. Background Before we get too far, here is some important information we will need to know in order to interact with the Linux kernel and perform the functions we want. To write device drivers we must first understand what they are. There are three main types of device drivers; character, block, and network. In this class, we will be writing character device drivers. A character device driver is one that transfers data directly to and from a user process. This is the most common type of device driver. Character device drivers normally perform I/O in a byte stream. They can also provide additional interfaces not present in block drivers, such as I/O control commands, memory mapping, and device polling. Drivers that support a file system are known as block device drivers. Block device drivers take a file system request, in the form of a buffer structure, and issue the I/O operations to the disk to transfer the specified block. Block device drivers can also provide a character driver interface that allows utility programs to bypass the file system and access the device directly. This device access is commonly referred to as the raw interface to a block device. A network device driver is a device driver that enables a network device to communicate between the computer and operating system as well as with other network computers and network devices. Character device drivers Character device drivers are a powerful tool for users to write code that creates a connection between the user space and kernel. It utilizes a file system interface to interact with the device. Just like when you echoed values to device files to enable them. In this case, since we will be writing the kernel module ourselves, the functionality of the file system has to be written by us in the module. The kernel module is the code that gives the functionality to some task in user space. In the user space, we will write a user space C code to communicate with the device driver. We will use the system call interface in our user space C code to access the device files that are created to talk to the device driver. You can read more depth about system calls in the documentation section. Writing device drivers for this board revolves around creating the interface to a file system that controls a given device. The behavior attributed to the read and write functionalities are designated by the developer. An example of this was seen in lab 0, where writing to the files were used to interact with gpio pins to turn LEDs on and off or set different attributes of the PWM. In most cases, device drivers will describe functionalities for file init, exit, open, close, read, and write functionalities. Many other functionalities exist that you are able to change the behavior of, but we will not spend too much time on them for this class. If you wish to read more about them, check out the book Linux Device Drivers. It is completely free available online. Figure 1: The details on how device drivers are accessed inside the Linux operating system Sample code Sample code for a device driver is included with this lab titled new_char.c. The behaviour of this code is intended to simple store the string of characters written to the file. Whenever the dev file to this device is written to, it will store the text into the 100 element character array titled data. When a user reads the file, it will return the stored text inside the array (or at least a pointer to the beginning of the array in C this is how Strings work in C). As you will see in this example there are multiple parts to a character device driver that need to be set up in order for it to properly integrate into the linux kernel. Here are a couple of main points that must be done for your module to work correctly. ● The space for the data that you will be writing to the device file must be allocated within the kernel module. In this example it is done with the code below. struct fake_device { char data[100]; struct semaphore sem; } virtual_device; ● A major number must be created for the device and the cdev object must be filled out. In the sample code, this is done with the following structures and the driver_entry(void) function struct cdev* mcdev; int major_number; dev_t dev_num; // Hold major and minor numbers that kernel gives us static int driver_entry(void); ● A semaphore and any data fields within the module must be initialized ● Pointing to the correct functions when inserting and removing the module // WHEN TO START AND STOP DRIVER module_init(driver_entry); module_exit(driver_exit); Part 1 Wiring Interacting with any hardware in a digital way comes down to sending signals of high and low voltage through data pins. The LCD used in this lab is no different. This lab will require that you utilize GPIOs to incorporate the LCD in our system. For this section, you will need to familiarize yourself with reading datasheets. The datasheet provided includes information on how to use the pins connecting to the LCD integrated circuit as well as other useful information needed from a hardware perspective. Although the material in these types of datasheets is extremely dry, learning how to read datasheets is an essential skill when working with any type of hardware in an embedded environment. The display requires an input of 8 data pins (D0 D7) along with 3 control pins (E, R/W, RS). The data pins are used to send 1 parallel byte of data to the LCD at a time, dictating which character is printed on the screen. The control pins are toggled in specific patterns to carry out operations such as write or clear. The GPIOs on the board wired to these pins of the LCD are up to you, just be consistent. The LCD will also require a few pins for power. V will be connected to 5V, and V will DD SS be connected to ground. You will also want to connect a 10k potentiometer in series between VEE and ground. If you do not have a potentiometer on hand, a 5k resistor should work well enough. Using a potentiometer allows a change in the screen brightness by varying the current output of VEE. Initializing the LCD It is important to note that our LCD display has very specific start up sequence. Page 32 of the datasheet includes a diagram to properly turn on the display. In between each step of the diagram, you will have to toggle the E pin high and back to low to achieve one clock pulse in the LCD this essentially pushes the bit orientation to the board. In order to get precise timing, it is recommended to include <linux/delay> and use the msleep(int) function to delay for a given number of milliseconds. It is recommended that the C and B bits of the initialization sequence are set to 1 to enable cursor and blink functionalities. The initialization code you write for the LCD should be contained in its own function and called in the module’s init function. This will cause the LCD to initialize every time you insert the module you created into the kernel. Get your code to a compilable state so that we can test to see if it works. More on this will be included in the following section. GPIO in kernel space In the previous lab we familiarized ourselves with gpios, but they were called in userspace. For this lab, we will be purely working in the kernel space. Unfortunately, this means you won’t be able to use any of the standard C library functions such as printf or access any of the dev files from other modules. Instead, there is another library of #include statements provided for access within the kernel. There is an appropriate replacement for printf titled printk, which will print its contents to the kernel message log rather than stdout. There are numerous library functions that you are allowed to look up and include in your code when writing your kernel module code. Specifically, you will want to look into using <linux/gpio.h> for this lab. Within <linux/gpio.h> exists functions for you to use to directly call functionalities of gpios on your board without using any dev files. Most of the functions provided by this library are fairly straightforward and only require you know the pin number to use. In order to initialize gpios, you must ensure that it is valid, request the pin, set its direction, and then export it. You will find the ability to do all of these through function calls within <linux/gpio.h>. As with any piece of embedded hardware, timing is also important. Inside of <linux/delay.h> are functions that allow you to delay within your kernel modules. This library isn’t very large, but provides access to functions to delay for accurate amounts of time. It’s possible that the only functions you will use from <linux/delay.h> are msleep and msleep_interruptible to delay for a given number of milliseconds.