GoDm@'s Blog

按键驱动综合实验和ioctl功能介绍

版权信息

warning

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


1. 实验内容

在设备树中添加按键外设“key”,同时使用pinctrl和gpio子系统,并使用中断系统启动定时器,并利用定时器进行消抖、读取按键值。

2. 查询手册并配置Pinctrl、GPIO、dts

查询原理图:key使用的引脚名为 SNVS_TAMPER1

查询参考手册,搜索“SNVS_TAMPER1”
该引脚可复用为 GPIO5_IO01:——GPIO子系统配置条件已就绪

现在明确了pinctrl子系统的复用,还要配置电气属性。

在参考手册找到 SW_PAD_CTL_PAD_SNVS_TAMPER1 SW PAD Control,该寄存器用于配置 GPIO5_IO01 引脚的电器属性,各个位的含义我专门写了一篇文章——参见IMX6ULL引脚电气属性配置寄存器浅析,这里就不细说了。我们需要:

0x13080,——pinctrl 所有配置条件已就绪!

在iomuxc节点下添加 key 的引脚组:这是pinctrl节点(宏定义在imx6ull-pinfunc-snvs.h)

&iomuxc {
    pinctrl_key: keygrp{
        fsl,pins = <
            MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x13080
        >;
    };
};

中断的属性也可以写了,就是interrupt-parent和interrupts这两个,我们触发方式设置为双边沿触发,中断宏定义在include/linux/irq.h

那么我们把完整的设备树节点写出来:

/ {
    key{
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "gdm-key";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_key>;
        key-gpios = <gpio5 1 GPIO_ACTIVE_HIGH>;
        interrupt-parent = <&gpio5>;
        interrupts = <1 IRQ_TYPE_EDGE_BOTH>;
        status = "okay";
    };
};

最后在内核源码根目录下编译一波:make dtbs

3. 验证:使用ftp把dts传到开发板

使用ftp文件传输到开发板相关方法,参见——mx6ull开发版移植nxp官方u-boot

这里主要讲一下如何将nxp官方的uboot启动命令修改为“使用ftp从网络中下载dts”而不是mmc中的dts,不然你每次启动还是从mmc中加载

进入到 uboot 界面,使用 pri 命令查看环境变量,看bootcmd命令:

bootcmd=run findfdt;run findtee;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi

使用递归的思想分析一波,设备启动后会进入 run loadimage 分支运行,最后运行 mmcboot 因此我们看mmcboot

echo Booting from mmc ...; run mmcargs; if test ${tee} = yes; then run loadfdt; run loadtee; bootm ${tee_addr} - ${fdt_addr}; else if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi; fi;

注意到 loadfdt,推测这应该就是load dts文件脚本,转到 loadfdt

fatload=mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}

还真是兄弟,直接改一手:dtb名字自己确定

setenv fatload "tftp 0x83000000 imx6ull-gdm-emmc.dtb"
saveenv

那么就ok啦,直接参考上面那篇文章网络传输dtb吧!

下载启动后,查看

cd /proc/device-tree/mykey
ls

确有此节点并且属性与设备树文件里的一致。

4. ioctl介绍

4.1. 为什么需要 ioctl?

字符设备驱动一般会提供 read、write 接口,而 read/write 主要用于:

但驱动经常有一些“不是读写”的功能,比如:

这些事情不是数据流,而是 命令
read/write 是拿来传数据的,不适合传“指令”。

于是内核提供了一个专门用于“发送命令”的接口:
ioctl:I/O control(输入输出控制)

它的作用就是:
用户程序给驱动发送一个命令,驱动执行对应的动作。

4.2. ioctl 的模型

把驱动想象成一部“电器”,比如一个空调,你可以:

也就是说:
ioctl 就是“控制面板上的按钮”。
用户程序通过 ioctl 给驱动发命令,驱动根据不同命令执行操作。

4.3. ioctl 函数的原型

驱动里必须实现一个函数:

long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);

参数解释:

4.4. cmd 是怎么设计的?

魔数 + 命令号 + 数据类型。

Linux 使用宏来构造 ioctl 命令:

这些宏会构造命令码,并把隐藏信息编码进去。
例如:

#define KEY_IOCTL_MAGIC 'K'
#define KEY_GET_VALUE   _IOWR(KEY_IOCTL_MAGIC, 0x01, int)

展开后,命令码包含信息:

内容 含义
魔数 'K' 标识该命令属于 Key 驱动
命令号 0x01 这条命令是第一个功能
操作方向 IOWR 读写用户空间
类型 int

这样做能保证不同驱动不会冲突。

4.5. arg 参数是什么?

看函数原型:

long unlocked_ioctl(..., unsigned long arg);

arg 是一个“数字”,但为了传数据,通常我们让用户传 指针

用户态:

int key_id = 0; ioctl(fd, KEY_GET_VALUE, &key_id);

驱动态:

copy_from_user(&key_id, (int *)arg, sizeof(int));

也就是说:
arg = 用户空间的数据地址
驱动读/写这个地址完成数据交互——也就是说,我把一个数据通过这个变量传入进驱动,驱动把要返回的数据通过这个变量再传回来

4.6. ioctl 内部流程

假设你要读某个按键:
用户程序:

int key_id = 2;
ioctl(fd, KEY_GET_VALUE, &key_id);

驱动里发生的事情:

  1. 内核把命令放进 unlocked_ioctl
  2. 驱动判断 cmd 是哪个指令
  3. 使用 copy_from_user 获取用户传来的 key_id
  4. 根据 key_id 获取按键值
  5. 使用 copy_to_user 把按键值写回 arg 指向的用户变量

最终用户程序里的 key_id 变成:key2 的值

4.7. ioctl 是如何区分命令的?

驱动里会写:

switch (cmd) {
case KEY_GET_VALUE:
...
case KEY_SET_MODE:
...
}

cmd 就像电话打进来时的“分机号”,告诉驱动要执行哪个功能。

4.8. 小结

接口 用途
read 获取数据流
write 发送数据流
ioctl 执行控制命令

ioctl 原理:

  1. 用户程序调用 ioctl(fd, cmd, arg)
  2. 内核把 cmd 和 arg 传给驱动的 unlocked_ioctl
  3. 驱动根据 cmd 执行不同操作
  4. arg 用于传入参数或返回结果
  5. 命令码用魔数+编号生成,防止冲突

5. 编写实验代码

代码下载


共计约1.8k字。于2025/12/08首次发布,最后更新于2025/12/12。

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

  1. 1. 实验内容
  2. 2. 查询手册并配置Pinctrl、GPIO、dts
  3. 3. 验证:使用ftp把dts传到开发板
  4. 4. ioctl介绍
    1. 4.1. 为什么需要 ioctl?
    2. 4.2. ioctl 的模型
    3. 4.3. ioctl 函数的原型
    4. 4.4. cmd 是怎么设计的?
    5. 4.5. arg 参数是什么?
    6. 4.6. ioctl 内部流程
    7. 4.7. ioctl 是如何区分命令的?
    8. 4.8. 小结
  5. 5. 编写实验代码