<<

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 ­­ which is a pretty advanced 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 and perform the functions we want. To 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 is one that transfers data directly to and from a . 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 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 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 and operating system as well as with other network and network devices.

Character device drivers Character device drivers are a powerful tool for users to write code that creates a connection between the 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 code to communicate with the device driver. We will use the interface in our user space C code to access the device files that are created to talk to ​ the device driver. You can 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, , , , read, and write functionalities. Many other functionalities exist that you are able to change the behavior of, but we will not spend too much 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 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; 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 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 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 for this lab. Within ​ 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 . ​

As with any piece of embedded hardware, timing is also important. Inside of 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 are msleep and msleep_interruptible to delay for a given ​ ​ ​ number of milliseconds. There are a few others provided that you are welcome to play around with, but none are required.

Compiling kernel modules If your code is written without errors, you should be able to compile it into a .ko file. This is a good sign, but isn’t exactly what we want when creating a kernel module. Instead, we will be compiling your code into a .ko file so it can be inserted directly into the kernel. The makefile for this process is slightly different, so you should use this for your new makefile: export ARCH=arm export CROSS_COMPILE=arm­linux­gnueabihf­ ccflags­y := ­std=gnu99

obj­m += lcd.o

# Kernel source directory KDIR =kernel_source_directory_goes_here PWD = $( pwd)

default: $(MAKE) ­C $(KDIR) SUBDIRS=$(PWD) modules clean: $(MAKE) ­C $(KDIR) SUBDIRS=$(PWD) clean This makefile was created to make a .ko file from code titled “lcd.c”. However, before you try to compile your “lcd.c”, there is a necessary stuff you need to do. As you notice in the code above, the kernel_source_directory_goes_here ​ need to be replaced. You need to install the kernel source directory to the computer which contains all of the backup files and programs in order to compile the kernel module and load the kernel module on the board. To install the kernel source directory, first, check the version of your operating system on the board. To do that, ssh to your beaglebone black and type: ​ ​ uname ­r then go back to your computer’s terminal, and follow the commands below: git clone https://github.com/RobertCNelson/bb­kernel.git ​ ​ ​ bb­kernel ​ git tag (This shows what versions can be checked out.) ​ ​ git checkout 3.8.13­bone70 ­b 3.8.13­bone70 ​ ​ ​ ​ ​

(3.8.13­bone70 corresponding to the version of the operating system on your beaglebone black) now we need to build the kernel:

./build_kernel.sh

Then the terminal will throw an failure message and an instruction like below:

+ Detected build host [ 14.04 LTS] + host: [x86_64] + git HEAD commit: [58ee2121370badd3c0a22e39f67068f242d5e068] /Ubuntu/Mint: missing dependencies, please install: ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ sudo apt­get update sudo apt­get install build­essential device­tree­compiler fakeroot lzma lzop u­boot­tools libncurses5­dev:amd64 libc6:i386 libncurses5:i386 libstdc++6:i386 zlib1g:i386 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ * Failed dependency check follow the instruction above (you may need admin password. Check it out with your TA), then type: gedit version.sh ​ comment out toolchain=”gcc_linaro_gnueabihf_4_7” by adding a # before it, ​ ​ and then de­comment toolchain=”gcc_linaro_gnueabihf_4_8”, save and close ​ ​ the version.sh, then rerun the build_kernel.sh. until all of the files and ​ ​ ​ ​ ​ programs to be downloaded and installed (if the system asks you to log in your gitlab account, do so and rerun build_kernel.sh). ​ ​

After a while, you will see Select Exit. Then it will install the kernel for you. The installing procedure takes about 15 or 20 mins, take a break and come back in 20 mins. If it succeeds, the terminal will show Script Complete. At this point, you have completed building your kernel ​ ​ source directory to your computer. Once you have done that, you don’t need to do it anymore in the future.

Then we need to find the directory of the kernel source and replace the fake code in the Makefile. cd bb_kernel/KERNEL ​ pwd you will then get the actual kernel source directory. Replace the fake code with it. And one more small change to the obj­m line to the name of your file should then make this makefile script work exactly for you. If not, then the directory of your kernel compiler is probably incorrect and you will have to change the directory entry under KDIR.

After your module is compiled you can upload it to the board and try running it. Do this using the same method as you did in Lab 0. SSH into your board and go to the directory where the kernel module is. instead of typing ./lcd like you would with previous code ​ to run your program, you will need to manually insert it into the kernel. It’s not too difficult and only requires one extra command: insmod. If you named your file lcd.ko, ​ ​ you just need to type insmod lcd.ko. And that’s it! Your module is now inserted. It ​ ​ ​ may seem like nothing has changed, so to prove it that you changed the kernel, you can type lsmod to see a list of running modules on your Phyboard. Upon insertion, it should ​ automatically run the init code you created earlier. For future use, if you wish to remove this module, you will type rmmod lcd.ko. If you have been using printk to print ​ ​ ​ error/success messages out, you can also type to see a list of driver messages ​ stored on your board. You’ll likely want to only see the last few entries if you’re one module, so using dmesg | tail might be more appropriate. ​ ​

Your LCD will have a blinking cursor on it to prove that your initialization code is working. This is a good point to stop and make sure is correct before you continue. You won’t be able to test any of your code until the initialization code is correct. It’s unlikely that your code will work as intended the first time, but that’s okay! It’s very rare for any new piece of hardware to work on your first try. Debugging your code and ironing out the logic will probably take up most of the time for the rest of the labs in this class.

Part 2 Printing text With your LCD initialized, you will be able to send it characters to display on the screen. Page 47 of the data sheet will given timing characteristics of the display for the write operation. Further below on page 49 will show a chart with the different timing values included in the diagram. Your goal is to create a function that prints a single character to the board.

/dev/ files The module you’ve been writing is intended to work as a character device, which means we should be able to interact with it by writing characters to some device file. Device files are not automatically created when the module is inserted, and it is standard practice for you to write a normal linux terminal command to do this yourself. For a file named lcd.ko, you will type the command mknod /dev/lcd c . You must know the major and minor numbers of your device to even ​ create it in the first place, so it’s very common to use a printk statement to write out ​ this exact command, and you copy and paste it directly from the dmesg buffer. It’s ​ extremely likely that your major number is 243 if this is your first device added to the Phyboard. It’s even more likely that your minor number is always 0 for any new module you create the device file for. You must get these numbers correct when you use this command for it to work, so don’t just use 243 and 0 because we say so!

Once your dev file is created, you can find it stored in the /dev/ directory. Use the cd command to get there and check it out to make sure it’s there. You can try writing a single character ‘a’ to the dev file by writing echo a > /dev/lcd. Eventually you will ​ ​ want to make a series of testing code that uses your LCD code from user space, but using echo for now saves you time in your debugging process. ​ ​

Once you have your device file created and are able to write to the screen, append the option to clear the screen when a specific character is written to the file. Finally, add the option to print an entire string to the LCD rather than a single character.

Part 3 Advanced display You now have enough knowledge to program anything this piece of hardware offers. Page 34 of the data sheet shows additional functionalities allowed by the LCD. Some functions you may want to include into your driver are: cursor/display shift, set DD RAM address, or write DD RAM. How you incorporate them is entirely up to you. You want to make the interaction with your dev file as intuitive and unbreakable as possible. Try imagining that you are creating the official driver for this LCD screen, and people around the world will be installing and using it on their machines!

The extra features you include bears the most weight to your grade. Because this class is curved, the amount of extra features added will be compared based on implementation and complexity with other groups in this class. You are given an unlimited amount of breathing room to add any additional hardware or complex to the projects in this class, so feel free to influence your project with a lot of creativity. Most importantly, try not to feel pressure about the curved portion of the class and have fun! One of the best aspects to being an embedded engineer is your flashy projects that you can show off to your friends.

How you decide to implement your screen is entirely up to you. Will you limit your display to two lines? Do you want to allow the user to shift the screen right or left? What happens if someone tries to write 1000 characters to your screen? What other options do you want your screen to have? How will you store the data within your dev file? You can use any number of fields you like. Your grade may vary largely depending on the complexity and robustness of your code.

Additionally, you aren't required to print the exact text someone sends to the file. If you want to parse the string that is sent to the file and print part of it, or maybe send it through some encryption and change it, these are acceptable as well. One cool example of this is by writing the world "hello" and having the screen print "hola". Making a translator might be difficult though, so we don’t recommend that for your first character device, but it’s a neat idea. Harder and more ambitious interfaces with the LCD will be graded with less concern for edge case checking.

User space code For the final turn in you will also include user space code that tests a series of strings, or any other functionalities you may have in the final working version. You will do this in the same way you wrote code for lab 0. Now that you have practice writing in userspace and kernel space, the interaction you had with dev files before hopefully makes more sense! It’s highly recommended you create a function that will test any given command or string that someone wishes to write to your LCD dev file and call that function multiple times inside main with various test strings. This will be part of the demo you show to us, so the quality of your test code can mean a lot.

Header files Code clarity is sometimes as important as working code. In any C project, it is standard you include a header file to go along with any C file. Within the header, you will wrap all of your include statements, preprocessor macros, function prototypes, and global variables into a single file. A sample header is given that should cover most of the style expectations we have for this class. If your header file is written well, it should read like a table of contents to your C code­­ and this will be the goal you will strive for. Appropriate naming conventions and header guards are also important, so don’t forget those! While we don’t cover header files very deeply in this class, most of the content should be evident in the example file we provide. Additional questions about header files are most likely answerable through quick googling, but don’t hesitate to email or ask your TA questions.

What to turn in Create a new repository on git and push the following files: README.md // simple overview to your code ​ lcd.c // your kmodule ​ lcd.h lcd_uspace.c // your user space test code ​ lcd_uspace.h Makefile report.pdf // please don’t turn in any doc or docx. ​

All of the examples for this lab used the name lcd.c as the main kernel file . The names ​ of the files you create will be completely up to you, but remember the naming advice given in lab 0.

You should type make clean before pushing anything to the repository to be graded. ​ ​ We want to be able to type make ourselves and look through the source code directly. ​ Additional scripts are allowed as well, such as a file that makes and uploads the file to the Phyboard.

Comment and style do make a decent portion of the final grade. Appropriate use of header files, consistent style, appropriately segmenting code into methods, and concise comments all will be taken into consideration. All of the pins you use should be apart of preprocessor define statements. Tab size should be set to 3, and all tabs should be filled as spaces. This is an option available in just about every text editor and ensures that formatting remains consistent on every machine. Lines should not exceed 80 characters. Grading will not be as strict as CSE 14X courses, but will have similar guidelines for commenting expectations. Remember to include names and dates at the top of all documents!

All of the variables and function prototypes you include in your header should also be static. Unless stated otherwise, you can assume you only want them to exist within the scope of the containing file.

The readme and lab report files are intended to require no more than 1 hour of work. The readme will mostly explain how to use your code and any other basic functionalities a git user might be interested in knowing if they were to pull your code. The lab report is intended to purely contain diagrams and tables of wiring for hardware. You can imagine that it is a minimalistic datasheet. We do want to see a very clean and concise diagram of the wiring you did for each project though, so you should expect to turn in a report every lab. You are welcome to include any other text in the lab report. For this lab, you should include a diagram of all wiring in and out of the Phyboard and the LCD. Make sure to label any pins used, and any additional components such as resistors or diodes should be included in the diagram. A circuit­like diagram is the best example of what we are looking for, but you are given stylistic freedom to convey the information how you please. Only turn in .pdf files for the report!

Demo With every lab you will need to demo your working code. Pull the code from your repository and walk through the code with your TA. You can expect your TA to ask you difficult questions about design choices or even attempt to break your project by changing the test code. Make good arguments for your implementation of your device file.

Documentation C system call interface: http://codewiki.wikidot.com/system­calls ​ Linux Device Drivers: https://lwn.net/Kernel/LDD3/ ​