This is a HowTo article for developing embedded Linux device drivers as kernel modules. All the content of this article can be run on a Raspberry Pi.
-1st time: Build environment preparation and simple kernel module creation -Second time: System call handler and driver registration (static method) -Third time: System call handler and driver registration (dynamic method) -4th: Read / write implementation and memory story --__ 5th time: Implementation of GPIO driver for Raspberry Pi <------------------ --- Contents of this time __ -6th time: Implementation of ioctl -7th time: Interface for procfs -8th time: Interface for debugfs -9th time: Call a function of another kernel module / Use GPIO control function -10th time: Create a device driver using I2C -11th time: Add I2C device to device tree -12th time: Load the created device driver at startup
https://github.com/take-iwiw/DeviceDriverLesson/tree/master/05_01
Up to the last time, we have learned how to implement system calls (open, close, read, write) that perform basic operations with the device driver. This time, we will implement the GPIO device driver for Raspberry Pi using the contents so far.
Since the purpose is not to make a solid GPIO driver, for the sake of simplicity, we will only target GPIO4 and always use output mode. Output High / Low with write. Read returns the current output level as a string of "1" / "0".
Last time, I explained the memory of Raspberry Pi. In Raspberry Pi 2, the physical address of the peripheral register seen from ARM (CPU) is 0x3F000000. This seems to have been 0x20000000 for the original Raspberry Pi. In fact, looking at the memory map of the BCM2835 Datasheet, I / O Peripherals says 0x20000000. It has been. As introduced last time, you can get this address with bcm_host_get_peripheral_address
. However, for the sake of simplicity, we will fix it at 0x3F000000 this time. If Raspberry Pi 4 or 5 comes out in the future, this address may change.
As mentioned above, we know that the physical address of the peripheral register starts at 0x3F000000. You can also find out which register to hit specifically for GPIO control in BCM2835 Datasheet. It is listed. At this time, there is one caveat in how to read this data sheet. This data sheet contains a description of each register, as well as an address for each. However, that address will be a bus address starting with 0x7E000000. What we need is the physical address as seen by the CPU. We already know that the start address is 0x3F000000, so be sure to look only at the offset. For example, GPFSEL0 (GPIO Function Select 0) is 0x7E200000 in the data sheet, but the physical address seen from the CPU is 0x3F200000 (in the case of Raspberry Pi 2).
By the way, the registers used this time are as follows.
I've made kernel modules so far, but in fact, register access is possible from user space. This time I just hit the GPIO control register, so I will implement it with a program on the user space for the time being. If this works, we will start implementing the device driver on the kernel module side later. Trying it in user space in advance has the advantage of being easier to debug.
To access a physical address from a userspace program, first open / dev / mem
. Then use that file descriptor (fd) to mmap. I think it's okay to use only the size, but it seems that there are many cases where the page size (4KByte) is secured. In addition, ʻO_SYNC` is specified when opening. This invalidates the cache and provides immediate register access. (In the first place, I think that the cache is not used because it is a register, but just in case)
The code looks like this: You can access the register (0x3F000000) by reading and writing to the virtual address acquired by mmap. The explanation of the value set in each register is omitted. Please check the data sheet. Basically, it only sets and inputs / outputs values to GPIO4.
userGpio.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
/*Physical address of peripheral register(From the BCM2835 specifications) */
#define REG_ADDR_BASE (0x3F000000) /* bcm_host_get_peripheral_address()Is better*/
#define REG_ADDR_GPIO_BASE (REG_ADDR_BASE + 0x00200000)
#define REG_ADDR_GPIO_LENGTH 4096
#define REG_ADDR_GPIO_GPFSEL_0 0x0000
#define REG_ADDR_GPIO_OUTPUT_SET_0 0x001C
#define REG_ADDR_GPIO_OUTPUT_CLR_0 0x0028
#define REG_ADDR_GPIO_LEVEL_0 0x0034
#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printf("%08X\n", REG(addr));
int main()
{
int address; /*Virtual address to GPIO register(User space) */
int fd;
/*Open device file for memory access*/
if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
perror("open");
return -1;
}
/* ARM(CPU)Physical address seen from → Mapping to virtual address*/
address = (int)mmap(NULL, REG_ADDR_GPIO_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, REG_ADDR_GPIO_BASE);
if (address == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}
/*Set GPIO4 to output*/
REG(address + REG_ADDR_GPIO_GPFSEL_0) = 1 << 12;
/*High output of GPIO4*/
REG(address + REG_ADDR_GPIO_OUTPUT_SET_0) = 1 << 4;
DUMP_REG(address + REG_ADDR_GPIO_LEVEL_0);
/*Low output of GPIO4*/
REG(address + REG_ADDR_GPIO_OUTPUT_CLR_0) = 1 << 4;
DUMP_REG(address + REG_ADDR_GPIO_LEVEL_0);
/*Release used resources*/
munmap((void*)address, REG_ADDR_GPIO_LENGTH);
close(fd);
return 0;
}
Connect the GPIO4 pin (the pin to the right of the SCL) to 3.3V via the LED and resistor. Build and execute with the following command.
gcc userGpio.c
sudo ./a.out
As you can see from the code, the LED lights up because GPIO4 is output low at the end. If you comment out this line, GPIO4 will be output High and the LED will turn off.
Finally, make a real device driver. As mentioned at the beginning, this time we will use a very simple specification for simplicity.
In kernel space, use ʻioremap_nocache` to translate physical addresses to virtual addresses (non-cached). Regarding this address translation, I wanted to translate a large area (for example, 4KByte) at once when loading or opening the kernel module, but the kernel space has only one virtual address space as a whole. So, I wondered if only one module could occupy so much, so I decided to convert 4 bytes each time I accessed the register. Of course, it is disadvantageous in terms of speed. What do you usually do? If you have any knowledge, please let me know. (In the case of kernel space, I feel that virtual addresses are not assigned, but just translated from physical addresses, so it may not be a waste of address space.)
After address translation, register access can be performed in the same way as a user space program. The code looks like this: The processing in mydevice_open
, mydevice_read
, mydevice_write
is important. Other than that, init and exit are the same as last time. I thought I'd omit it, but since it's a separate article, I'll post it all.
Regarding the exchange of data with the user space, I used copy_to_user
and copy_from_user
last time. Since the data to be exchanged is only 1Byte this time, I tried using put_user
and get_user
.
myDeviceDriver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*Physical address of peripheral register(From the BCM2835 specifications) */
#define REG_ADDR_BASE (0x3F000000) /* bcm_host_get_peripheral_address()Is better*/
#define REG_ADDR_GPIO_BASE (REG_ADDR_BASE + 0x00200000)
#define REG_ADDR_GPIO_GPFSEL_0 0x0000
#define REG_ADDR_GPIO_OUTPUT_SET_0 0x001C
#define REG_ADDR_GPIO_OUTPUT_CLR_0 0x0028
#define REG_ADDR_GPIO_LEVEL_0 0x0034
#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printk("%08X\n", REG(addr));
/***Information about this device***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/Device name displayed on devices etc.*/
static const unsigned int MINOR_BASE = 0; /*Starting number and number of minor numbers used in this device driver(=Number of devices) */
static const unsigned int MINOR_NUM = 1; /*Minor number is 0 only*/
static unsigned int mydevice_major; /*Major number of this device driver(Dynamically decide) */
static struct cdev mydevice_cdev; /*Character device object*/
static struct class *mydevice_class = NULL; /*Device driver class object*/
/*Function called at open*/
static int mydevice_open(struct inode *inode, struct file *file)
{
printk("mydevice_open");
/* ARM(CPU)Physical address seen from → Virtual address(Kernel space)Mapping to*/
int address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_GPFSEL_0, 4);
/*Set GPIO4 to output*/
REG(address) = 1 << 12;
iounmap((void*)address);
return 0;
}
/*Function called at close*/
static int mydevice_close(struct inode *inode, struct file *file)
{
printk("mydevice_close");
return 0;
}
/*Function called when reading*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_read");
/* ARM(CPU)Physical address seen from → Virtual address(Kernel space)Mapping to*/
int address = address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_LEVEL_0, 4);
int val = (REG(address) & (1 << 4)) != 0; /*0 if GPIO4 is 0,Set to 1*/
/*Returns the GPIO output value to the user as a character*/
put_user(val + '0', &buf[0]);
iounmap((void*)address);
return count;
}
/*Function called when writing*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_write");
int address;
char outValue;
/*Get the output value to GPIO set by the user*/
get_user(outValue, &buf[0]);
/* ARM(CPU)Physical address seen from → Virtual address(Kernel space)Mapping to*/
if(outValue == '1') {
/* '1'Then SET*/
address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_OUTPUT_SET_0, 4);
} else {
/* '0'Then CLR*/
address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_OUTPUT_CLR_0, 4);
}
REG(address) = 1 << 4;
iounmap((void*)address);
return count;
}
/*Handler table for various system calls*/
struct file_operations s_mydevice_fops = {
.open = mydevice_open,
.release = mydevice_close,
.read = mydevice_read,
.write = mydevice_write,
};
/*Road(insmod)Functions sometimes called*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/* 1.Secure a free major number*/
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
if (alloc_ret != 0) {
printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
return -1;
}
/* 2.Obtained dev( =Major number+Minor number)Get the major number from and keep it*/
mydevice_major = MAJOR(dev);
dev = MKDEV(mydevice_major, MINOR_BASE); /*Unnecessary? */
/* 3.Initialization of cdev structure and registration of system call handler table*/
cdev_init(&mydevice_cdev, &s_mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
/* 4.This device driver(cdev)In the kernel*/
cdev_err = cdev_add(&mydevice_cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", alloc_ret);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/* 5.Register the class of this device(/sys/class/mydevice/make) */
mydevice_class = class_create(THIS_MODULE, "mydevice");
if (IS_ERR(mydevice_class)) {
printk(KERN_ERR "class_create\n");
cdev_del(&mydevice_cdev);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/* 6. /sys/class/mydevice/mydevice*make*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(mydevice_class, NULL, MKDEV(mydevice_major, minor), NULL, "mydevice%d", minor);
}
return 0;
}
/*Unload(rmmod)Functions sometimes called*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
/* 7. /sys/class/mydevice/mydevice*To delete*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_destroy(mydevice_class, MKDEV(mydevice_major, minor));
}
/* 8.Remove class registration for this device(/sys/class/mydevice/To delete) */
class_destroy(mydevice_class);
/* 9.This device driver(cdev)From the kernel*/
cdev_del(&mydevice_cdev);
/* 10.Remove the major number registration used in this device driver*/
unregister_chrdev_region(dev, MINOR_NUM);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Build and embed it in the kernel as follows:
make
sudo insmod MyDeviceModule.ko
echo "0" > /dev/mydevice0
echo "1" > /dev/mydevice0
cat /dev/mydevice0
1111111111111111^C
Writing "0" or "1" with echo should change the output of GPIO4 and the LED should flicker. Also, reading with cat will display the output level at that time. However, it always returns a value, so you need to stop it with Ctrl-c.
It seems that you can check the physical memory map with the following command. If you look at this, you can see that it has the same content as we have seen so far. SDRAM is located from physical address 0. In the memory map below, it is 00000000-3b3fffff. 0x3b3fffff = 994050047 = About 1GByte, so it seems that we are actually meeting. The register address of Peri is also located at 0x3fXXXXXX.
sudo cat /proc/iomem
00000000-3b3fffff : System RAM
00008000-00afffff : Kernel code
00c00000-00d3da63 : Kernel data
3f006000-3f006fff : dwc_otg
3f007000-3f007eff : /soc/dma@7e007000
3f00b840-3f00b84e : /soc/vchiq
3f00b880-3f00b8bf : /soc/mailbox@7e00b880
3f101000-3f102fff : /soc/cprman@7e101000
3f200000-3f2000b3 : /soc/gpio@7e200000
3f201000-3f201fff : /soc/serial@7e201000
3f201000-3f201fff : /soc/serial@7e201000
3f202000-3f2020ff : /soc/sdhost@7e202000
3f215000-3f215007 : /soc/aux@0x7e215000
3f980000-3f98ffff : dwc_otg
Recommended Posts