总结驱动开发中的常用接口
内核错误码
内核和应用共用一套基础错误码数字,但用法有区别。
- 错误号的数值(EINVAL=22、ENOMEM=12 等)内核和应用完全一样
- 但返回方式不一样 :
- 应用层:直接返回正整数
- 内核层:返回负的错误码 (
-EINVAL、-ENOMEM)
1. 错误码定义在哪?
基础错误码都在这:
include/uapi/asm-generic/errno-base.h include/uapi/linux/errno.h这些是 uapi = user api,意思就是:给内核 + 用户态共同使用的标准定义
所以:
- 应用
#include <errno.h>- 内核
#include <linux/errno.h>看到的
EINVAL都是 22,一模一样。
2. 关键区别:返回时带不带负号
应用层(用户态 C 语言)
系统调用失败时,库函数会把错误码放到全局变量
errno里,是正数:
if (open(...) < 0) { printf("%d\n", errno); // 输出 22 这种正数 }内核态(驱动、内核函数)
直接返回负的错误码:
return -EINVAL; // 实际返回 -22 return -ENOMEM; // 返回 -12内核返回
-EINVAL→ 到了应用层 libc 会处理成:
- 返回
-1errno = EINVAL(22)
3. 所以可以理解成:
- 错误码编号:内核 ↔ 应用完全通用
- 内核返回负值,应用看到的 errno 是正值
4. 你在 WiFi 驱动里常见的对应关系
内核里写 实际值 应用 errno 含义 -EINVAL -22 EINVAL=22 无效参数 -ENOMEM -12 ENOMEM=12 内存不足 -EBUSY -16 EBUSY=16 设备忙 -ETIMEDOUT -110 ETIMEDOUT=110 超时 -EIO -5 EIO=5 I/O 错误 -ENODEV -19 ENODEV=19 无此设备
一句话记住:
内核和应用共用同一套错误号, 内核返回负的,应用层 errno 是正的。
IS_ERR/PTR_ERR等错误处理
Linux 内核 指针错误处理全套接口(终极总结)
我给你整理驱动里 100% 会遇到 的所有指针错误处理宏 / 函数 ,你截图里的
IS_ERR / PTR_ERR只是其中两个。这些是内核通用 ,不是某个子系统专属,platform、i2c、spi、gpio、regulator、net 全都用这套!
一、内核为什么要用这套错误处理?
内核很多函数返回 指针 (
void*/struct xxx*)但又想返回 错误码(负数:-ENOMEM、-EINVAL、-EPROBE_DEFER)于是内核设计了一套规则:
- 正常指针 = 有效地址
- 错误指针 = 把错误码编码进指针(特殊地址)
所以必须用专用宏判断、提取。
二、全套 5 个核心错误处理宏(驱动必背)
1. IS_ERR(ptr)
判断指针是不是错误指针
if (IS_ERR(ptr)) { // 出错了 }2. PTR_ERR(ptr)
从错误指针里提取错误码(负数)
int err = PTR_ERR(ptr);3. ERR_PTR(err)
把错误码变成错误指针
return ERR_PTR(-ENOMEM);4. IS_ERR_OR_NULL(ptr)
指针是 NULL 或者错误指针 → 都算失败
if (IS_ERR_OR_NULL(ptr)) return -EINVAL;5. ERR_CAST(ptr)
强制类型转换错误指针(很少用)
三、最常用组合(驱动 99% 都是这样)
标准写法 1:获取资源失败处理
struct gpio_desc *gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(gpiod)) { // 是不是错误指针? int err = PTR_ERR(gpiod); // 拿出错误码 return err; }标准写法 2:返回错误指针
if (!reg) return ERR_PTR(-ENODEV); // 把错误码变指针标准写法 3:判断 NULL + 错误
if (IS_ERR_OR_NULL(ptr)) return -EINVAL;
四、你截图里的代码(就是这套)
if (IS_ERR(power)) { if (PTR_ERR(power) == -EPROBE_DEFER) return -EPROBE_DEFER; dev_dbg(...); }
IS_ERR→ 判断是不是错误PTR_ERR→ 拿出错误码- 检查是不是
-EPROBE_DEFER(probe 延迟)
五、常见错误码(你会大量看到)
这些是
PTR_ERR拿出来的最常见错误:
-ENOMEM 内存不足 -EINVAL 参数无效 -ENODEV 无此设备 -EBUSY 设备忙 -EPROBE_DEFER 依赖未就绪(最常见于电源、时钟) -ETIMEDOUT 超时 -EIO IO错误
六、极简总结(背这 4 个就够)
IS_ERR 是不是错误指针 PTR_ERR 拿出错误码 ERR_PTR 把错误码变指针 IS_ERR_OR_NULL 错误或空
内核中的打印接口
一、最核心、最常用的 5 个打印接口
驱动里 99% 都是这 5 个!
1. printk ------ 最原始、最通用
printk("<0>Hello World\n");
- 内核最基础打印
- 可以指定日志级别
- 任何驱动、任何地方都能用
2. pr_info ------ 最常用(正常信息)
pr_info("probe ok\n");
- 等价于
printk(KERN_INFO "...")- 驱动正常流程打印
3. pr_err ------ 最常用(错误打印)
pr_err("failed to request gpio\n");
- 等价于
printk(KERN_ERR "...")- 出错时必用
4. pr_warn ------ 警告
pr_warn("gpio not found\n");5. pr_debug ------ 调试打印(默认不输出)
pr_debug("enter %s\n", __func__);
- 需要开 DEBUG 才会输出
二、带设备名的打印(驱动最推荐)
比 pr_info 更好,会自动显示设备名,看 log 非常清晰!
1. dev_info
dev_info(dev, "probe success\n");2. dev_err
dev_err(dev, "reset gpio failed\n");3. dev_warn
dev_warn(dev, "no irq\n");4. dev_dbg
dev_dbg(dev, "read reg = %x\n", val);✔ 优点:
- 自动带
eth0sdioi2c设备名- 看 dmesg 一眼知道是谁打的
- 写驱动、看驱动 90% 都用这个!
三、专门给网络驱动用的打印(你会大量看到)
你看 dm9000、WiFi、以太网驱动 会遇到:
1. netdev_err
netdev_err(dev, "tx timeout\n");2. netdev_info
netdev_info(dev, "link up\n");
四、最简单分类记忆(背这个)
1. 通用内核打印
printkpr_infopr_errpr_warnpr_debug2. 带设备信息(推荐)
dev_infodev_errdev_warndev_dbg3. 网络驱动专用
netdev_errnetdev_info
五、驱动里最常用的 3 个(你必须记住)
这 3 个占了驱动打印的 90%:
- dev_info → 正常信息
- dev_err → 错误
- pr_err → 没有 device 指针时用
六、超级实用小技巧(驱动里到处都是)
__func__ // 打印函数名 __LINE__ // 打印行号例子:
dev_err(dev, "%s line %d: error\n", __func__, __LINE__);更多待补充
platform平台获取资源的接口
一、先记住一句话
platform 设备的资源 = 设备树里的 reg /interrupt/clocks 等硬件信息 platform 获取资源接口 = 驱动从设备树把这些信息读出来
二、最核心、最常用的 5 个接口(99% 驱动用这些)
1. platform_get_resource ------ 获取任意资源(最通用)
struct resource *res = platform_get_resource(pdev, type, index);用途: 获取 reg、内存、IO、总线地址 等
常用 type:
IORESOURCE_MEM→ 设备寄存器地址(reg)IORESOURCE_IRQ→ 中断号IORESOURCE_IO→ IO 端口例子:
// 获取第0段寄存器地址 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取第1个中断 res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
2. platform_get_irq ------ 专门获取中断(最常用)
int irq = platform_get_irq(pdev, index);最简单、最推荐的获取中断方式!
3. resource_size ------ 获取资源长度(reg 大小)
resource_size_t size = resource_size(res);你截图里就有这个!
4. platform_get_resource_byname ------ 按名字获取资源
struct resource *res = platform_get_resource_byname(pdev, type, "name");
5. devm_platform_get_and_ioremap_resource ------ 获取 + 映射 二合一(现代驱动首选)
void __iomem *base = devm_platform_get_and_ioremap_resource(pdev, index, &res);一步完成:
- 获取资源
- 检查资源
- ioremap 映射
- devm 自动管理释放
三、完整列表(所有 platform 资源接口)
1. 获取资源
platform_get_resource(pdev, type, index); // 通用 platform_get_resource_byname(pdev, type, name); // 按名字 platform_get_irq(pdev, index); // 中断专用 platform_get_irq_byname(pdev, name); // 按名字获取中断2. 获取 + 映射(现代驱动推荐)
devm_platform_get_and_ioremap_resource(pdev, index, res); devm_ioremap_resource(dev, res); // 自己获取res后映射3. 辅助函数
resource_size(res); // 获取资源长度
四、最常用组合(你截图里的标准流程)
// 1. 获取内存资源(reg) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 2. 获取长度 size = resource_size(res); // 3. 映射(老驱动) request_mem_region(res->start, size, "name"); base = ioremap(res->start, size); // 现代驱动直接一步到位 base = devm_ioremap_resource(dev, res);
五、最简单记忆口诀
获取资源用 platform_get_resource 获取中断用 platform_get_irq 获取长度用 resource_size 获取+映射用 devm_ioremap_resource
platform_set_drvdata / platform_get_drvdata
一句话讲透 platform_set_drvdata / platform_get_drvdata
作用:把你自己定义的结构体,"绑在" platform 设备上,方便随时取用
这是 platform 驱动最核心、最常用、几乎每个驱动都有 的一对接口。
1. 它到底是干嘛的?
你写驱动一定会定义一个 私有数据结构体,比如:
struct my_drv_data { void __iomem *base; // 寄存器基地址 int irq; // 中断号 struct net_device *ndev; int reset_gpio; };这个结构体 在 probe 里创建 ,但 open/read/write 等函数里也要用。
问题:别的函数拿不到这个结构体指针怎么办?
答案:用 platform_set_drvdata 把它存进 pdev 里! 用 platform_get_drvdata 随时取出来!
2. 函数原型
// 保存(存进去) void platform_set_drvdata(struct platform_device *pdev, void *data); // 取出(拿回来) void *platform_get_drvdata(struct platform_device *pdev);
3. 最经典使用流程(所有 platform 驱动都这样)
① probe 函数里:创建 → 保存
static int my_probe(struct platform_device *pdev) { // 1. 分配私有数据 struct my_drv_data *drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); // 2. 初始化各种资源 drvdata->base = devm_ioremap_resource(...); drvdata->irq = platform_get_irq(...); // 3. 把私有数据 存进 pdev platform_set_drvdata(pdev, drvdata); // <--- 核心 return 0; }② 其他函数里:取出使用
static int my_remove(struct platform_device *pdev) { // 从 pdev 里取出之前存的结构体 struct my_drv_data *drvdata = platform_get_drvdata(pdev); // 直接使用 dev_info(&pdev->dev, "irq = %d\n", drvdata->irq); return 0; }
4. 超级通俗比喻
platform_set_drvdata = 给设备贴一个 "便利贴"
- 便利贴上写着你的所有私有数据(地址、中断、GPIO 等)
- 任何函数拿到 pdev,就能撕下便利贴,拿到所有数据
5. 为什么必须用它?
驱动的生命周期函数:
- probe
- remove
- interrupt
- suspend
- resume
它们都只给你 pdev,不给你私有结构体 所以你必须把结构体 存在 pdev 身上。
6. 最终极简总结
platform_set_drvdata(pdev, ptr); // 把我的数据存进设备 platform_get_drvdata(pdev); // 把我的数据取出来这是 platform 驱动的 "数据传递神器"。
常见的内存操作接口
关于内存分配接口
🎯 场景一:为CPU分配普通内存
这是最常用的情况,比如给网络驱动分配一个数据缓冲区来描述数据包。
主力函数:
kmalloc与kzalloc这是分配小块内存的首选,分配出的内存在物理地址和虚拟地址上都是连续的。对驱动来说,这非常重要,因为很多硬件设备都要求物理地址连续。
kzalloc是kmalloc的安全版本,它会把分配的内存全部清零,可以避免读到内核遗留的"脏数据",是强烈推荐的做法。
// 分配一个 256 字节的缓冲区,并自动清零 void *buffer = kzalloc(256, GFP_KERNEL); if (!buffer) { // 处理内存分配失败的情况 return -ENOMEM; } // ... 使用 buffer ... kfree(buffer); // 使用完毕后,务必释放关键标志位:
GFP_KERNELvsGFP_ATOMIC这是新手最容易踩的坑,它决定了内存分配的"行为模式"。
GFP_KERNEL:标准模式 。在进程上下文(比如驱动的write、read系统调用)中使用。如果内存紧张,它会主动让出CPU,等待内存回收,可能会睡眠 。因此,绝对不能在中断处理函数、自旋锁等原子上下文中使用。
GFP_ATOMIC:原子模式 。用于上述的原子上下文中。分配过程不会睡眠,但因此成功率更低,应作为紧急情况下的后备选择。大内存分配:
vmalloc当你需要分配一块很大的、但物理上可以不连续的内存时使用(例如加载一个很大的内核模块)。它只保证虚拟地址连续。由于内部映射开销较大,性能不如
kmalloc,通常不用于DMA传输。
void *large_buf = vmalloc(1024 * 1024); // 分配 1MB // ... vfree(large_buf);⚡️ 场景二:为DMA设备分配内存
当你的网卡(如 dm9000)需要通过DMA直接往内存里读写数据时,就必须使用这类接口。
主力函数:
dma_alloc_coherentDMA操作有一个核心痛点:缓存一致性(Cache Coherency) 。CPU的缓存和DMA直接操作的内存,内容可能不一致。这个函数分配的内存,会确保CPU和DMA设备看到的内容始终一致,免去了你手动处理缓存的麻烦。
c
dma_addr_t dma_handle; // 分配一个DMA缓冲区,cpu_addr是CPU使用的虚拟地址 // dma_handle是设备使用的物理总线地址 void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); // ... 将 dma_handle 告诉硬件,然后进行DMA传输 ... dma_free_coherent(dev, size, cpu_addr, dma_handle);🗺️ 场景三:访问硬件寄存器 (MMIO)
驱动的本质是控制硬件,而控制硬件就是读写它的寄存器。在Linux下,由于内存管理单元(MMU)的存在,必须先建立"映射"才能访问。
主力函数:
ioremap硬件寄存器有它自己的物理地址(比如
0x10002000)。ioremap的作用就是把这个物理地址映射到内核的虚拟地址空间,之后你就可以像操作普通内存一样,用指针读写它了。
// 假设从数据手册查到某个设备寄存器的物理基址是 0x10002000,长度是 4KB #define REG_BASE_PHYS 0x10002000 #define REG_SIZE 0x1000 void __iomem *reg_base; // 在驱动初始化时进行映射 reg_base = ioremap(REG_BASE_PHYS, REG_SIZE); if (!reg_base) { return -EIO; } // 使用 readl/writel 等辅助函数来读写寄存器 u32 val = readl(reg_base + 0x10); // 读偏移 0x10 的寄存器 writel(val | 0x1, reg_base + 0x00); // 写偏移 0x00 的寄存器 // 在驱动卸载时解除映射 iounmap(reg_base);注意操作MMIO区域时,推荐使用内核提供的
readb/writeb、readl/writel等函数,而不是直接用*操作符,以确保操作的顺序和宽度正确。🔧 内存屏障
有时候,为了让内存的读写操作严格按照你代码的顺序执行,需要使用内存屏障。这在驱动中通常有两个目的:
保证顺序:防止CPU编译器和处理器对I/O操作进行重排序。
DMA同步 :在启动DMA传输前,使用
dma_wmb()确保所有的描述符和数据都已经真正写入内存。类似地,在读取DMA传输完的数据前,使用dma_rmb()。
// 确保前面的数据写入都完成,再通知硬件开始DMA dma_wmb(); writel(CMD_START, reg_base + CMD_REG);📝 小结与最佳实践
你的需求是什么? 应该使用的API 一句话提醒 分配常规小内存 kzalloc/kfree记得使用 GFP_KERNEL还是GFP_ATOMIC分配大块虚拟内存 vmalloc/vfree性能略低,不用于DMA 分配DMA一致性内存 dma_alloc_coherent/dma_free_coherent解决了让人头疼的缓存一致性问题 访问硬件寄存器 ioremap/iounmap配合 readl/writel使用保证内存操作顺序 dma_wmb/dma_rmb启动DMA前和接收数据后特别重要 对于新手来说,可以记住这几个核心原则:
尽量使用
kzalloc而不是kmalloc。时刻注意自己处于进程上下文还是中断上下文,以此来选择
GFP_KERNEL或GFP_ATOMIC。所有分配的内存都必须在退出时释放,避免内存泄漏。
更多补充。
一、物理地址 ↔ 虚拟地址 映射(操作寄存器专用)
这是操作硬件寄存器最核心的一套接口,你截图里全是这个!
1.
request_mem_region
- 作用:申请物理地址段(占坑,防止冲突)
- 老驱动常用
2.
release_mem_region
- 作用:释放物理地址段(对应上面)
3.
ioremap
- 作用:物理地址 → 内核虚拟地址
- 驱动必须用它才能读写寄存器
4.
iounmap
- 作用:取消映射
5. devm_ioremap_resource(现代推荐!)
- 作用:三合一
- 申请地址
- 映射
- devm 自动释放
- 新驱动全部用这个!
二、内核动态内存分配(像用户态 malloc)
1.
kmalloc
最常用
分配物理连续内存
用于小内存(一般 < 128KB)
buf = kmalloc(size, GFP_KERNEL);
2.
kfree
- 释放
kmalloc3.
devm_kmalloc
- 设备托管,自动释放
- 驱动最推荐
4.
vzalloc
- 分配大内存
- 物理地址可以不连续
三、IO 内存读写(操作寄存器用)
映射完地址后,用这些函数读写寄存器:
1.
readl/readw/readb
- 读 32bit / 16bit / 8bit 寄存器
2.
writel/writew/writeb
- 写 32bit / 16bit / 8bit 寄存器
3.
__raw_readl/__raw_writel
- 无内存屏障版本(更快,但要注意顺序)
四、Resource 资源获取(平台驱动专用)
你前面看到的
platform_get_resource属于这一类:1.
platform_get_resource
- 获取 reg /irq 资源
2.
resource_size
- 获取资源长度
3.
platform_get_irq
- 获取中断号
五、内存拷贝(内核态常用)
1.
memcpy
- 普通拷贝
2.
memset
- 清零 / 设置值
3.
memmove
- 安全拷贝
4.
dma_memcpy
- DMA 专用拷贝
六、DMA 相关内存(网络 / WiFi/SDIO 大量用)
1.
dma_alloc_coherent
- 分配 DMA 可用内存
2.
dma_free_coherent
- 释放
3.
dma_map_single
- DMA 映射
七、最最重要的一张表(背这个!)
接口 用途 场景 ioremap 物理地址转虚拟 操作寄存器 devm_ioremap_resource 申请 + 映射 + 自动释放 现代驱动首选 request_mem_region 申请物理地址 老驱动 kmalloc/devm_kmalloc 内核动态内存 申请缓冲区 readl/writel 寄存器读写 操作硬件 platform_get_resource 获取设备资源 平台驱动 memcpy/memset 内存操作 数据处理 更多待补充。
电源管理子系统
Regulator 子系统常用接口(你会在驱动里反复看到)
接口 作用 场景 devm_regulator_get(dev, name)获取 regulator 句柄(推荐) 驱动 probe 阶段 regulator_get(dev, name)非托管版本,需手动 regulator_put老驱动 regulator_enable(reg)上电 硬件工作前 regulator_disable(reg)下电 硬件休眠 / 卸载 regulator_get_voltage(reg)获取当前电压 调试 / 电压检查 regulator_set_voltage(reg, min_uV, max_uV)设置电压 需调压的硬件 IS_ERR/PTR_ERR错误处理 所有内核接口通用
关键知识点补充
1. 为什么用
devm_regulator_get?
- 自动托管,驱动卸载时自动
regulator_put,彻底避免资源泄漏- 现代驱动必须用
devm_版本 ,老驱动的regulator_get已不推荐2.
-EPROBE_DEFER为什么重要?
- 嵌入式系统中,电源 regulator 可能由其他驱动(如 PMIC 驱动)提供
- 如果 PMIC 驱动还没加载完,网卡驱动先 probe 就会拿不到电源,直接失败
- 返回
-EPROBE_DEFER让内核稍后重试,直到 PMIC 就绪,保证驱动正常加载3. 设备树对应写法
这段代码对应的设备树节点通常是:
dm9000@10000000 { compatible = "davicom,dm9000"; reg = <0x10000000 0x100>; vcc-supply = <&vcc_3v3>; // 对应 "vcc" };
vcc_3v3是板级定义的 regulator(比如 PMIC 的 3.3V 电源轨)。
input子系统
一、Input 子系统是干嘛的?
专门管理:按键、触摸屏、鼠标、键盘、遥控器、游戏手柄...... 它把硬件事件(按下、松开、坐标)上报给 Linux 内核 → 再给上层应用(Android、Linux 桌面)
上层只需要读
/dev/input/eventX就能拿到按键事件。
二、标准驱动流程(5 步走,所有 Input 驱动都一样)
1. 分配 input_dev 结构体 2. 设置支持哪些事件(按键、坐标、力反馈等) 3. 注册 Input 设备 4. 硬件触发时:上报事件(按下/松开) 5. 卸载时:注销设备
三、核心接口大汇总(全部在这里)
1. 分配 / 释放 input_dev
// 分配 struct input_dev *input_allocate_device(void); // 释放(自动 devm 版本,推荐) void input_free_device(struct input_dev *dev);2. 设置事件类型(最关键)
你必须告诉内核你支持哪些事件:
// 支持按键事件 __set_bit(EV_KEY, input->evbit); // 支持某个按键(如 KEY_POWER、KEY_VOLUMEDOWN) __set_bit(KEY_POWER, input->keybit);常用事件类型:
EV_KEY按键EV_ABS绝对值(触摸屏 X/Y)EV_REL相对值(鼠标)EV_SYN同步(内核自动用)3. 注册 Input 设备
int input_register_device(struct input_dev *dev);4. 卸载注销
void input_unregister_device(struct input_dev *dev);
- 上报事件(最重要!)
硬件触发(中断 /poll)时调用:
// 上报按键 input_report_key(struct input_dev *dev, unsigned int code, int value); // 上报绝对坐标(触摸屏) input_report_abs(struct input_dev *dev, unsigned int axis, int value); // 上报同步(必须!表示一帧事件结束) input_sync(struct input_dev *dev);上报必须 + sync!
四、最常用全套 API(你 99% 会遇到)
1. 设备创建
input_allocate_device input_register_device input_unregister_device input_free_device2. 事件设置
__set_bit(EV_KEY, dev->evbit); __set_bit(KEY_0, dev->keybit); __set_bit(KEY_1, dev->keybit);3. 事件上报
input_report_key(dev, code, val); // 1=按下 0=松开 input_report_abs(dev, ABS_X, x); // 触摸屏X input_report_abs(dev, ABS_Y, y); // 触摸屏Y input_sync(dev); // 提交事件4. 轮询 / 中断专用
input_event(dev, type, code, value); //通用上报
五、标准 Input 驱动完整模板(可直接编译)
这就是按键驱动最标准写法,所有开发板都这么写:
#include <linux/input.h> struct input_dev *input; // 1. probe 里初始化 int probe(struct platform_device *pdev) { // 分配 input = input_allocate_device(); input->name = "test_key"; input->phys = "key/input0"; // 设置支持的事件:按键 __set_bit(EV_KEY, input->evbit); __set_bit(KEY_POWER, input->keybit); // 支持电源键 // 注册 input_register_device(input); platform_set_drvdata(pdev, input); return 0; } // 2. 按键中断里上报 static irqreturn_t key_irq(int irq, void *data) { int val = gpio_get_value(gpio); // 上报按键 input_report_key(input, KEY_POWER, !val); input_sync(input); // 必须同步 return IRQ_HANDLED; }
六、你必须记住的 3 条铁律
- 上报事件后必须调用 input_sync (),否则上层收不到
- 必须用
__set_bit()告诉内核你支持哪些事件- Input 驱动只做一件事:硬件事件 → 上报给内核
input_set_drvdata /input_get_drvdata
这对函数 专门给 Input 子系统用 ,作用和我们之前学的
platform_set_drvdata几乎一模一样!我用最简单、最直白的方式给你讲透。
一、一句话结论
input_set_drvdata = 把你的私有数据,绑在 input_dev 上
input_get_drvdata = 从 input_dev 上把私有数据取回来
它是 Input 子系统专属的数据传递工具。
二、函数原型
void input_set_drvdata(struct input_dev *dev, void *data); void *input_get_drvdata(struct input_dev *dev);非常简单:
- 第一个参数:input 设备
- 第二个参数:你自己的私有数据指针