LV06-03-chrdev-05-ioctl的使用
本文主要是字符设备驱动——使用ioctl函数实现字符设备控制的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.2的64位版本 | |
VMware® Workstation 17 Pro | 17.6.0 build-24238078 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 | |
linux内核 | linux-4.19.71(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
nxp-imx/linux-imx/releases/tag/v4.19.71 | NXP linux内核仓库tags中的v4.19.71 | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 | NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0 | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
Source Code | https://elixir.bootlin.com/linux/latest/source | linux kernel源码 |
https://elixir.bootlin.com/u-boot/latest/source | uboot源码 |
一、概述
1. 背景
一般情况下,一个字符设备的驱动,除了读取和写入设备之外,大部分的驱动程序都需要通过设备驱动程序来执行各种类型的硬件控制。
例如,针对串口设备,驱动层除了需要提供对串口的读写,还需要提供对串口波特率、校验位、以及流控等配置信息的控制。
这些配置信息需要从应用层传递一些基本数据,相比普通的读写数据,控制数据仅仅也只是数据类型不同。同时传输的控制信息,数据量一般情况下也不会太大。
2. ioctl简介
ioctl 是设备驱动程序中用来控制设备的接口函数,一个字符设备驱动通常需要实现设备的 打开、关闭、读取、写入等功能,而在一些需要细分的情况下,就需要扩展新的功能,通常以增设 ioctl()命令的方式来实现。
二、应用层的ioctl
1. ioctl函数
我们可以使用man命令来查看ioctl函数的说明:
1 | NAME |
该函数用于向设备发送控制和配置命令。用户程序所作的只是通过命令码cmd告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情,而ioctl就是负责接收cmd命令码来实现这些命令,它保证了程序的有序和整洁。
参数:
- fd :是用户程序打开设备时返回的文件描述符。
- cmd :是用户程序对设备的控制命令。
- args:应用程序向驱动程序下发的参数,如果传递的参数为指针类型,则可以接收驱动向 用户空间传递的数据。
返回值:成功返回0;失败返回-1,同时设置errno。
2. 命令说明
在这个ioctl系统调用过程中,有一个非常关键的参数,就是cmd。其由用户空间直接不经修改的传递给驱动程序。大小为4个字节,在其定义中该参数被分为四个字段。
- cmd[31:30]:dir(direction),ioctl命令访问模式(属性数据传输方向),占据2bit,表示是由内核空间到用户空间,或是用户空间到内核空间
- cmd[29:16]:size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数。
- cmd[15:8]:type(device type),设备类型,一个驱动程序一般使用一个 type,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”(也就是说来源没有依据),可以为任意char型字符,例如 ‘a’、’b’、’c’ 等等,其主要作用是使ioctl命令有唯一的设备标识。
- cmd[7:0]:nr(number),命令编号或者叫序数,表示这个设备的第几个命令,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl命令,通常从 0 开始编号递增。
<linux/ioctl.h>
中包含的<asm/ioctl.h>
头文件定义了一些构造命令编号的宏。我们可以参考一下这里ioctl.h - include/uapi/asm-generic/ioctl.h - Used to create numbers:
1 | /* |
在驱动中可通过上面几个宏定义快速组合一个命令。_IO用于构造无数据传输的命令编号。_IOR用于构造从驱动程序中读取数据的命令编号。_IOW用于构造向设备写入数据的命令编号。_IOWR用于构造双向传输命令编号。
3. 命令定义实例
例如可以使用以下代码定义不需要参数、向驱动程序写参数、向驱动程序读参数三个宏:
1 |
三、驱动层的ioctl
1. unlocked_ioctl
应用程序中 ioctl 函数会调用 file_operation 结构体中的 unlocked_ioctl 接口,接口定义如下:fs.h - include/linux/fs.h - *unlocked_ioctl *:
1 | long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); |
参数:
- struct file * 参数:文件描述符。
- unsigned int 参数:与应用程序的 cmd 参数对应,在驱动程序中对传递来的 cmd 参数进行判断从而 做出不同的动作。
- unsigned long参数:与应用程序的 arg 参数对应,从而实现内核空间和用户空间参数的传递。
2. 函数实现
2.1 命令定义
1 |
2.2 sdev_ioctl
1 | static long sdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
四、使用实例
1. 源码编写
1.1 chrdev_ioctl_demo_app.c
1 |
|
1.2 chrdev_ioctl_demo.c
1 |
|
2. 开发板测试
1 | 加载驱动 |