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 -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 <----------- ---------- Contents of this time __ -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/09_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/09_02
In the 4th installment of this series, I implemented a GPIO device driver for Raspberry Pi. At that time, control was performed by directly hitting the register. The register address and set value were set while looking at the data sheet of BCM2835. These are "chip-dependent" information. When creating device drivers to control external devices such as sensors and motors, I don't want to look at the chip datasheets one by one. There is a function for GPIO control, so let's use it.
In connection with this, I will try to call the functions defined in other kernel modules first.
As mentioned earlier in 4th: Read / write implementation and memory story, the kernel has one memory space in total. Share This also includes kernel modules. Therefore, you can call the functions of other kernel modules from the kernel module you have implemented, or you can call the functions that are statically built into the kernel itself.
To create a function that can be called from other modules, simply define the function and then export it with ʻEXPORT_SYMBOL. ʻEXPORT_SYMBOL
registers the function in the kernel's symbol table so that it can be called by other kernel modules.
Create a module that defines only the entry functions for loading (insmod) and unloading (rmmod) and the function (mydevicea_func ()
). Let's call this MyDeviceDriverA. Originally, the function declaration should be described in the header, but it is troublesome, so I will omit it.
make
to make MyDeviceDriverA.ko.
myDeviceDriverA.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
/***Information about this device***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDeviceA" /* /proc/Device name displayed on devices etc.*/
void mydevicea_func(void)
{
printk("This is a message in mydevicea_func\n");
}
/*Register in the kernel symbol table. Make it callable from other kernel modules*/
EXPORT_SYMBOL(mydevicea_func);
/*Road(insmod)Functions sometimes called*/
static int mydevicea_init(void)
{
printk("[A]: mydevicea_init\n");
mydevicea_func();
return 0;
}
/*Unload(rmmod)Functions sometimes called*/
static void mydevicea_exit(void)
{
printk("[A]: mydevicea_exit\n");
}
module_init(mydevicea_init);
module_exit(mydevicea_exit);
Create kernel module B that calls the function prepared earlier. You can call it like normal C language. Since the function declaration header is omitted this time, it is declared on the caller side with extern. I'm not very well-behaved. At the timing of loading (insmod) this module B, let's call the previous function (mydevicea_func ()
).
make
to make MyDeviceDriverB.ko. The actual state of mydevicea_func ()
is not here, but there is no problem when creating a kernel module. This is because make for kernel modules only compiles and creates object files, not links.
myDeviceDriverB.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
/***Information about this device***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDeviceB" /* /proc/Device name displayed on devices etc.*/
/*Road(insmod)Functions sometimes called*/
static int mydeviceb_init(void)
{
printk("[B]: mydeviceb_init\n");
extern void mydevicea_func(void);
mydevicea_func();
return 0;
}
/*Unload(rmmod)Functions sometimes called*/
static void mydeviceb_exit(void)
{
printk("[B]: mydeviceb_exit\n");
}
module_init(mydeviceb_init);
module_exit(mydeviceb_exit);
First, load MyDeviceDriverA.ko.
sudo insmod MyDeviceModuleA.ko
dmesg
[16909.979207] [A]: mydevicea_init
[16909.979223] This is a message in mydevicea_func
After loading, if you look at the log with dmesg, you can see that the init process of module A and the function was called in init. I don't think this is a problem.
Then load MyDeviceDriverB.ko.
sudo insmod MyDeviceModuleB.ko
dmesg
[17087.119434] [B]: mydeviceb_init
[17087.119449] This is a message in mydevicea_func
Then, you can see that module B can also call the function defined in module A in this way.
The dependency is that module B is using module A. Therefore, module A must be loaded before loading module B. Otherwise, an error will occur when loading module B. Similarly, Module B must be unloaded before Module A can be unloaded. If you try to unload module A first, you will get an error.
If you implement the necessary contents properly and place the created kernel module (.ko) in the appropriate place, you can use modprobe
instead of ʻinsmod` to automatically load the dependent modules as well. It seems that they will do it.
As mentioned at the beginning, as long as you make device drivers for external devices and onboard devices, I don't think you will do register settings by looking at the chip data sheet. If you are an engineer of an SoC maker, want to expand the functions, or if you volunteer to develop chip-dependent devices, you will need it. (So it can't be completely irrelevant. There may be bugs.)
When controlling GPIO from a device driver, we call the functions created by those people. At this time, everyone is not implementing it in a disjointed format, but implementing it so that it has an interface like the one in linux / gpio.h
. Therefore, the user (although the driver developer) can control GPIO using the functions in linux / gpio.h
. You can use the same code on another chip if you use the functions here. (Looking at the documentation, it says something like "GPIO has a wide range of functions, so follow linux / gpio.h
as much as possible. "Therefore, it may differ depending on the chip used. There is.)
Which chip to use GPIO control processing is determined by the setting at the time of kernel build. In the case of Raspberry Pi, you should be using the process for bcm2835. GPIO processing for BCM2835 was in pinctrl-bcm2835.c
. I haven't followed it deeply, but I think that calling the function in linux / gpio.h
will eventually lead to each process of pinctrl-bcm2835.c
. (In addition to the basic control of GPIO, there is also MUX control as a function pin, so it seems to be quite complicated.)
__ Anyway, you can control GPIO using the function in linux / gpio.h
__
This is a function required for basic GPIO control.
int gpio_direction_output(unsigned gpio, int value)
--Set GPIO to output. gpio is the pin number. value is the initial output value (0 = low, 1 = high)int gpio_direction_input(unsigned gpio)
--Set GPIO as input. gpio is the pin numbervoid gpio_set_value(unsigned gpio, int value)
--Output to GPIO. gpio is the pin number. value is the output value (0 = low, 1 = high)int gpio_to_irq(unsigned gpio)
--Get the interrupt number of the pin specified by GPIOint request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
--Register an interrupt handler. This is not a GPIO-specific functionSince it is troublesome to implement read / write, create a device driver (kernel module) with the following simple specifications. As a premise, let's assume that you have connected the LED to GPIO4 of Raspberry Pi and the button to GPIO17. LED is connected to 3.3V via a resistor. The button is connected to GND, and the GPIO17 side is pulled up. If it is troublesome, you can see the output of GPIO4 with a tester, or the input of GPIO17 can be directly connected to 3.3V / GND.
Specifications of the kernel module to be created
--When loading modules (insmod) --Set GPIO4 (LED) to output and output Low --Set GPIO17 (button) as input and register an interrupt handler --When unloading a module (rmmod) --Remove the registered interrupt handler --In the interrupt handler, --Printk the input value of GPIO17 (button)
The code looks like this: The interrupt handler will be mydevice_gpio_intr ()
. This is registered with request_irq ()
at load time.
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/gpio.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
/***Information about this device***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/Device name displayed on devices etc.*/
#define GPIO_PIN_LED 4
#define GPIO_PIN_BTN 17
static irqreturn_t mydevice_gpio_intr(int irq, void *dev_id)
{
printk("mydevice_gpio_intr\n");
int btn;
btn = gpio_get_value(GPIO_PIN_BTN);
printk("button = %d\n", btn);
return IRQ_HANDLED;
}
/*Road(insmod)Functions sometimes called*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
/*Output GPIO4 for LED. The initial value is 1(High) */
gpio_direction_output(GPIO_PIN_LED, 1);
/*0 to GPIO4 for LED(Low)To output*/
gpio_set_value(GPIO_PIN_LED, 0);
/*Input GPIO17 for button*/
gpio_direction_input(GPIO_PIN_BTN);
/*Get the GPIO17 interrupt number for the button*/
int irq = gpio_to_irq(GPIO_PIN_BTN);
printk("gpio_to_irq = %d\n", irq);
/*Register the GPIO17 interrupt handler for the button*/
if (request_irq(irq, (void*)mydevice_gpio_intr, IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "mydevice_gpio_intr", (void*)mydevice_gpio_intr) < 0) {
printk(KERN_ERR "request_irq\n");
return -1;
}
return 0;
}
/*Unload(rmmod)Functions sometimes called*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
int irq = gpio_to_irq(GPIO_PIN_BTN);
free_irq(irq, (void*)mydevice_gpio_intr);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Build and load as follows.
make
sudo insmod MyDeviceModule.ko
The LED should light up. After that, press the button several times or try connecting GPIO17 to 3.3V / GND.
dmesg
[19652.388837] mydevice_init
[19652.388873] gpio_to_irq = 183
[19654.100437] mydevice_gpio_intr
[19654.100457] button = 0
[19656.061705] mydevice_gpio_intr
[19656.061727] button = 1
If you look at the log with dmesg, you can see that the registered interrupt handler is called and the GPIO input value is printed in it. By the way, you can check the interrupt status in / proc / interrupts
.
cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
183: 7 0 0 0 pinctrl-bcm2835 17 Edge mydevice_gpio_intr
There seems to be no general way to set it from code (https://raspberrypi.stackexchange.com/questions/44924/how-to-set-pull-up-down-resistors-in-a-kernel-module). It seems to be set in the device tree. Looking at brcm, bcm2835-gpio.txt, it seems that it is set using brcm, pull
in the dts file.
Recommended Posts