1.环境

i.MX8MP EVK,Linux 5.10.52,Yocto

需要编译出yocto的sdk,然后使用以下命令激活交叉编译环境。

source /opt/fsl-imx-xwayland/5.10-hardknott/environment-setup-cortexa53-crypto-poky-linux

https://www.nxp.com/docs/en/user-guide/IMX_PORTING_GUIDE.pdf

根据上面的链接单独编译出内核头文件。

使用以下命令打开串口,然后上电。

sudo chmod 777 /dev/ttyUSB2

picocom -b 115200 /dev/ttyUSB2
2.编写驱动

2.1驱动结构体

填写file_operations 结构体的函数,需要实现open,read,write和ioctl等函数。

static struct file_operations lzmchrdev_fops = {
	.owner = THIS_MODULE,	
	.open = lzmchrdev_open,
	.read = lzmchrdev_read,
	.write = lzmchrdev_write,
	.release = lzmchrdev_release,
    .unlocked_ioctl = lzmchrdev_ioctl,
    .poll = lzmchrdev_poll,
};

2.2入口函数和出口函数

入口函数需要注册字符驱动设备,然后在/dev目录下生成节点。出口函数则需要释放驱动设备和申请的资源。

static int __init lzmchrdev_init(void)
{
    
	int retvalue = 0;
    dev_t devno = MKDEV(LZMCHRDEV_MAJOR, 0);

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(LZMCHRDEV_MAJOR, LZMCHRDEV_NAME, &lzmchrdev_fops);
	if(retvalue < 0){
		printk("lzmchrdev driver register failed\r\n");
	}
    /* 生成/dev下的设备节点 */
    lzm_class = class_create(THIS_MODULE, "lzmchrdev");
    device_create(lzm_class, NULL, devno, NULL, "lzmchrdev");

	printk("lzmchrdev->driver_init\r\n");
	return 0;
}

static void __exit lzmchrdev_exit(void)
{
    device_destroy(lzm_class, MKDEV(LZMCHRDEV_MAJOR, 0));
    class_destroy(lzm_class);
    kfree(lzm_data);
    kfree(kerneldata);
	unregister_chrdev(LZMCHRDEV_MAJOR, LZMCHRDEV_NAME);
	printk("lzmchrdev->driver_exit\r\n");
}

2.3读写函数

读函数使用copy_to_user将内核数据传给用户空间,写函数通过copy_from_user将数据从用户空间读到内核里。

static ssize_t lzmchrdev_read(struct file *file, char __user *user_buffer, size_t size, loff_t *offset)
{
	struct lzm_device_data *read_data = (struct lzm_device_data *) file->private_data;

    memcpy(read_data->readbuf, kerneldata, sizeof(kerneldata));
    /* read data from my_data->buffer to user buffer */
    if (copy_to_user(user_buffer, read_data->readbuf, size))
        return -EFAULT;

    printk("lzmchrdev->userspace_read_data\r\n");
    return 0;
}

static ssize_t lzmchrdev_write(struct file *file, const char __user *user_buffer, size_t size, loff_t *offset)
{
	struct lzm_device_data *write_data = (struct lzm_device_data *) file->private_data;

    /* read data from user buffer to lzm_data->user_buffer */
    if (copy_from_user(write_data->writebuf, user_buffer, size))
        return -EFAULT;

    printk("lzmchrdev->userspace_to_kernel:%s\r\n",write_data->writebuf);
    return 0;
}
3.编写应用程序

打开设备节点,对其进行读写操作和ioctl操作。

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

//定义幻数
#define LZM_IOC_MAGIC '$'
//数据清零
#define LZM_IOC_CLEAR _IO(LZM_IOC_MAGIC, 0)
//获取数据(通过指针)
#define LZM_IOC_GET   _IOR(LZM_IOC_MAGIC, 1, int)
//获取数据(通过返回值)
#define LZM_IOC_QUERY _IO(LZM_IOC_MAGIC, 2)
//设置数据(通过指针)
#define LZM_IOC_SET   _IOW(LZM_IOC_MAGIC, 3, int)
//设置数据(通过直接引用参数值)
#define LZM_IOC_TELL  _IO(LZM_IOC_MAGIC, 4)

static char usrdata[] = {"This is usr data!"};
char *filename = "/dev/lzmchrdev";

int main(void)
{
	int fd, retvalue;
    int data;
	char readbuf[1024], writebuf[1024];

	/* 打开驱动文件 */
	fd  = open(filename, O_RDWR);
	if(fd < 0){
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	retvalue = read(fd, readbuf, 50);
	if(retvalue < 0){
			printf("read file %s failed!\r\n", filename);
	}else{
		/*  读取成功,打印出读取成功的数据 */
		printf("APP read data:%s\r\n",readbuf);
	}

 	/* 向设备驱动写数据 */
	memcpy(writebuf, usrdata, sizeof(usrdata));
	retvalue = write(fd, writebuf, 50);
	if(retvalue < 0){
		printf("write file %s failed!\r\n", filename);
	}

    //数据清零  
    ioctl(fd, LZM_IOC_CLEAR);

    //直接传值测试
    data = ioctl(fd, LZM_IOC_QUERY);
    printf("app data %d\n", data);
    data = 100;
    ioctl(fd, LZM_IOC_TELL, data);

    //指针传值测试
    ioctl(fd, LZM_IOC_GET, &data);
    data = 122;
    ioctl(fd, LZM_IOC_SET, &data);

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

	return 0;
}
5.代码地址
https://gitee.com/qmiller/chrdev