一、部署第一个驱动程序
本章节介绍如何在 Pico-G1 开发板上编写一个"hello word"驱动程序、并将驱动暴露成用户态接口,供应用程序调用,完成从驱动编写到编译再到调用的完整流程。本章节基于,部署第一个应用程序,读者需先阅读部署第一个应用程序.
驱动执行流程:
模块加载时执行 hellodrv_init
调用 misc_register() 注册设备
内核自动生成 /dev/hellodrv
用户态程序打开 /dev/hellodrv
用户态通过 ioctl 向驱动发命令
驱动通过 copy_to_user() 把数据返回给用户态
模块卸载时执行 hellodrv_exit
调用 misc_deregister() 注销设备
1. 修改应用程序
请参考将helloword.c改成下列格式,并按说明进行编译烧录。
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define HELLODRV_MAGIC 'H' #define HELLODRV_GET_MSG _IOR(HELLODRV_MAGIC, 0x01, char *) int main(void) { int fd; char buf[64] = {0}; printf("hello world from firmware!\n"); fd = open("/dev/hellodrv", O_RDWR); if (fd < 0) { perror("open /dev/hellodrv failed"); return -1; } if (ioctl(fd, HELLODRV_GET_MSG, buf) < 0) { perror("ioctl HELLODRV_GET_MSG failed"); close(fd); return -1; } printf("message from driver: %s\n", buf); close(fd); return 0; }2. 新建驱动程序
在SDK根目录中执行:
# 在SDK根目录中执行: cd source/kernel/linux-5.10.y/drivers/misc mkdir hellodrv cd hellodrv touch hellodrv.c touch Makefile touch Kconfig编写驱动程序
编写hellodrv.c
在内核里注册一个设备
在板子上生成一个设备节点,比如 /dev/hellodrv
让用户态程序 helloworld 可以通过这个设备节点与驱动通信
先实现一个最小功能:通过 ioctl 从驱动读取一段固定字符串
#include <linux/module.h> #include <linux/init.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/ioctl.h> #include <linux/device.h> /* * 驱动名称 * 最终在 /dev 下生成的设备节点就是 /dev/hellodrv */ #define HELLODRV_NAME "hellodrv" /* * ioctl 魔数 * 用于区分不同驱动的 ioctl 命令,避免冲突 */ #define HELLODRV_MAGIC 'H' /* * ioctl 命令:从驱动获取一段字符串 * _IOR 表示:用户态从内核态读取数据 * 第三个参数表示数据类型,这里是 char * */ #define HELLODRV_GET_MSG _IOR(HELLODRV_MAGIC, 0x01, char *) /* * 驱动返回给用户态的字符串 * 这里先写死一个固定消息,方便验证用户态和内核态的通信是否正常 */ static const char *g_msg = "hello from driver"; /* * 设备打开时的回调函数 * 当用户态执行 open("/dev/hellodrv", ...) 时会进入这里 */ static int hellodrv_open(struct inode *inode, struct file *filp) { pr_info("hellodrv: device opened\n"); return 0; } /* * 设备关闭时的回调函数 * 当用户态执行 close(fd) 时会进入这里 */ static int hellodrv_release(struct inode *inode, struct file *filp) { pr_info("hellodrv: device closed\n"); return 0; } /* * ioctl 回调函数 * 用户态程序可以通过 ioctl(fd, cmd, arg) 与驱动进行控制交互 * * cmd:用户态传入的命令 * arg:用户态传入的参数,通常是一个用户空间地址 */ static long hellodrv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { char msg[64]; switch (cmd) { case HELLODRV_GET_MSG: /* * 先清空临时缓冲区 */ memset(msg, 0, sizeof(msg)); /* * 将固定字符串复制到本地缓冲区 */ snprintf(msg, sizeof(msg), "%s", g_msg); /* * 把内核中的数据拷贝到用户空间 * 不能直接把内核地址传给用户态,必须使用 copy_to_user */ if (copy_to_user((void __user *)arg, msg, strlen(msg) + 1)) return -EFAULT; pr_info("hellodrv: ioctl GET_MSG\n"); return 0; default: /* * 不认识的命令,返回非法参数错误 */ return -EINVAL; } } /* * 文件操作表 * 内核通过它知道这个设备支持哪些操作 */ static const struct file_operations hellodrv_fops = { .owner = THIS_MODULE, .open = hellodrv_open, .release = hellodrv_release, .unlocked_ioctl = hellodrv_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = hellodrv_ioctl, #endif }; /* * misc 设备结构体 * 通过 misc_register 注册后,会自动生成 /dev/hellodrv */ static struct miscdevice hellodrv_miscdev = { .minor = MISC_DYNAMIC_MINOR, /* 动态分配次设备号 */ .name = HELLODRV_NAME, /* 设备节点名 */ .fops = &hellodrv_fops, /* 绑定文件操作集 */ .mode = 0666, /* 设备节点权限:可读可写 */ }; /* * 模块初始化函数 * 在驱动加载时执行 */ static int __init hellodrv_init(void) { int ret; ret = misc_register(&hellodrv_miscdev); if (ret) { pr_err("hellodrv: misc_register failed, ret=%d\n", ret); return ret; } pr_info("hellodrv: module loaded, /dev/%s created\n", HELLODRV_NAME); return 0; } /* * 模块退出函数 * 在驱动卸载时执行 */ static void __exit hellodrv_exit(void) { misc_deregister(&hellodrv_miscdev); pr_info("hellodrv: module unloaded\n"); } /* * 指定模块入口和出口 */ module_init(hellodrv_init); module_exit(hellodrv_exit); /* * 模块信息 */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("ljh"); MODULE_DESCRIPTION("Hello driver for helloworld app");编写Makefile
obj-$(CONFIG_HELLODRV) += hellodrv.o编写Kconfig
config HELLODRV tristate "Hello driver" help A simple misc driver for user-space test.更改上级Makefile 和 Kconfig 文件
在source/kernel/linux-5.10.y/drivers/misc/Makefile合适的位置加上这一行,意思是如果配置打开了 CONFIG_HELLODRV,就进入 hellodrv/ 这个目录继续编译。
obj-$(CONFIG_HELLODRV) += hellodrv/在source/kernel/linux-5.10.y/drivers/misc/Kconfig合适的位置加上这一行,意思是drivers/misc/hellodrv/Kconfig 里面还有一个新的配置项,要把它读进来
3. 将驱动程序编译成内核模块
1. 配置Hello Drv编译选项
vim source/kernel/linux-5.10.y/arch/arm/configs/xmorca_defconfig在末尾添加CONFIG_HELLODRV=m 检测是否配置成功
grep CONFIG_HELLODRV source/kernel/linux-5.10.y/arch/arm/configs/xmorca_defconfig2. 编译内核
make linux确认配置是否生效
grep CONFIG_HELLODRV out/xm7206v12a/linux-5.10.y/.config查找驱动模块
find out/xm7206v12a/linux-5.10.y -name "hellodrv.ko"3. 将驱动模块装进rootfs中
cd out/xm7206v12a/rootfs/lib/ mkdir -p modules/5.10.0/extr # 回到SDK根目录后执行: cp -f out/xm7206v12a/linux-5.10.y/drivers/misc/hellodrv/hellodrv.ko out/xm7206v12a/rootfs/lib/modules/5.10.0/extr/ # 确定是否复制成功 ls -l out/xm7206v12a/rootfs/lib/modules/5.10.0/extr/hellodrv.ko # 重新打包镜像 make fs_image4.烧录镜像 请参考 烧录章节:镜像烧录 烧录 SPI 镜像。
挂载驱动模块
insmod lib/modules/5.10.0/extr/hellodrv.ko执行应用程序
/usr/bin/helloworld预期输出
/ # insmod lib/modules/5.10.0/extr/hellodrv.ko hellodrv: module loaded, /dev/hellodrv created / # /usr/bin/helloworld hello world from firmware! hellodrv: device opened hellodrv: ioctl GET_MSG message from driver: hello from driver hellodrv: device closed / #二、SD卡挂载
受限于板载资源,外部文件通常建议存放至 SD 卡中。基于此,本章节将介绍 SD 卡的挂载流程。
创建SD卡挂载点
mkdir -p /sd_card查看SD卡设备节点
ls /dev/mmc*预期输出:
mmcblk1 mmcblk1p1其中:
/dev/mmcblk1表示整张 MMC/SD 卡设备;/dev/mmcblk1p1表示这张卡上的第 1 个分区
挂载SD卡
mount /dev/mmcblk1p1 /sd_card查看挂载是否成功
df -h | grep sd_card预期输出:
/ # df -h | grep /sd_card /dev/mmcblk1p1 14.8G 56.1M 14.8G 0% /sd_card / #如果命令输出中包含/sd_card,则说明挂载成功。此时即可将外部文件拷贝到该目录下进行保存和使用。
卸载SD卡
在完成文件读写后,建议先卸载 SD 卡再拔出介质,以避免数据损坏。
umount /sd_card如需重新挂载,可再次执行挂载命令。