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