Raspberry pie linux led character device driver (traditional)

Posted by ec on Tue, 14 Sep 2021 01:24:32 +0200

  for any peripheral driver under Linux, it is necessary to configure the corresponding hardware registers. Therefore, the LED driver finally configures the IO port of raspberry pie, but writing the driver under Linux should comply with the linux driver framework.

1, Raspberry pie GPIO

1.1 register address

The CPU of raspberry pie 3B is BCM2709 and the CPU of raspberry pie 4B is BCM2711. You can view the register address of GPIO by viewing the data manual of the chip. You can also view the allocation of raspberry pie GPIO addresses through instructions.

cat /proc/iomem

There are iomem and ioports files in the proc directory, which mainly describe the distribution of io memory and io port resources of the system. Linux designs a general data structure resource to describe various I/O resources (such as I/O port, peripheral memory, DMA, IRQ, etc.). This structure is defined in the include/linux/ioport.h header file. Linux manages each type of I/O resources with an inverted tree structure. Each type of I/O resource corresponds to an inverted resource tree, and each node in the tree is a resource structure. Based on the above idea, Linux refers to I/O port resources based on I/O mapping and I/O port resources based on memory mapping as "I/O Region"/ The proc/iomem file records the allocation of physical addresses. It is also displayed in a tree structure and used as a request_mem_region and ioremap

1.2 register parsing

The control GPIO is LED. The main concern is that the registers of GPIO are:
1. GPFSELn select register
2. GPSETn set register
3. GPCLRn clear register

2, Address mapping

In some old versions of Linux, the processor must have MMU, and the full name of MMU is called Memory
Manage Unit, that is, the memory management unit.. The main functions of MMU are as follows:
① Complete the mapping from virtual space to physical space.
② Memory protection, set the access rights of memory and set the buffer characteristics of virtual storage space
For a 32-bit processor, its limited physical memory can be mapped to the entire 4GB virtual space through the MMU. When the Linux kernel starts, it initializes the MMU and sets the memory mapping. After setting, the CPU accesses the virtual address. Two functions are required for access switching between virtual address (VA) and physical address (PA): ioremap and iounmap.
1. ioremap function
The ioremap function is used to obtain the virtual address space corresponding to the specified physical address space, which is defined in the arch/arm/include/asm/io.h file.
2. iounmap function
When unloading the driver, you need to use the iounmap function to release the mapping made by the ioremap function.

3, I/O memory access

After using the ioremap function to map the physical address of the register to the virtual address, we can directly access these addresses through the pointer. However, the Linux kernel does not recommend this, but recommends using a set of operation functions to read and write the mapped memory.
1. Read operation function
Read operation functions are as follows:

u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)

The readb, readw and readl functions correspond to 8bit, 16bit and 32bit read operations respectively. The parameter addr is to read the write memory address, and the return value is the read data.
2. Write operation function
Write operation functions are as follows:

void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

The writeb, writew and writel functions correspond to 8bit, 16bit and 32bit write operations respectively. The parameter value is the value to be written and addr is the address to be written.

4, Linux character device driver framework

4.1 introduction to character device driver

In Linux, everything is a file. After the driver is loaded successfully, a corresponding file will be generated in the "/ dev" directory. The application can operate the hardware by operating the file named "/ dev/xxx" (XXX is the specific driver file name).
The functions used by the application have corresponding functions in the specific driver. Each
Each system call has a corresponding driver function in the driver. In the Linux kernel file include/linux/fs.h, there is a function called file_operations structure, which is the collection of Linux kernel driver operation functions
In the development of Linux driver, the driver needs to be written according to its specified framework, so the focus of learning linux driver development is its driver framework.

4.2 loading and unloading of driver module

There are two operating modes of Linux driver. The first is to compile the driver into the Linux kernel, so that the driver will run automatically when the Linux kernel starts. The second is to compile the driver into a module (the module extension under Linux is. ko). After the Linux kernel is started, use the "insmod" command to load the driver module. The loading and unloading registration functions of the module are as follows:

module_init(xxx_init); //Register module load function
module_exit(xxx_exit); //Register module unload function

module_ The init function is used to register a module loading function with the Linux kernel. The parameter is xxx_init is the specific function that needs to be registered. When loading the driver, XXX_ The init function will be called. module_ The exit() function is used to register a module unloading function with the Linux kernel. The parameter is xxx_exit is the specific function that needs to be registered. When uninstalling the specific driver, XXX_ The exit function is called.
After the driver is compiled, the extension is. ko. The modprobe command will also check the dependency analysis, error checking, error reporting and other functions of the module when loading the driver.

4.3 character device registration and cancellation

For the character device driver, the character device needs to be registered after the driver module is loaded successfully. Similarly, the character device also needs to be logged off when the driver module is unloaded. The prototype of the registration and logout function of the character device is as follows:

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)

5, Programming

5.1. Write driver code

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_MAJOR 		 two hundred 		/*  Main equipment No*/
#define LED_NAME 		 "ledtest"  	/*  Device name*/

#define LEDOFF  	 0 				/*  Turn off the lights*/
#define LEDON  	 one 				/*  Turn on the light*/
 
#define LED_PIN					    26 //GPIO26 


  /* Register physical address */	
 
#define BCM2837_ GPFSEL2_ Base (0x3f200008) / / bits 20-18 of gpfsel2 register of GPIO control GPIO_ twenty-six  
#define BCM2837_ GPSET0_ Base (0x3f20001c) / / bit26 of gpset0 of GPIO controls GPIO_26 set
#define BCM2837_ GPCLR0_ Base (0x3f200028) / / bit25 of gpclr0 of GPIO controls GPIO_26 clear
 
/* Mapped register virtual address pointer */
static void __iomem *BCM2837_GPFSEL2;
static void __iomem *BCM2837_GPSET0;
static void __iomem *BCM2837_GPCLR0;


/*
 * @description		: LED On / off
 * @param - sta 	: LEDON(0) Turn on the LED and LEDOFF(1) turns off the LED
 * @return 			: nothing
 */
void led_switch(u8 sta)
{

	if(sta == LEDON) {
		writel(1<<(LED_PIN), BCM2837_GPCLR0);
	}else if(sta == LEDOFF) {
		writel(1<<(LED_PIN), BCM2837_GPSET0);
	}
}

/*
 * @description		: open device
 * @param - inode 	: inode passed to driver
 * @param - filp 	: The device file has a file structure called private_ Member variable of data
 * 					  Generally, private is used when open ing_ Data points to the device structure.
 * @return 			: 0 success; Other failures
 */
static int led_open(struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * @description		: Read data from device 
 * @param - filp 	: Device file to open (file descriptor)
 * @param - buf 	: Data buffer returned to user space
 * @param - cnt 	: Length of data to read
 * @param - offt 	: Offset relative to the first address of the file
 * @return 			: The number of bytes read. If it is negative, it indicates that the read failed
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: Write data to device 
 * @param - filp 	: A device file that represents an open file descriptor
 * @param - buf 	: Data to write to device
 * @param - cnt 	: Length of data to write
 * @param - offt 	: Offset relative to the first address of the file
 * @return 			: The number of bytes written. If it is negative, it indicates that the write failed
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* Get status value */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* Turn on the LED */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* Turn off the LED */
	}
	return 0;
}

/*
 * @description		: Turn off / release the device
 * @param - filp 	: Device file to close (file descriptor)
 * @return 			: 0 success; Other failures
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* Device operation function */
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: Drive exit function
 * @param 		: nothing
 * @return 		: nothing
 */
static int __init led_init(void)
{
	int retvalue = 0;
	u32 val = 0;

	/* Initialize LED */
	/* 1,Register address mapping */
	BCM2837_GPFSEL2 = ioremap(BCM2837_GPFSEL2_BASE,4);  
	BCM2837_GPSET0 = ioremap(BCM2837_GPSET0_BASE,4); 
	BCM2837_GPCLR0 = ioremap(BCM2837_GPCLR0_BASE,4); 

	/*2,Initialize pin function and output high level at the same time*/
	val = readl(BCM2837_GPFSEL2);
	val = val | (1<<((LED_PIN % 10) * 3));
	writel(val, BCM2837_GPFSEL2);
	writel(1<<(LED_PIN), BCM2837_GPSET0);


	/* 3,Register character device driver */
	retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
	if(retvalue < 0){
		printk("register chrdev failed!\r\n");
		return -EIO;
	}
	return 0;
}

/*
 * @description	: Drive exit function
 * @param 		: nothing
 * @return 		: nothing
 */
static void __exit led_exit(void)
{
	/* Unmap */

	iounmap(BCM2837_GPFSEL2);  
	iounmap(BCM2837_GPSET0);  
	iounmap(BCM2837_GPCLR0); 

	/* Unregister character device driver */
	unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zsx");

5.2. Write test code

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"


#define LEDOFF 	0
#define LEDON 	1

/*
 * @description		: main main program
 * @param - argc 	: argv Number of array elements
 * @param - argv 	: Specific parameters
 * @return 			: 0 success; Other failures
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* Turn on the led driver */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	databuf[0] = atoi(argv[2]);	/* What to do: turn on or off */

	/* Write data to / dev/led file */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* Close file */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

5.3. Write makefile

KERNELDIR := /home/linux/
CURRENT_PATH := $(shell pwd)

obj-m := led.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

6, Run test

Compile the driver by executing make, and compile the test APP by the command gcc ledtest.c -o ledtest. Copy the compiled led.ko and ledtest files to the / lib/modules/5.10.17-v7l + / directory, and enter the following command to load the led.ko driver module:

sudo depmod //This command needs to be run when the driver is loaded for the first time
sudo modprobe ledtest//Load driver
dmesg //View module print information

After the driver is loaded successfully, create the "/ dev/led" device node. The command is as follows:
mknod /dev/ledtest c 200 0
After the driver node is successfully created, you can use ledtest software to test whether the driver works normally. Enter the following command to turn on the LED:

./ledtest /dev/led 1 //Turn on the LED

Turn off the LED after entering the following command:

./ledtest /dev/led 0 //Turn off the LED

Topics: Linux Linux Driver