How to make an embedded Linux device driver (9)

9th time: Call a function of another kernel module / Use GPIO control function

About this series

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

The entire source code that appears in this article

https://github.com/take-iwiw/DeviceDriverLesson/tree/master/09_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/09_02

Contents of this time

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.

Call a function defined in another kernel module

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.

Create kernel module A that provides the function

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 provided function

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);

Try to run

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.

Dependencies

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.

Create a kernel module that uses GPIO control functions

Chip-dependent GPIO control function

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 __

GPIO control function

This is a function required for basic GPIO control.

Create a kernel module that uses GPIO control functions

Since 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);

Try to run

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

What are the pull-up / pull-down settings?

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

How to make an embedded Linux device driver (11)
How to make an embedded Linux device driver (8)
How to make an embedded Linux device driver (1)
How to make an embedded Linux device driver (4)
How to make an embedded Linux device driver (7)
How to make an embedded Linux device driver (2)
How to make an embedded Linux device driver (3)
How to make an embedded Linux device driver (6)
How to make an embedded Linux device driver (5)
How to make an embedded Linux device driver (10)
How to make an embedded Linux device driver (9)
How to make an embedded Linux device driver (12) (Complete)
[Blender x Python] How to make an animation
How to make Linux compatible with Japanese keyboard
How to make Yubico Yubikey recognized in Manjaro Linux
How to make an interactive CLI tool in Golang
How to make an HTTPS server with Go / Gin
[Python] How to make an adjacency matrix / adjacency list [Graph theory]
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit Part 2-
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit edition-
How to make a Python package (written for an intern)
How to create an ISO file (CD image) on Linux
How to make a Japanese-English translation
How to install wkhtmltopdf (Amazon Linux2)
How to create an email user
How to install VMware-Tools on Linux
How to make a crawler --Advanced
How to make a recursive function
How to install MBDyn (Linux Ubuntu)
How to make a deadman's switch
[Blender] How to make a Blender plugin
[Blender] How to make Blender scripts multilingual
How to make a crawler --Basic
How to build MongoDB C driver
How to check Linux OS version
How to make a string into an array or an array into a string in Python
How to get the printer driver for Oki Mac into Linux
How to make Word Cloud characters monochromatic
How to build my own Linux server
How to make an artificial intelligence LINE bot with Flask + LINE Messaging API
[Python] How to make a class iterable
python3 How to install an external module
How to create an NVIDIA Docker environment
How to convert Python to an exe file
I want to know how LINUX works!
[Linux] How to use the echo command
How to use the Linux grep command
How to update php on Amazon linux 2
How to display emoji on Manjaro Linux
How to install packages on Alpine Linux
[Cocos2d-x] How to make Script Binding (Part 2)
How to install Anisble on Amazon Linux 2
How to operate Linux from the console
How to install Windows Subsystem For Linux
How to update security on CentOS Linux 8
I want to make an automation program!
How to install php7.4 on Linux (Ubuntu)
How to make multi-boot USB (Windows 10 compatible)
[Cocos2d-x] How to make Script Binding (Part 1)
How to find large files on Linux
How to get started with laravel (Linux)