Beaglebone Kernel Module Documentation by Wesley Cheng 1. Kernel 1.1 Kernel Source Tree 1.1.a Preparations 1.1.b Compiling the Kernel 1.2 Creating a Kernel Module 1.3 Makefile 1.4 Running the Kernel 1.5 Interacting with Kernel Module 1.5.a IOCTL information 1.5.b IOCTL code 1. Kernel In this section, we will discuss how to create a kernel module or character device on the Beaglebone. The objective for doing this was to obtain faster toggle rates on the Beaglebone compared to a user program. 1.1 Kernel Source Tree First we need to set-up the kernel source tree for the Beaglebone so that kernel modules can be compiled on the Beaglebone. This was chosen instead of cross-compiling on a different platform so that it would be easier to recompile after small changes. 1.1.a Preparations To do this, Angstrom has provided set-up scripts using a tool they call bitbake which supposedly simplifies things by taking different recipes to compile the kernel we want for whichever machine we are using it for. It is not recommended to get the kernel source and set it up in the Beaglebone since it requires a good amount of RAM and space. Instead, try using a virtual machine with a version of linux installed. This guide used an Ubuntu virtual machine with 2 GB of RAM and 20 GB of storage. Also, make sure an internet connection is set up and the files in the update manager are up to date. After that is set up, also make sure to have the following packages installed by using sudo apt-get install: sudo apt-get install build-essential sudo apt-get install diffstat sudo apt-get install texi2html sudo apt-get install texinfo sudo apt-get install gawk sudo apt-get install chrpath sudo apt-get installl zop sudo apt-get install python-psyco sudo apt-get install subversion sudo apt-get install git 1.1.b Compiling the Kernel Then get the setup-scripts with the following command in a terminal: git clone git://github.com/Angstrom-distribution/setup-scripts.git After that is done, change directories into setup-scripts and run the following commands. Though the systemd-image is probably not necessary. ./oebb.sh update MACHINE=beaglebone ./oebb.sh config beaglebone MACHINE=beaglebone ./oebb.sh update MACHINE=beaglebone ./oebb.sh bitbake virtual/kernel MACHINE=beaglebone ./oebb.sh systemd-image MACHINE=beaglebone ./oebb.sh bitbake virtual/kernel -c compile -f After those steps have been done, the kernel source will be found in setup-scripts at: /build/tmp-angstrom_vYYYY_MM-eglibc/work/beaglebone-angstrom-linux-gnueabi/linux-ti33x-psp-X.X-rXXX-gitreNNN....NN/git/ where YYYY and MM are a datestamp, X.X rXXX is a Linux revision number, and NNN....NN is a long cryptographic hash. To be able to compile modules on the beaglebone, the kernel must be transferred over. This can be done using the scp command and the folder can also be compressed if desired. Just make sure to uncompress it on the beaglebone. scp -r user@host:directory/SourceFolder TargetFolder where host is the ip address of the beaglebone which you can find with ifconfig. Then on the beaglebone, move the folder to /usr/src/ and rename it to linux-X.X.XX depending on the version number which can be found in the Makefile. After this is done, in the kernel folder run the following commands: make modules make scripts After following these steps your Beaglebone is now ready to compile device drivers! 1.2 Creating a Kernel Module This section shall go over how to make a kernel module. For example purposes, we will be making a kernel module with the name myModule. To create a kernel module, we will write some C code and that code will be compiled to create a module. First, there are a few different include files and functions that should be used. Make sure to include the following basic lines: #include #include #include MODULE_LICENSE("Dual BSD/GPL"); The include files shall provide the right sources to make the module and the MODULE_LICENSE function gives the module an identifier used by the kernel. Then two functions should be written to specify what we want to do when the module is loaded and released. The init function should be of type static int (void) and the exit function should be of type static void (void). Then the two functions should be assigned to the macros module_init(init function) and module_exit(exit function). Here is an example: static int myModule_init(void) {} static void myModule_exit(void) {} module_init(myModule_init); module_exit(myModule_exit); Note that we used myModule in the naming system but any names can be chosen as long as the init and exit functions are passed as parameters in module_init and module_exit. These are just some of the basics of some Kernel code and we will go into a bit more detail on specifics in 1.5 Interacting with Kernel Module. 1.3 Makefile After the kernel code is finished, a makefile is needed for compilation. The basic makefile just needs to have: obj-m := myModule.o where myModule is the name of the compiled c kernel code. After the makefile is created with the name Makefile, you can compile your module with the following command: make -C /usr/src/linux-X.X.XX M=pwd modules where X.X.XX is the version number that you renamed it as and pwd is the working directory where your Makefile and code is. The C option changes to the directory where the kernel source tree is set-up and the M= option specifies the target of where your module is. 1.4 Running the Kernel After the kernel module has been compiled through the make command, a program target.ko will be created. That program can be inserted to the kernel with the command: insmod myModule.ko Also, you can view the current modules running and remove modules by the commands: lsmod rmmod myModule.ko More interactions with the running kernel are mentioned in the next section. 1.5 Interacting with Kernel Module Now that we are able to insert kernel modules we may also want to interact with them with a user program. This documentation used the ioctl, I/O control commands, to interact with the kernel module. 1.5.a IOCTL information In user space it has the form: int ioctl(int fd, unsigned long cmd, ...); where fd is the file descriptor, cmd is the command, and the ... is an optional argument In kernel space it has the form: int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); though since recent linux versions, it is now: long (*unlocked_ioctl) (struct file *filp, unsigned cmd, unsigned long arg); the cmd is interpreted through a switch case doing something different with filp and arg based on the command. It should be assigned a unique number to ensure it does the right command to the right device. To assign unique numbers, a "magic number" which represents the first eight bits to correspond to a device is used while sequential numbers are used for the next eight. The magic number can also be the same as the major number of the char device. To choose the number, information can be found in: include/asm/ioctl.h and Documentation/ioctl-number.txt More information about the commands is also found here 8 bits type 0xFF _IOC_TYPEBITS 8 bits number 00-FF _IOC_NRBITS 2 bits direction _IOC_NONE, _IOC_READ, _IOC_WRITE, and _IOC_READ|_IOC_WRITE 14 bits size _IOC_SIZEBITS The macros used to help assign unique numbers given the magic number and sequential number are _IO an ioctl with no parameters _IO(type,nr) _IOW an ioctl with write parameters (copy_from_user) _IOR(type,nr,datatype) _IOR an ioctl with read parameters (copy_to_user) _IOW(type,nr,datatype) _IOWR an ioctl with both write and read parameters. _IOWR(type,nr,datatype) where type is the magic/major number, nr is the command number, and datatype is the type of data being read/written. Datatype should be something like int or char instead of sizeof(int). First we must choose a major/magic number for the ioctl command number that does not conflict with other numbers. The number 60 was chosen here and I believe it does not conflict with the numbers in include/asm/ioctl.h and Documentation/ioctl-number.txt Then we can create a file in /dev for the char device and this is done by 'mknod /dev/myModule c 60 0' where gpio is the name of the device, c is for char device, 60 is the major number and 0 is the minor number. There are different type of kernel modules that can be created and we used a char module because our main goal was to see how fast the GPIO could toggle and did not need more complex usage. There are also block devices and network devices which this documentation does not go into. 1.5.b IOCTL Code In the header file we use the following macros to specify unique commands for ioctl: _IO, _IOW, _IOR, and _IOWR An example is: #define IOC_MAGIC 60 #define IOC_SET _IOR(IOC_MAGIC, 2, int) The headerfile should be included in both the kernel and user code. Next, for the kernel code, the structure file_operations should be added as such. The header file is required struct file_operations myModule_fops = { .read = myModule_read, .write = myModule_write, .unlocked_ioctl = myModule_ioctl, .open = myModule_open, .release = myModule_release }; Also the corresponding functions should be declared: int myModule_open(struct inode *inode, struct file *filp); int myModule_release(struct inode *inode, struct file *filp); ssize_t myModule_read(struct file *filp, char *buf, size_t count, loff_t *f_pos); ssize_t myModule_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos); long myModule_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); The file operations that were just created help the user program to interact with the kernel module. The kernel module is treated as a folder in /dev/myModule the commands open, release, read, and write interact with that folder. For example, we can do the following command: echo 1 > /dev/myModule and it should use the myModule_write function to write 1 to /dev/myModule. Since we are creating a char device, we must use the register_chrdev and unregister_chrdev to do so: register_chrdev(myModule_major, "myModule", &myModule_fops); unregister_chrdev(myModule_major, "myModule"); where myModule_major was defined as 60, the string is the name in /dev and myModule_fops were just defined. Now for the "long myModule_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)" function, a switch statement is used to parse the cmd based on the values from the header file. In each case, functions can be called to read, write or etc. An example of a switch statement in the function is switch (cmd) { case IOC_SET: myModule_read(filp, buf, count, f_pos); break; } After that has been done, a user program can be written to use the ioctl for the switch statement cases. Include and the functions can call ioctl through: int ioctl(int fd, unsigned long cmd, ...); or an example would be ioctl(fd, IOC_SET, arg); The fd can be obtained through open(); and the cmd is chosen from the header file. The ... can pass an argument such as a pointer or an int. To finally put everything together: compile the kernel module with make, compile the user code with gcc, insmod the kernel module, and then run the user code.