First of all, it is clear that the role of the driver is similar to providing an interface for the application program to associate the device and operating the device to open, read and write, etc. In the summary of the previous article, I learned the development framework of the driver, and based on this, the system device For the association with the driver, the first is to create a device file. This device file is placed under /dev/ in the root directory. You can view the devices in the /dev/ directory to know what devices are currently running on the system. You can see that there are Many device files are intercepted here
Our next step is to create a device file to associate with our driver for the application to call
The key information of the device file is: device number = major device number + minor device number, use ls -l to view the device file, you can get the major and minor device numbers corresponding to the device file.
Use mknod to create a device file: mknod /dev/xxx c major device number minor device number
It should be noted that the major and minor device numbers of the device file we created cannot be used by other device files. Here we create a device file. The major device number is 250 and the secondary device number is 0.
mknod /dev/test c 250 0
You can view the device number by cat /proc/devices
Then install our driver file
insmod module.ko
What is the link between our device files and drivers here?
The answer is the major device number. The major device number of the device file we created is 250, and the device number in the driver file is also 250. The device file and the driver file can be associated with the major device number. The next step is to call the device driver in the application. Interface, corresponding to open, write, etc.
Note that the interface called by the application should be consistent with the interface provided by the device
// Customize a file_operations structure variable and fill it static const struct file_operations test_fops = { .owner = THIS_MODULE, // Convention, just write it directly .open = test_chrdev_open, // The actual call when the application open opens the device in the future .release = test_chrdev_release, // This is the function corresponding to this .open .write = test_chrdev_write, .read = test_chrdev_read, };
application
#define FILE "/dev/test" // The name of the device file created by mknod just now char buf[100]; int main(void) { int fd = -1; int i = 0; fd = open(FILE, O_RDWR); if (fd < 0) { printf("open %s error.\n", FILE); return -1; } printf("open %s success..\n", FILE); while (1) { memset(buf, 0 , sizeof(buf)); printf("please enter on | off \n"); scanf("%s", buf); if (!strcmp(buf, "on")) { write(fd, "1", 1); } else if (!strcmp(buf, "off")) { write(fd, "0", 1); } else if (!strcmp(buf, "flash")) { for (i=0; i<3; i++) { write(fd, "1", 1); sleep(1); write(fd, "0", 1); sleep(1); } } else if (!strcmp(buf, "quit")) { break; } } // close file close(fd);//The actual call is the release in the file_operations structure return 0; }
Results of the
Brief summary:
a driver file
Create a device file in the /dev/ directory
Driver files and device files are associated with major device numbers
The application program achieves the purpose of calling the driver function by calling the program interface
The second summary: virtual address mapping
The concept of virtual memory will not be discussed here, and Baidu has an explanation.
There are two types of virtual addresses: static virtual addresses and dynamic ones. There is another thing called virtual memory. Don't get confused.
The virtual static address can be understood as the determined address table, which has always existed and is static, while the virtual dynamic address is dynamic and needs to be applied and released by calling the function. These two methods have their own advantages and disadvantages.
virtual static address
For Samsung's X5PV210, mainly check these files under the kernel
The main mapping table is located at: arch/arm/plat-s5p/include/plat/map-s5p.h The virtual address base address is defined in: arch/arm/plat-samsung/include/plat/map-base.h GPIO The relevant master mapping tables are located at: arch/arm/mach-s5pv210/include/mach/regs-gpio.h GPIO The specific register definitions are located at: arch/arm/mach-s5pv210/include/mach/gpio-bank.h
map-s5p.h file content
#ifndef __ASM_PLAT_MAP_S5P_H #define __ASM_PLAT_MAP_S5P_H __FILE__ #define S5P_VA_CHIPID S3C_ADDR(0x00700000) #define S5P_VA_GPIO S3C_ADDR(0x00500000) #define S5P_VA_SYSTIMER S3C_ADDR(0x01200000) #define S5P_VA_SROMC S3C_ADDR(0x01100000) #define S5P_VA_AUDSS S3C_ADDR(0X01600000) #define S5P_VA_UART0 (S3C_VA_UART + 0x0) #define S5P_VA_UART1 (S3C_VA_UART + 0x400) #define S5P_VA_UART2 (S3C_VA_UART + 0x800) #define S5P_VA_UART3 (S3C_VA_UART + 0xC00) #define S3C_UART_OFFSET (0x400) #define VA_VIC(x) (S3C_VA_IRQ + ((x) * 0x10000)) #define VA_VIC0 VA_VIC(0) #define VA_VIC1 VA_VIC(1) #define VA_VIC2 VA_VIC(2) #define VA_VIC3 VA_VIC(3) #endif /* __ASM_PLAT_MAP_S5P_H */
map-base.h file content
#ifndef __ASM_PLAT_MAP_H #define __ASM_PLAT_MAP_H __FILE__ /* Fit all our registers in at 0xF4000000 upwards, trying to use as * little of the VA space as possible so vmalloc and friends have a * better chance of getting memory. * * we try to ensure stuff like the IRQ registers are available for * an single MOVS instruction (ie, only 8 bits of set data) */ #define S3C_ADDR_BASE (0xFD000000) #ifndef __ASSEMBLY__ #define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x)) #else #define S3C_ADDR(x) (S3C_ADDR_BASE + (x)) #endif #define S3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */ #define S3C_VA_SYS S3C_ADDR(0x00100000) /* system control */ #define S3C_VA_MEM S3C_ADDR(0x00200000) /* memory control */ #define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */ #define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */ #define S3C_VA_OTG S3C_ADDR(0x00E00000) /* OTG */ #define S3C_VA_OTGSFR S3C_ADDR(0x00F00000) /* OTG PHY */ #define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */ /* This is used for the CPU specific mappings that may be needed, so that * they do not need to directly used S3C_ADDR() and thus make it easier to * modify the space for mapping. */ #define S3C_ADDR_CPU(x) S3C_ADDR(0x00500000 + (x)) #endif /* __ASM_PLAT_MAP_H */
The other two will not be shown, when the register is operated on the external device
define register
#include <mach/regs-gpio.h> #include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h #include <linux/string.h> #define GPJ0CON S5PV210_GPJ0CON #define GPJ0DAT S5PV210_GPJ0DAT #define rGPJ0CON *((volatile unsigned int *)GPJ0CON) #define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
operation register
rGPJ0CON = 0x11111111; rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // Bright
It can achieve the purpose of operating external devices through virtual static address operation registers.
virtual dynamic address
It needs to be applied by calling the function, and the dynamic mapping table needs to be released after use to save resources
Define the register address, and apply for the physical address used for dynamic application of virtual address mapping, and apply through request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")
#define GPJ0CON_PA 0xe0200240 //physical address #define GPJ0DAT_PA 0xe0200244 unsigned int *pGPJ0CON; unsigned int *pGPJ0DAT;
Apply for mapping virtual address
pGPJ0CON = ioremap(GPJ0CON_PA, 4); pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
operation register
*pGPJ0CON = 0x11111111; *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // Bright
Contact Mapping
iounmap(pGPJ0CON); iounmap(pGPJ0DAT); release_mem_region(GPJ0CON_PA, 4); release_mem_region(GPJ0DAT_PA, 4);
Note that this is only used for early learning. Due to the limited number of soc pins, the general pins need to be multiplexed in time to realize the operation of peripherals with different functions, so the operation of registers cannot affect the status of other peripherals. This involves judging whether the io is free, and then applying and operating, which will be recorded later, so we will go here first.