4926 views|7 replies

291

Posts

5

Resources
The OP
 

【i.MX6ULL】Driver Development 1——Character Device Development Template [Copy link]

The previous articles (starting from i.MX6ULL Embedded Linux Development 1-Preliminary Exploration of Uboot Transplantation ) introduced the system transplantation of embedded Linux (Uboot, kernel and root file system) and the use of MfgTool to burn the system into the EMMC of the board.

This article begins to introduce embedded Linux driver development.

There is a lot of content, so let’s look at the directory first:

1 Linux driver classification

Peripheral drivers in Linux can be divided into three categories: character device drivers, block device drivers and network device drivers.

  • Character device driver : A character device is a device that can read and write according to a byte stream (such as a file). Character devices are the most common, from the simplest lighting to I2C, SPI, audio, etc., all belong to character device drivers.

  • Block device driver : Device driver based on storage blocks, such as EMMC, NAND, SD card, etc. For users, there is no difference in the access method between character devices and block devices.

  • Network device driver : that is, network driver, which has the characteristics of both character device and block device, because its input and output are structured blocks (messages, packets, frames), but the size of its blocks is not fixed.

2 Basic principles of Linux drivers

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 performing corresponding operations on this file named " /dev/xxx ".

For example , the simplest lighting function will have a driver file like /dev/led. The application uses the open function to open the file /dev/led. If you want to light up or turn off the LED , use the write function to write the switch value. If you want to get the status of the LED , use the read function to read the corresponding status from the driver. After use, use the close function to close the /dev/led file.

2.1 Linux software layer structure

Linux software can be divided into four layers from top to bottom, taking LED control as an example:

  • Application layer : The application uses the open function provided by the library to turn on the LED device

  • Library : The library executes the "swi" instruction according to the parameters passed in by the open function, which causes the CPU to exception and enter the kernel

  • Kernel : The kernel's exception handling function finds the corresponding driver according to the passed parameters, returns the file handle to the library, and then returns it to the application layer

  • After the application layer obtains the file handle, it uses the write or ioctl provided by the library to issue control instructions

  • The library executes the "swi" instruction according to the parameters passed by the write or ioctl function and enters the kernel

  • The kernel's exception handling function finds the corresponding driver based on the passed parameters

  • Driver : The driver controls the hardware and lights up the LED

Applications run in user space , while Linux drivers are part of the kernel, so they run in kernel space . When the application layer opens the /dev/led driver through the open function, since the user space cannot directly operate the kernel, the " system call " method is used to " fall " from the user space to the kernel space to operate the underlying driver.

For example, if the application calls the open function, there should also be a corresponding open function in the driver .

2.2 Linux kernel driver operation function

Each system call has a corresponding driver function in the driver. In the Linux kernel file include/linux/fs.h, there is a file_operations structure, which is a set of Linux kernel driver operation functions:

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*mremap)(struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    /*省略若干行...*/
};

Among them, the commonly used functions in character device driver development are:

  • owner: A pointer to the module that owns the structure, usually set to THIS_MODULE.

  • llseek function: used to modify the current read and write position of the file.

  • read function: used to read device files.

  • write function: used to write (send) data to the device file.

  • poll function: It is a polling function used to query whether the device can perform non-blocking reading and writing.

  • unlocked_ioctl function: provides control functions for the device, corresponding to the ioctl function in the application.

  • compat_ioctl function: Same as unlocked_ioctl, except that on a 64-bit system, a 32-bit application will use this function. On a 32-bit system, a 32-bit application will call unlocked_ioctl.

  • mmap function: used to map the device's memory into the process space (that is, user space). Generally, frame buffer devices use this function, such as the video memory of the LCD driver. After mapping the frame buffer (LCD video memory) into the user space, the application can directly operate the video memory, so there is no need to copy back and forth between the user space and the kernel space.

  • open function: used to open the device file.

  • release function: used to release (close) the device file, corresponding to the close function in the application.

  • fasync function: used to refresh the data to be processed and to refresh the data in the buffer to the disk.

  • aio_fsync function: Similar to fasync function, except that aio_fsync is an asynchronous refresh function.

2.3 Linux driver operation mode

The Linux driver can be run in two modes:

  • Compile the driver into the Linux kernel so that the driver program will run automatically when the Linux kernel starts.

  • Compile the driver into a module (with a .ko extension ), and use the "insmod" command to load the driver module after the Linux kernel is started.

During the driver development phase, it is generally compiled into a module, and there is no need to compile the entire Linux code, which is convenient for debugging the driver. When the driver development is completed, you can choose whether to compile the driver into the Linux kernel according to actual needs.

2.4 Linux device number

2.4.1 Composition of device number

Each device in Linux has a device number, which consists of two parts: the major device number and the minor device number.

  • Major device number: indicates a specific driver

  • Minor device number: indicates the devices using this driver

Linux provides a data type called dev_t to represent the device number. Its essence is a 32-bit unsigned int data type, where the upper 12 bits are the major device number and the lower 2 bits are the minor device number . Therefore, the major device number in Linux ranges from 0 to 4095 .

Several macro definitions for device number operations are provided in the include/linux/kdev_t.h file:

#define MINORBITS   20 
#define MINORMASK   ((1U << MINORBITS) - 1) 

#define MAJOR(dev)   ((unsigned int) ((dev) >> MINORBITS)) 
#define MINOR(dev)   ((unsigned int) ((dev) & MINORMASK)) 
#define MKDEV(ma,mi)  (((ma) << MINORBITS) | (mi))
  • MINORBITS: indicates the number of digits in the minor device number, a total of 20 digits

  • MINORMASK: indicates the minor device number mask

  • MAJOR: used to obtain the major device number from dev_t, just shift dev_t right by 20 bits

  • MINOR: used to obtain the minor device number from dev_t, taking the value of the lower 20 bits of dev_t

  • MKDEV: Used to combine the given major device number and minor device number values into a device number of type dev_t

2.4.2 Allocation of major device numbers

The allocation of major device numbers includes static allocation and dynamic allocation

  • Static allocation requires manual specification of device numbers, and be careful not to duplicate existing ones. Some commonly used device numbers have been allocated by Linux kernel developers. Use the "cat /proc/devices" command to view all device numbers already in use in the current system.

  • Dynamic allocation means applying for a device number before registering a character device. The system will automatically allocate an unused device number to avoid conflicts. You can release this device number when uninstalling the driver.

Device number application function:

/*
* dev:保存申请到的设备号
* baseminor:次设备号起始地址,一般baseminor为0 (次设备号以baseminor为起始地址地址开始递)
* count:要申请的设备号数量
* name:设备名字
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 

Device number release function:

/*
* from:要释放的设备号
* count:表示从from开始,要释放的设备号数量
*/
void unregister_chrdev_region(dev_t from, unsigned count) 

3 Character Device Driver Development Template

3.1 Loading and unloading

When writing a driver, you need to register two functions for module loading and unloading:

module_init(xxx_init);  //注册模块加载函数 
module_exit(xxx_exit);  //注册模块卸载函数 
  • module_init() is used to register a module loading function with the Linux kernel. The parameter xxx_init is the specific function that needs to be registered. When the "insmod" command is used to load the driver, the xxx_init function will be called.

  • module_exit() is used to register a module unloading function with the Linux kernel. The parameter xxx_exit is the specific function that needs to be registered. When the "rmmod" command is used to unload the specific driver, the xxx_exit function will be called.

The character device driver module loading and unloading templates are as follows:

/* 驱动入口函数 */ 
static int __init xxx_init(void) 
{ 
    /*入口函数内容 */ 
    return 0; 
} 

/* 驱动出口函数 */ 
static void __exit xxx_exit(void) 
{ 
    /*出口函数内容*/ 
} 

/*指定为驱动的入口和出口函数 */ 
module_init(xxx_init); 
module_exit(xxx_exit); 

After the driver is compiled, the extension is .ko. There are two commands to load the driver module:

  • insmod: The simplest module loading command, used to load the specified .ko module. This command cannot resolve module dependencies.

  • modprobe: This command analyzes the module dependencies and loads all dependent modules into the kernel, so it is smarter.

    By default, the modprobe command will search for modules in the /lib/modules/<kernel-version> directory (the homemade root file system does not have this directory and needs to be created manually)

There are also two commands for uninstalling the driver :

  • rmmod: For example, use rmmod drv.ko to uninstall the drv.ko module

  • modprobe -r: This command not only uninstalls the specified driver, but also uninstalls other modules it depends on. If these dependent modules are still being used by other modules, you cannot use modprobe to uninstall the driver module!!!

3.2 Registration and Deregistration

For character device drivers, when the driver module is loaded successfully, the character device needs to be registered . Similarly, when the driver module is uninstalled, the character device also needs to be unregistered .

The prototype of the character device registration function is as follows:

/* func: register_chrdev 注册字符设备
* major:主设备号
* name:设备名字,指向一串字符串
* fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量
*/
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) 

The prototype of the character device deregistration function is as follows:

/* func: unregister_chrdev 注销字符设备
* majo:要注销的设备对应的主设备号 
* name:要注销的设备对应的设备名
*/
static inline void unregister_chrdev(unsigned int major, const char *name) 

Generally, the registration of character devices is performed in the entry function xxx_init of the driver module, and the deregistration of character devices is performed in the exit function xxx_exit of the driver module.

static struct file_operations test_fops;

/* 驱动入口函数 */ 
static int __init xxx_init(void) 
{ 
    /* 入口函数具体内容 */ 
  int retvalue = 0; 
  /* 注册字符设备驱动 */ 
    retvalue = register_chrdev(200, "chrtest", &test_fops); 
    if(retvalue < 0)
   { 
        /*  字符设备注册失败, 自行处理 */ 
    } 
    return 0; 
} 

/* 驱动出口函数 */ 
static void __exit xxx_exit(void) 
{ 
    /* 注销字符设备驱动 */ 
    unregister_chrdev(200, "chrtest"); 
} 

/* 将上面两个函数指定为驱动的入口和出口函数 */ 
module_init(xxx_init); 
module_exit(xxx_exit); 

Note: Select the major device number that is not in use. You can enter the command "cat /proc/devices" to view the device number that is currently in use.

3.3 Implementing specific operation functions of the device

The file_operations structure is the specific operation function of the device.

Assume that there are two requirements for the chrtest device:

  • Able to implement opening and closing operations: Need to implement the two functions open and release in file_operations

  • Able to perform read and write operations: Need to implement the read and write functions in file_operations

The first is to open (open), read (read), write (write), release (release) 4 basic operations

/*打开设备*/ 
static int chrtest_open(struct inode *inode, struct file *filp) 
{ 
    /*用户实现具体功能*/
    return 0; 
} 

/*从设备读取*/ 
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 
{ 
    /*用户实现具体功能*/
    return 0; 
} 

/*向设备写数据*/ 
static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 
{ 
    /*用户实现具体功能*/
    return 0; 
} 

/*关闭释放设备*/ 
static int chrtest_release(struct inode *inode, struct file *filp) 
{ 
     /*用户实现具体功能*/ 
    return 0; 
} 

Then comes the driver's entry (init) and exit (exit) functions:

/*文件操作结构体*/
static struct file_operations test_fops = { 
    .owner = THIS_MODULE,  
    .open = chrtest_open, 
    .read = chrtest_read, 
    .write = chrtest_write, 
    .release = chrtest_release, 
}; 

/*驱动入口函数*/ 
static int __init xxx_init(void) 
{ 
    /*入口函数具体内容*/ 
    int retvalue = 0; 

  /*注册字符设备驱动*/ 
    retvalue = register_chrdev(200, "chrtest", &test_fops); 
    if(retvalue < 0)
   { 
        /*字符设备注册失败*/ 
    } 
    return 0; 
}

/*驱动出口函数*/ 
static void __exit xxx_exit(void) 
{
    /*注销字符设备驱动*/ 
    unregister_chrdev(200, "chrtest"); 
}

/*指定为驱动的入口和出口函数*/ 
module_init(xxx_init); 
module_exit(xxx_exit); 

3.4 Add LICENSE and author information

LICENSE must be added, otherwise an error will be reported during compilation. The author information can be added or not.

MODULE_LICENSE() //添加模块 LICENSE 信息 
MODULE_AUTHOR()  //添加模块作者信息

To summarize:

4 Character device driver development experiment

Let's take the chrdevbase virtual device in the tutorial provided by Zhengdian Atom as an example to completely write a character device driver module. chrdevbase is not an actual device, but is only used to learn the development process of character devices.

4.1 Programming

Drivers and applications need to be written separately .

Note: In order to distinguish the print information of the two programs, add the "[BSP]" mark before the driver 's print , and add the "[APP]" mark before the application 's print .

4.1.1 Writing a driver

  • Some definitions

#define CHRDEVBASE_MAJOR    200             /*主设备号*/
#define CHRDEVBASE_NAME     "chrdevbase"    /*设备名*/

static char readbuf[100];       /*读缓冲区*/
static char writebuf[100];      /*写缓冲区*/
static char kerneldata[] = {"kernel data!"}; /*内核驱动中的数据,用来测试应用程序读取该数据*/
  • open, close, read, write

/*
 * [url=home.php?mod=space&uid=1068364]@description[/url] : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp    : 设备文件,file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * [url=home.php?mod=space&uid=784970]@return[/url] : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("[BSP] chrdevbase open!\n");
    return 0;
}

/*
 * @description     : 从设备读取数据 
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    
    /* 向用户空间发送数据 */
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
  
    retvalue = copy_to_user(buf, readbuf, cnt);
    if(retvalue == 0)
   {
        printk("[BSP] kernel senddata ok!\n");
    }
  else
   {
        printk("[BSP] kernel senddata failed!\n");
    }
    
    printk("[BSP] chrdevbase read!\n");
    return 0;
}

/*
 * @description     : 向设备写数据 
 * @param - filp    : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
  
    /* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    if(retvalue == 0)
   {
        printk("[BSP] kernel recevdata:%s\n", writebuf);
    }
  else
   {
        printk("[BSP] kernel recevdata failed!\n");
    }
    
    printk("[BSP] chrdevbase write!\n");
    return 0;
}

/*
 * @description     : 关闭/释放设备
 * @param - filp    : 要关闭的设备文件(文件描述符)
 * @return          : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    printk("[BSP] chrdevbase release!\n");
    return 0;
}

  • Driver loading and unloading

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,   
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/*
 * @description : 驱动入口函数 
 * @param       : 无
 * @return      : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    int retvalue = 0;

    /* 注册字符设备驱动 */
    retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    if(retvalue < 0)
   {
        printk("[BSP] chrdevbase driver register failed\n");
    }
    printk("[BSP] chrdevbase init!\n");
    return 0;
}

/*
 * @description : 驱动出口函数
 * @param       : 无
 * @return      : 无
 */
static void __exit chrdevbase_exit(void)
{
    /* 注销字符设备驱动 */
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("[BSP] chrdevbase exit!\n");
}

/*将上面两个函数指定为驱动的入口和出口函数*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
  • The last LIENSE and the author

/*LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai & xxpcb"); //本篇的程序代码在“正点原子”左大神提供的代码上进行修改

4.1.2 Writing Applications

Here we divide the program into three sections for analysis. Let’s look at the beginning first :

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

static char usrdata[] = {"usr data!"}; /*应用程序中的数据,用于测试通过驱动访问写入内核*/

int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];

	if(argc != 3)
    {
		printf("[APP] Error Usage!\n");
		return -1;
	}

    //参数1是驱动的文件名,用来指定驱动的位置
	filename = argv[1];

	//【1】打开驱动文件
	fd  = open(filename, O_RDWR);
	if(fd < 0)
    {
		printf("[APP] Can't open file %s\n", filename);
		return -1;
	}
    printf("[APP] open file: '%s' success\n", filename);

Mainly some header files and main function entry. When calling the main function, you need to pass in 2 parameters (actually 3 parameters, the function name itself is the default 0th parameter, no need to specify manually), the specific functions are:

  • Parameter 0: argv[0], the function name itself, not used here

  • Parameter 1: argv[1], filename, not used here

  • Parameter 2: argv[2], custom operation parameter, which will be described in the following function. 1 is to read from the driver file, and 2 is to write data to the driver file.

Let’s look at the specific operations:

    //【2】从驱动文件读取数据
	if(atoi(argv[2]) == 1)//参数1表示【读取】内核中的数据
    { 
		retvalue = read(fd, readbuf, 50);
		if(retvalue < 0)
        {
			printf("[APP] read file '%s' failed!\n", filename);
		}
        else
        {
			/* 读取成功,打印出读取成功的数据 */
			printf("[APP] read data:%s\n",readbuf);
		}
	}
    //【3】向设备驱动写数据
	if(atoi(argv[2]) == 2)//参数2表示向内核中【写入】数据
    {
		memcpy(writebuf, usrdata, sizeof(usrdata));
		retvalue = write(fd, writebuf, 50);
		if(retvalue < 0)
        {
			printf("[APP] write file %s failed!\n", filename);
		}
        else
        {
            printf("[APP] write data:'%s' to file ok\n", writebuf);
        }
	}

Finally, shut down the device :

	//【4】关闭设备
	retvalue = close(fd);
	if(retvalue < 0)
    {
		printf("[APP] Can't close file %s\n", filename);
		return -1;
	}
	printf("[APP] close file ok\r\n");

	return 0;
}

Closing means that the device will no longer be used (if you want to use it again, just reopen it). The character device driver is closed by closing the driver file.

4.2 Program Compilation

4.2.1 Compile the driver

Compile the driver, that is, compile the chrdevbase.c file into a .ko module. Use Makefile to compile. First create Makefile:

KERNELDIR := /home/xxpcb/myTest/imx6ull/kernel/nxp_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

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

Meaning of each line:

  • KERNELDIR : Linux kernel source directory used by the development board

  • CURRENT_PATH : Current path, obtained by running the "pwd" command

  • obj-m : compile the chrdevbase.c file into the chrdevbase.ko module

  • Specific compilation command : The following modules means compiling modules, -C means switching the working directory to the KERNERLDIR directory, and M means the module source directory

Enter the "make" command to compile. After compilation, many compilation files will appear.

Note: If the following error is reported during direct make compilation, it is because the compiler and architecture are not specified in the kernel, and the default x86 platform compilation error is used.

Modify the top-level Makefile of the Kernel project and directly define the variables ARCH and CROSS_COMPILE to arm and arm-linux-gnueabihf-

(For an introduction to the kernel, see: i.MX6ULL Embedded Linux Development 3-Kernel Porting )

4.2.2 Compiling the Application

Compiling an application does not require the participation of a kernel file. Only one file can be compiled, so enter the command directly to compile:

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

Compilation will generate chrdevbaseApp, which is an ARM version executable file in 32-bit LSB format

4.3 Testing

The previous article ( i.MX6ULL Embedded Linux Development 6-System Burning to eMMC and the Pitfalls! ) has implemented the system transplantation and packaging burning work, and the system has been burned into EMMC. This time we will conduct experiments directly on this basis.

4.3.1 Create a driver module directory

When loading the driver module, the modprobe command used will search for files in a specific directory. For example, if the development board uses the 4.1.15 version of the Linux kernel, the directory is "/lib/modules/4.1.15". This directory usually does not exist and needs to be created according to the version of the Linux kernel.

Note that this is the path in the development board's file system. You can access the development board through the serial port and create this directory using Linux commands.

4.3.2 Send files to the development board (TFTP transfer)

For this test, you first need to transfer the files compiled in Ubuntu to the board for running. How to transfer them? You can use the TFTP transfer service.

In the previous article ( i.MX6ULL Embedded Linux Development 2-uboot Porting Practice ), we have introduced how to build a TFTP server in Ubuntu .

After setting up the TFTP service, start transferring files to the development board. The specific steps are as follows:

  • The development board is connected to the network cable and is in the same LAN as the Ubuntu virtual machine.

  • Make sure Ubuntu has installed the TFTP service and set the TFTP service folder

  • Copy the compiled file in ubuntu to the TFTP service folder of ubuntu !!!

    mv chrdevbaseApp ~/myTest/tftpboot/
    mv chrdevbase.ko ~/myTest/tftpboot/

    Note: After compiling the program, before transferring it to the board, be sure to copy the file to the TFTP folder first, otherwise the board may get the old file in the TFTP folder.

  • Use the following command in the serial port of the development board to transfer the files in Ubuntu to the development board

    cd /lib/modules/4.1.15   /*确保在要下载文件的目录中,若已在,则忽略*/
    tftp -g -r chrdevbaseApp 192.168.5.101 /*获取chrdevbaseApp文件*/
    tftp -g -r chrdevbase.ko 192.168.5.101 /*获取chrdevbase.ko文件*/

    Here, -g stands for get, which means downloading files, -r stands for remote file, which is the file name of the remote host , followed by the file name to be downloaded , and finally the IP address of the remote host ubuntu .

    After entering the command, you can see the file transfer progress, as shown below:

4.3.3 Start testing

After the driver file chrdevbase.ko and the application file chrdevbaseApp are transferred to the /lib/modules/4.1.15 directory in the board, you can test it.

First, use the insmod command to load the driver, then use lsmod to view the current driver (there is only one character driver we just loaded), and then use the cat command to view the devices information to confirm whether the device has been listed in the system. The three instructions are as follows:

insmod chrdevbase.ko 
lsmod
cat /proc/devices 

The specific output information is:

It can be seen that there is a chrdevbase device in the system, and the major device number is 200 set in the program.

After the driver is loaded, a corresponding device node file must be created in the /dev directory (the application program operates the device through this node file).

Enter the following two commands to create the device node file /dev/chrdevbase and view the results:

mknod /dev/chrdevbase c 200 0 
ls /dev/chrdevbase -l

At this point, the character device driver has been loaded and we can test our application, that is, read and write :

According to the settings of the above program, 1 is read and 2 is write:

./chrdevbaseApp /dev/chrdevbase 1  
./chrdevbaseApp /dev/chrdevbase 2
  • Let’s look at the “read test” first. Note that you need to give chrdevbaseApp executable permissions, otherwise it will not run.

The lower part of the picture shows the program output information, but it seems that there is only the output of the BSP driver, and no output of the APP application. It should be that the kernel printk print and the application printf print conflict, resulting in the APP print being squeezed out.

  • Let's look at "Write Test", which also has only BSP printing.

4.3.4 Avoiding Print Conflicts

To solve the printing conflict problem, we can add a 1 second sleep(1) delay before and after each printf call to avoid printing conflicts.

After increasing the delay and testing again, the printing is normal:

After the test, the module is uninstalled with the rmmod command:

5 Conclusion

This article introduces the basic driver in embedded Linux driver development - the basic mode of character driver development. A virtual character device driver is used for testing to understand the calling relationship between the driver and the application.

This post is from ARM Technology

Latest reply

Thanks for sharing, very cool   Details Published on 2022-4-9 09:10

赞赏

2

查看全部赞赏

 

1w

Posts

204

Resources
From 2
Personal signature

玩板看这里:

http://en.eeworld.com/bbs/elecplay.html

EEWorld测评频道众多好板等你来玩,还可以来频道许愿树许愿说说你想要玩的板子,我们都在努力为大家实现!

 
 

1w

Posts

204

Resources
3
 

Thanks for sharing~~ It's so late, you are still working, take care to rest~~ Health is the capital of revolution

This post is from ARM Technology
Add and join groups EEWorld service account EEWorld subscription account Automotive development circle
 
Personal signature

玩板看这里:

http://en.eeworld.com/bbs/elecplay.html

EEWorld测评频道众多好板等你来玩,还可以来频道许愿树许愿说说你想要玩的板子,我们都在努力为大家实现!

 
 

6555

Posts

0

Resources
4
 

The original poster introduced the basic driver in embedded Linux driver development. What will happen if it is not the basic one?

This post is from ARM Technology

Comments

It is composed of basic kernel APIs, and the rest is just the data manual.  Details Published on 2021-8-24 17:07
 
 
 

6062

Posts

4

Resources
5
 

Thanks for sharing. Indeed, Linux driver development should start from character devices.

First, it is easy to get started, and second, it is very useful.

This post is from ARM Technology
 
 
 

7422

Posts

2

Resources
6
 
Jacktang posted on 2021-8-24 07:26 The original poster introduced the basic driver in embedded Linux driver development. What will happen if it is not the basic one?

It is composed of basic kernel APIs, and the rest is just the data manual.

This post is from ARM Technology
 
Personal signature

默认摸鱼,再摸鱼。2022、9、28

 
 

1942

Posts

2

Resources
7
 

The analysis is quite detailed!

This post is from ARM Technology
 
 
 

125

Posts

0

Resources
8
 

Thanks for sharing, very cool

This post is from ARM Technology
 
 
 

Guess Your Favourite
Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list