Linux驱动开发中的常用接口总结(一)

总结驱动开发中的常用接口

内核错误码

内核和应用共用一套基础错误码数字,但用法有区别。

  • 错误号的数值(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 会处理成:

  • 返回 -1
  • errno = 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);

✔ 优点:

  • 自动带 eth0 sdio i2c 设备名
  • 看 dmesg 一眼知道是谁打的
  • 写驱动、看驱动 90% 都用这个!

三、专门给网络驱动用的打印(你会大量看到)

你看 dm9000、WiFi、以太网驱动 会遇到:

1. netdev_err

复制代码
netdev_err(dev, "tx timeout\n");

2. netdev_info

复制代码
netdev_info(dev, "link up\n");

四、最简单分类记忆(背这个)

1. 通用内核打印

  • printk
  • pr_info
  • pr_err
  • pr_warn
  • pr_debug

2. 带设备信息(推荐)

  • dev_info
  • dev_err
  • dev_warn
  • dev_dbg

3. 网络驱动专用

  • netdev_err
  • netdev_info

五、驱动里最常用的 3 个(你必须记住)

这 3 个占了驱动打印的 90%

  1. dev_info → 正常信息
  2. dev_err → 错误
  3. 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);

一步完成:

  1. 获取资源
  2. 检查资源
  3. ioremap 映射
  4. 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分配普通内存

这是最常用的情况,比如给网络驱动分配一个数据缓冲区来描述数据包。

  • 主力函数:kmallockzalloc

    这是分配小块内存的首选,分配出的内存在物理地址和虚拟地址上都是连续的。对驱动来说,这非常重要,因为很多硬件设备都要求物理地址连续。kzallockmalloc 的安全版本,它会把分配的内存全部清零,可以避免读到内核遗留的"脏数据",是强烈推荐的做法。

    复制代码
    // 分配一个 256 字节的缓冲区,并自动清零
    void *buffer = kzalloc(256, GFP_KERNEL); 
    if (!buffer) {
        // 处理内存分配失败的情况
        return -ENOMEM;
    }
    // ... 使用 buffer ...
    kfree(buffer); // 使用完毕后,务必释放
  • 关键标志位:GFP_KERNEL vs GFP_ATOMIC

    这是新手最容易踩的坑,它决定了内存分配的"行为模式"。

    • GFP_KERNEL标准模式 。在进程上下文(比如驱动的 writeread 系统调用)中使用。如果内存紧张,它会主动让出CPU,等待内存回收,可能会睡眠 。因此,绝对不能在中断处理函数、自旋锁等原子上下文中使用。

    • GFP_ATOMIC原子模式 。用于上述的原子上下文中。分配过程不会睡眠,但因此成功率更低,应作为紧急情况下的后备选择。

  • 大内存分配:vmalloc

    当你需要分配一块很大的、但物理上可以不连续的内存时使用(例如加载一个很大的内核模块)。它只保证虚拟地址连续。由于内部映射开销较大,性能不如 kmalloc,通常不用于DMA传输。

    复制代码
    void *large_buf = vmalloc(1024 * 1024); // 分配 1MB
    // ...
    vfree(large_buf);

⚡️ 场景二:为DMA设备分配内存

当你的网卡(如 dm9000)需要通过DMA直接往内存里读写数据时,就必须使用这类接口。

  • 主力函数:dma_alloc_coherent

    DMA操作有一个核心痛点:缓存一致性(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/writebreadl/writel 等函数,而不是直接用 * 操作符,以确保操作的顺序和宽度正确。

🔧 内存屏障

有时候,为了让内存的读写操作严格按照你代码的顺序执行,需要使用内存屏障。这在驱动中通常有两个目的:

  1. 保证顺序:防止CPU编译器和处理器对I/O操作进行重排序。

  2. 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_KERNELGFP_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

  • 释放 kmalloc

3. 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);
  1. 上报事件(最重要!)

硬件触发(中断 /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_device

2. 事件设置

复制代码
__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 条铁律

  1. 上报事件后必须调用 input_sync (),否则上层收不到
  2. 必须用 __set_bit() 告诉内核你支持哪些事件
  3. 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 设备
  • 第二个参数:你自己的私有数据指针
相关推荐
草莓熊Lotso1 小时前
Vibe Coding 时代:LangChain 与 LangGraph 全链路解析
linux·运维·服务器·数据库·人工智能·mysql·langchain
蜡台7 小时前
Python包管理工具pip完全指南-----2
linux·windows·python
^—app5668667 小时前
游戏运存小启动不起来临时解决方法
运维·服务器
Ujimatsu7 小时前
虚拟机安装Debian 13.x及其常用软件(2026.4)
linux·运维·ubuntu
千百元7 小时前
zookeeper启不来了
linux·zookeeper·debian
志栋智能8 小时前
超自动化安全:构建智能安全运营的核心引擎
大数据·运维·服务器·数据库·安全·自动化·产品运营
AnalogElectronic9 小时前
linux 测试网络和端口是否连通的命令详解
linux·网络·php
Edward1111111110 小时前
4月28日防火墙问题
linux·运维·服务器
想学后端的前端工程师10 小时前
【补充内外网突然不通的情况】
运维·服务器
面汤放盐10 小时前
何时使用以及何时不应使用微服务:没有银弹
java·运维·云计算