This article helps breakdown and explain the sections of the simple character device driver code.
For related subjects, please refer to the SOC Table of Contents.
First, we include the packages to be used.
#include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <asm/errno.h> #include <linux/io.h> #include <linux/of.h> #include <linux/of_device.h>
linux/kernel.h shows that we are doing kernel work, and allows for the printk() command to be used, which prints messages to the kernel log. printk() is used mainly as a logging mechanism for the kernel, which logs information and gives warnings.
linux/module.h shows that we are specifically working on a module with regard to kernel work. Modules are code segments that can be loaded and unloaded to the kernel on demand.
linux/fs.h defines the file structure of the device via the kernel. It will be used later in line 148 to add the driver to the system via the register_chrdev command.
linux/uaccess.h allows for the usage of the get_user and put_user commands. This includes the copy_to_user command in line 86 and the copy_from_user command in line 101.
asm/errno.h is necessary in coding the Linux Kernel.
linux/io.h and linux/of.h are C wrappers for input/output interfaces and macros for many other useful functions.
linux/of_device.h adds an optional input_id parameter, leaving current functionality unchanged.
/* 0x43c0_0000 + 4 = value 0x43c0_0000 + 8 = direction */ #define DEVICE_NAME "fii-module" #define CLASS_NAME "fii-module_class" static int major; static struct class *swled_class; static unsigned int *gpio_data; static unsigned int *gpio_dir ;
The above segment simply defines some definitions.
static int swled_open(struct inode *node,struct file *filp){ unsigned int gpio_base = (0x43c00000); gpio_data = ioremap(gpio_base,8); if(gpio_data){ printk("kernel: ioremap(0x%08x)=0x%08x \n",gpio_base,gpio_data); } else{ return -EINVAL; } gpio_dir = gpio_data + 1; printk("kernel: gpio_data address = 0x%08x \n", gpio_data); printk("kernel: gpio_dir address = 0x%08x \n", gpio_dir); return 0; }
Because the swled_open function controls a section of buffer memory, we will need to use the read and write functions to modify the buffer memory. This section serves to open the device.
ioremap maps the physical address of an input/output device to the kernel virtual address. Where the input is (physical_address, size). We can see that the physical address is gpio_base, and the size is 8.
*Note that Linux remaps the physical addresses of devices for easier management.
Because gpio_data is a pointer to an address, it will mean it loaded incorrectly if it has a value of 0. Thus, it will proceed to throw -EINVAL.
Note that -EINVAL is a macro for “Invalid Argument”.
The next two printk lines help log the data.
int swled_release(struct inode *inode, struct file *filp) { // MOD_DEC_USE_COUNT; iounmap(gpio_data); return 0; }
The above section serves to close and release the device. iounmap is the opposite of ioremap. While ioremap maps physical addresses, iounmap destroys it.
static ssize_t swled_read(struct file *filp, char *buf, size_t size, loff_t *offset) { unsigned char val[32]; unsigned int temp; unsigned int *addr_p; int i = 0, cnt; printk("kernel: swled_read start.... size = %d\n", size); cnt = size; addr_p = gpio_data; temp = *addr_p ; for(i = 0; i < cnt/4; i++ ) { val[i*4 + 3] = temp & 0xff; val[i*4 + 2] = (temp >> 8) & 0xff; val[i*4 + 1] = (temp >> 16) & 0xff; val[i*4 + 0] = (temp >> 24) & 0xff; addr_p ++; temp = *addr_p; } if(i % 4 == 1) { val[i*4 + 0] = temp & 0xff; } if(i % 4 == 2) { val[i*4 + 1] = temp & 0xff; val[i*4 + 0] = (temp >> 8) & 0xff; } if(i % 4 == 3) { val[i*4 + 2] = temp & 0xff; val[i*4 + 1] = (temp >> 8) & 0xff; val[i*4 + 0] = (temp >> 16) & 0xff; } copy_to_user(buf, val, cnt); return cnt; }
The above section serves to read from the device. It is taking the information stored in temp, which is the address of gpio_data, and storing it in the val array. Note that the copy_to_user function at the end has the parameters (destination, source, size). Where the function will copy size number of bytes pointed at by source, which much exist in kernel-space, to destination, which must exist in user-space.
The main part of this section is lines 60 to 84. It breaks up the data into 8-bit chunks, and stores the chunks in reverse order, which allows for it to be in normal order when it is eventually read. Note that & 0xff serves as a mask which only leaves the right-most 8 bits. If the size is not a perfect multiple of 4, it will simply append the extras to the end.
static ssize_t swled_write(struct file * filp, const char __user *buf, size_t size, loff_t * offset) { unsigned char val[32]; unsigned int temp = 0; unsigned int * addr_p; int i,cnt; memset(val,0,32); addr_p = gpio_data; printk("kernel: swled_write start.... size = %d\n", size); copy_from_user(&val, buf, size); cnt = size - 1; printk("kernel: val[0] = 0x%08x \n", val[0]); if(val[0] == 'w') { temp = val[2]; temp = temp << 8 | val[3]; temp = temp << 8 | val[4]; temp = temp << 8 | val[5]; *addr_p = temp; printk("kernel: gpio_data = 0x%08x \n", temp); } else if(val[0] == 'd') { addr_p ++; temp = val[2]; temp = temp << 8 | val[3]; temp = temp << 8 | val[4]; temp = temp << 8 | val[5]; *addr_p = temp; printk("kernel: gpio_dir = 0x%08x \n", temp); } else { printk("kernel: invalid parameter \n"); } return size; }
The above section focuses on writing to the device. Making use of the copy_from_user command, which also has the parameters (destination, source, size). Where the function will copy size number of bytes pointed at by source, which much exist in user-space, to destination, which must exist in kernel-space.
The section also deals with taking in the inputs from the application, in the case of the ‘dir’ ‘wr’ and ‘rd’ commands. It takes in the first character to determine which command it is and prepares the data, as well as writes data to the log file accordingly.
static struct file_operations swled_oprs = { .owner = THIS_MODULE, .open = swled_open, .write = swled_write, .read = swled_read, .release= swled_release, };
The above section deals with the file_operations structure, which is defined in linux/fs.h, and it holds various pointers to functions defined by the driver in order to perform operations on the device.
static int swled_init(void){ /* register device */ /* * * int register_chrdev (unsigned int major, * const char *name, * struct file_operations *fops); * * major => 0, automatic mode; others major device ID */ major=register_chrdev(0, DEVICE_NAME, &swled_oprs); if (major < 0) { printk ("Registering the character device failed with %d\n", major); return major; } swled_class = class_create(THIS_MODULE, CLASS_NAME); device_create(swled_class, NULL, MKDEV(major,0), NULL, DEVICE_NAME); return 0; }
The above section deals with driver entry functions. Char devices are accessed through device files, which is typically located in the /dev folder.
Registering the device with the kernel adds the driver to the system. Note that this is done using the register_chrdev function, which is defined in linux/fs.h.
Where unsigned int major is the major number to be requested, const char *name is the name of the device which will appear in /proc/devices and struct file_operations *fops is a pointer to the file_operations table for the driver. Note that if the function returns a negative value, it means that the registration failed.
static void swled_exit(void){ unregister_chrdev(major, DEVICE_NAME); device_destroy(swled_class,MKDEV(major,0)); class_destroy(swled_class); }; module_init(swled_init); module_exit(swled_exit); MODULE_LICENSE("GPL");
Here, the driver exit function is defined above. The device is removed from the system via unregister_chrdev and device_destroy. While class_destroy destroys the struct class structure along with pointers.
module_init() defines the function to be called during module insertion or upon boot. In this case the function is swled_init.
module_exit() defines the function to be called during module removal. Everything must be cleaned up when it returns. In this case the function is swled_exit.
MODULE_LICENSE(“GPL”) is used to suppress the warning saying the code is not open source.