Linux Device Driver Development -- I/O port control

1, Knowledge reserve

Bus address: Address Bus (also known as Address Bus) belongs to a computer bus (part), which is used by CPU or DMA capable units to communicate the physical address of computer memory components / places that these units want to access (read / write). Simply put, it is the range of memory that the CPU can access.
For example, the system with 32-bit win7 clearly has 8G memory, but the system only recognizes 3.8G, and the system with 64 bit can recognize 8G, because the 32-bit operating system can only represent / access the 32nd power of 2 = 4294967296 addresses, and each address accesses one byte. Therefore, 4294967296 (i.e. the 32nd power of 2) addresses access the 32nd power of 2 bytes, i.e. 4 GB.
4,294,967,296 Byte=4,194,304 KB
4,194,304 KB=4,096 MB
4,096 MB=4 GB

Physical Address: information is stored in bytes in the memory. In order to correctly store or obtain information, each byte unit is given a unique memory address, which is called Physical Address, also known as actual address or absolute address.

Virtual address: the logical address used by the program to access the memory is called virtual address. Similar to the segment address in the real address mode, the virtual address can also be written in the form of "segment: offset". Here, the segment refers to the segment selector.
In raspberry pie Linux operating system, physical addresses are mapped into virtual addresses through page table (MMU) management.

2, Kernel function

1. ioremap function

Map an IO physical address space to the virtual address space of the kernel for easy access
#include <asm/io.h>
void * ioremap(unsigned long phys_addr, unsigned long size)
Parameters:
phys_addr: the starting IO physical address to map
Size: the size of the space to be mapped. 32 bits are 4 bytes.
Return: mapped virtual address

2. iounmap function

Unmap physical address
#include <asm/io.h>
void iounmap(unsigned long virtual_addr)
Parameters:
virtual_addr: mapped virtual address

3,copy_from_user function

#include <linux/uaccess.h>
static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
Parameters:
To: pointer to kernel space
from: user space pointer
n: Represents the number of bytes of data copied from user space to kernel space.
Return: if the copy operation is successful, return 0; otherwise, return the number of bytes that have not been copied.

4. volatile keyword

Volatile is a type modifier that reminds the compiler that the variables defined later may change at any time. Therefore, every time the compiled program needs to store or read this variable, it will directly read data from the variable address (memory). If there is no volatile keyword, the compiler may optimize reading and storage, and may temporarily use the value in the register (CACHE). If this variable is updated by another program, there will be inconsistency. The hardware register of memory mapping usually needs volatile description, because its reading and writing may be different each time;

3, Driver code writing

Raspberry pie I/O port operation is similar to STM32, which is also realized through register operation.
(refer to BCM2835 chip manual)
1. Registers related to I/O port operation:
(1) GPFSELn control I/O port mode: input / output

The mode control of pin4 I/O port corresponds to bits 12-14 in GPFSEL0 and is set to 001 as the output mode.

(2) GPSETn I/O port is set to high level: bit write 1 is valid and write 0 is invalid

pin4 set high level control corresponds to the 4th bit of GPSET0 and is set to 1.

(3) GPCLRn I/O port is set to low level: bit write 1 is valid and write 0 is invalid

pin4 set low level control corresponds to bit 4 of GPCLR0 and is set to 1.

Based on the basic framework of kernel driver in the previous section, the I/O port driver of pin4 is written to simply control the output high / low level of pin4. The following has been added to the framework:
(1) Three register pointers are defined. The physical address of the register is mapped to the virtual address through the ioremap function, and points to the virtual address of GPFSEL0, GPSET0 and GPCLR0 registers respectively.
(2) At pin4_ GPIO initialization is added to the open function, and pin4 is set to output mode.
(3) At pin4_ In the write function, copy is added_ from_ The user function copies the data sent from the user state, that is, the buf in the user state write function, and selects the control I/O port output high / low level through the judgment statement.
(4) In the driver exit function, unmap the physical address of the register.

//File pin4driver.c
#include <linux/fs.h>//file_ Operation statement
#include <linux/module.h>//module_ init module_ Exit declaration
#include <linux/init.h>//_ init _ Exit declaration
#Include < Linux / device. H > / / class device declaration
#include <linux/uaccess.h>//copy_ from_ Header file for user
#Include < Linux / types. H > / / device number dev_t type declaration
#Include < ASM / Io. H > / / ioremap iounmap header file

volatile unsigned int *GPFSEL0=NULL;
volatile unsigned int *GPSET0=NULL;
volatile unsigned int *GPCLR0=NULL;

static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno;//Equipment number
static int major=231;//Main equipment No
static int minor=0;//Secondary equipment No
static char *module_name="pin4";//Module name

static int pin4_open(struct inode *inode, struct file *file)
{
	printk("pin4_open\n");//The kernel print function printk is similar to the printf function
//GPIO initialization: configure pin4 pin to output mode
	*GPFSEL0&=~(0x06<<12);
	*GPFSEL0|=(0x01<<12);
	return 0;
}

static ssize_t pin4_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos)
{
	int usrcmd;
	printk("pin4_write\n");
	copy_from_user(&usrcmd,buf,count);//Copy the data sent from the user state write function to usrcmd
	if(usrcmd==1)
	{
		printk("set 1\n");
//pin4 pull high
		*GPSET0|=0x01<<4;
	}
	else if(usrcmd==0)
	{
		printk("set 0\n");
//pin4 pull low
		*GPCLR0|=0x01<<4;
	}
	else
	{
		printk("undo\n");
	}
	return 0;
}
static struct file_operations pin4_flops = 
{
	.owner  =   THIS_MODULE,
	.open   =   pin4_open,
	.write  =   pin4_write,
};
int __init pin4_drv_init(void)//Real drive entrance
{
	devno=MKDEV(major,minor);//Create device number
	register_chrdev(major, module_name, &pin4_flops);//Register the character device and tell the kernel to add the driver to the driver list
	pin4_class=class_create(THIS_MODULE,"myfirstdemo");//Create class
	pin4_class_dev=device_create(pin4_class,NULL,devno,NULL,module_name);//Create a device file and let the code automatically generate the device under dev
	GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);//The physical address is transformed into the virtual address, and the io port register is mapped into an ordinary memory unit for access
	GPSET0=(volatile unsigned int *)ioremap(0x3f20001C,4);//Mapping virtual addresses
	GPCLR0=(volatile unsigned int *)ioremap(0x3f200028,4);
	return 0;
}

void __exit pin4_drv_exit(void)
{
	iounmap(GPFSEL0);
	iounmap(GPSET0);
	iounmap(GPCLR0);
	device_destroy(pin4_class,devno);//Logout device
	class_destroy(pin4_class);//Logout class
	unregister_chrdev(major, module_name);//Unregister character device
}

module_init(pin4_drv_init);//This macro is called when the entry kernel loads the driver
module_exit(pin4_drv_exit);//This macro is called when the exit kernel unloads the driver
MODULE_LICENSE("GPL v2");//GPL v2 specification
//File pin4test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
    int fd; 
	int cmd;
    fd=open("/dev/pin4",O_RDWR);
	if(fd<0)
	{
		printf("load fail!\n");
		perror("why");
	}
	else
	{
		printf("load success!\n");
	}
	printf("please input 0/1\n");
	scanf("%d",&cmd);
	printf("cmd=%d\n",cmd);
	write(fd,&cmd,1);
    close(fd);
	return 0;
}

4, Driver compilation and other operations

1,get into/home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char folder
cd /home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char
2,In the current folder Makefile Add the following command to the file
vi Makefile 
obj-m                           += pin4driver.o
3,take pin4driver.c put to/home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char Character device driver folder
cp ~/Desktop/pin4driver.c .
4,get into/home/sh/Desktop/learnPi/linux-rpi-4.14.y folder
cd /home/sh/Desktop/learnPi/linux-rpi-4.14.y
5,Compile driver module
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
6,Will compile completed.ko Document adoption scp Send to raspberry pie
scp drivers/char/pin4driver.ko pi@192.168.31.106:/home/pi/Desktop
7,Load driver module
sudo insmod pin4driver.ko
8,Check whether the module information in the kernel is correct pin4driver
lsmod 
9,to/dev/pin4 add permission
sudo chmod 666 /dev/pin4
10,compile pin4test.c Output as pin4test Executable file
gcc pin4test.c -o pin4test
11,function pin4test
./pin4test
load success!  //Driver file opened successfully
please input 0/1  //Input 1 or 0, 1 is high level and 0 is low level
1  //Input 1
12,View all I/O Port status information
gpio readall
13,View print information
dmesg 

1. Status information of all I/O ports (pin4 corresponds to BCM 4, Mode:Out refers to output, and V:1 refers to high level)

2. Kernel print information

Tags: C Linux

Posted on Sat, 02 Oct 2021 17:24:25 -0400 by frost110