How to make an embedded Linux device driver (4)

4th: Read / write implementation and memory story

About this series

I will write a HowTo on how to make an embedded Linux kernel module (device driver). All the content of this article can be moved on 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 time: Read / write implementation and memory story <---------------- ----- Contents of this time __ -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 -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/04_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/04_02

Contents of this time

Up to the last time, we have done the method of incorporating the created device driver into the kernel as a kernel module. This time, I will exchange values with this device driver by read / write from the user program.

Prerequisite knowledge

Regarding memory space

Computers usually have memory such as DDR. In the case of Raspberry Pi 2, 1GByte SDRAM is installed. For Raspberry Pi, the physical address of this memory starts at 0x0000_0000. However, if the program we write uses the physical address directly when accessing the data in memory, that is not the case. The CPU has a function called MMU, and the MMU converts the physical address ⇔ virtual address. The kernel and each process run in their own virtual address space. For example, if 50 processes are running, there will be 51 (number of processes + kernel) virtual address space. The addresses in each virtual space are independent. For example, process A's 0xAAAA_AAAA and process B's 0xAAAA_AAAA are different. This prevents memory from being corrupted by other processes. Also, you can have more memory space than the actual physical memory capacity. These are the advantages of using an MMU.

For Raspberry Pi

memorymap.jpg

The above is the memory map of Raspberry Pi. First, the center is the physical address seen from the CPU (ARM). If you look at this, you can see that the SDRAM is allocated from 0x0000_0000. The right side is the memory map of the logical address after being translated by the ARM MMU. 0x0000_0000 to 0xC000_0000 are virtual addresses for user-space processes. Normal programs run in this virtual memory space. Duplicate virtual addresses occur between different processes. However, the MMU will translate it so that it does not overlap on the physical address. From 0xC000_0000, the upper level is the virtual memory space for the kernel space.

I mentioned above that the physical address of SDRAM as seen by ARM starts from 0x0000_0000, but when the CPU actually accesses the memory, it goes through the bus (probably AXI). Also, this is probably Raspberry Pi's own configuration, but it seems that it is connected to the bus via a video processor (VC (Video Core) on the left side in the figure). Therefore, the conversion of VC with MMU is included. Looking at the figure, 0xC000_0000 is the bus address of SDRAM (non-cache) in the end. Therefore, it is necessary to set this address when using DMA etc. By the way, it seems to be 0x8000_0000 via L2 cache. For example, when creating data to be transferred by DMA with the CPU, if you write to this address, coherency will not be maintained. Data inconsistency occurs because DMA transfer is performed before writeback from cache to SDRAM is performed. You need to convert it to a non-cached address before accessing the memory.

It is troublesome to check these addresses one by one in the memory map, but it seems that you can get them with the following function. The build is gcc get_memory.c -L / opt / vc / lib -lbcm_host. For Raspberry Pi2, bcm_host_get_sdram_address returned C0000000 and bcm_host_get_peripheral_address returned 3F000000.

get_memory.c


#include <stdio.h>

int main()
{
	/* https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md */
	extern unsigned bcm_host_get_sdram_address(void);
	printf("%08X\n", bcm_host_get_sdram_address());
	extern unsigned bcm_host_get_peripheral_address(void);;
	printf("%08X\n", bcm_host_get_peripheral_address());

	return 0;
}

Please see Datasheet: BCM2835-ARM-Peripherals.pdf for details on the Raspberry Pi memory map. ..

The important thing here

It's a bit off the beaten track, but what's important this time is that the caller of the system call (user-space process) and the virtual address space within the kernel module are different. Passing a pointer does not guarantee that it points to the same address.

User space-Secure data copy in kernel space

Last implementation

Last time, I implemented the following read system call handler in the implementation for the time being. Whenever you read, 1Byte of'A'is stored in buf. This buf is the area reserved by the caller (user space). Therefore, out-of-area access should occur. However, it actually worked fine. The value was also stored correctly. This is because the virtual address space is divided into 1 Gbyte for the kernel and 3 Gbyte for the user, as shown in the memory map at the beginning. Therefore, the kernel can access the virtual address space of processes running in the same context (that is, the process that called the system call). In the opposite direction, I think a memory protection violation will occur. However, this happens to work, and I don't think it will work if, for example, the target address is page swapped out.

static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_read");
	buf[0] = 'A';
	return 1;
}

Follow the rules and copy the data

The above method happens to work, but textbooks use copy_to_user and copy_from_user when copying data in userspace-kernel space. In the code below, the character string set by the user with copy_from_user at the time of writing is stored in the static variable stored_value. The contents retained at the time of reading are returned by copy_to_user.

#define NUM_BUFFER 256
static char stored_value[NUM_BUFFER];

/*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");
	if(count > NUM_BUFFER) count = NUM_BUFFER;
	if (copy_to_user(buf, stored_value, count) != 0) {
		return -EFAULT;
	}
	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");
	if (copy_from_user(stored_value, buf, count) != 0) {
		return -EFAULT;
	}
	printk("%s\n", stored_value);
	return count;
}

Manage differently for each open (file descriptor)

The above code keeps the data static. Therefore, even if different users open them separately, they will access the same variable. In addition, you will access the same variables using a different device. Reserve a data area when you open it so that you can manage it individually.

Processing at the time of open / close

The file structure is given as an argument of open. There is a member called private_data in this file structure, and you can freely save pointers. This file structure itself is saved / managed by the user as a file descriptor. Use kmalloc to allocate memory. Release with kfree when closed. This time, let's assume that the structure _mydevice_file_data is the type that holds the data.

/***Each file(File descriptor created for each open)Information associated with***/
#define NUM_BUFFER 256
struct _mydevice_file_data {
	unsigned char buffer[NUM_BUFFER];
};

/*Function called at open*/
static int mydevice_open(struct inode *inode, struct file *file)
{
	printk("mydevice_open");

	/*Reserve an area to store data unique to each file*/
	struct _mydevice_file_data *p = kmalloc(sizeof(struct _mydevice_file_data), GFP_KERNEL);
	if (p == NULL) {
		printk(KERN_ERR  "kmalloc\n");
		return -ENOMEM;
	}

	/*Initialize file-specific data*/
	strlcat(p->buffer, "dummy", 5);
	
	/*Have the secured pointer held by fd on the user side*/
	file->private_data = p;

	return 0;
}

/*Function called at close*/
static int mydevice_close(struct inode *inode, struct file *file)
{
	printk("mydevice_close");

	if (file->private_data) {
		/*Release the data area unique to each file reserved at the time of open*/
		kfree(file->private_data);
		file->private_data = NULL;
	}

	return 0;
}

Processing at the time of read / write

When the user reads and writes, the file descriptor obtained by open is set as an argument. In the implementation on the device driver side, the data area allocated at the time of opening can be accessed by referring to the private_data member in the file structure passed as an argument. This allows you to manage data individually for each open (file descriptor).

/*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");
	if(count > NUM_BUFFER) count = NUM_BUFFER;

	struct _mydevice_file_data *p = filp->private_data;
	if (copy_to_user(buf, p->buffer, count) != 0) {
		return -EFAULT;
	}
	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");

	struct _mydevice_file_data *p = filp->private_data;
	if (copy_from_user(p->buffer, buf, count) != 0) {
		return -EFAULT;
	}
	return count;
}

Try to call from the user program

About error code

It's a little off topic, but the kernel return value is usually 0. The read / write system is the number of bytes actually processed. In case of an error, a negative value is returned.

On the user program side, by including #include <errno.h>, the error code is stored in the ʻerrnovariable. You can check it directly, but by using a function calledperror, the error code will be converted into an easy-to-understand sentence. For example, do this: ʻIf ((fd = open ("/ dev / mydevice0", O_RDWR)) <0) perror ("open");

Test code

test.c


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main()
{
	char buff[256];
	int fd0_A, fd0_B, fd1_A;

	printf("%08X\n", buff);

	if ((fd0_A = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
	if ((fd0_B = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
	if ((fd1_A = open("/dev/mydevice1", O_RDWR)) < 0) perror("open");

	if (write(fd0_A, "0_A", 4) < 0) perror("write");
	if (write(fd0_B, "0_B", 4) < 0) perror("write");
	if (write(fd1_A, "1_A", 4) < 0) perror("write");

	if (read(fd0_A, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	if (read(fd0_B, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	if (read(fd1_A, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	
	if (close(fd0_A) != 0) perror("close");
	if (close(fd0_B) != 0) perror("close");
	if (close(fd1_A) != 0) perror("close");

	return 0;
}

Compile and run the test code as above.

gcc test.c
./a.out
0_A
0_B
1_A

The result looks like this, and you can see that even if you access the same device, you can keep / get different values if you open them separately.

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 make a slack bot
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 Selenium as light as possible
[Linux] How to subdivide files and folders
How to make an artificial intelligence LINE bot with Flask + LINE Messaging API
How to install aws-session-manager-plugin on Manajro Linux
[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 power off Linux with Ultra96-V2
How to update security on CentOS Linux 8
I want to make an automation program!