《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 20 章 Linux 输入设备驱动

《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》

第 20 章 Linux 输入设备驱动

参考:宋宝华 著,机械工业出版社,2015年版


20.1 输入设备驱动简介

20.1.1 输入子系统的概念

Linux 输入子系统(Input Subsystem)是专门为人机交互输入设备设计的驱动框架,统一管理键盘、鼠标、触摸屏、游戏手柄、传感器等各类输入设备:

复制代码
输入子系统的设计目标:

问题:输入设备种类繁多,接口各异
  - 键盘:PS/2、USB、I2C
  - 鼠标:PS/2、USB、蓝牙
  - 触摸屏:I2C、SPI、USB
  - 游戏手柄:USB、蓝牙

解决方案:输入子系统提供统一的框架
  ✓ 驱动只需上报事件,无需关心如何传递给应用程序
  ✓ 应用程序通过统一的 /dev/input/eventN 接口读取事件
  ✓ 支持事件过滤、转换、合并等处理
  ✓ 支持热插拔(USB 设备插入时自动注册)

20.1.2 输入子系统的层次结构

复制代码
Linux 输入子系统架构:

┌─────────────────────────────────────────────────────────────┐
│                      用户空间                                │
│  应用程序读取 /dev/input/event0、event1...                   │
│  Qt / GTK / Android InputReader / evtest                    │
└──────────────────────────┬──────────────────────────────────┘
                           │ read(fd, &event, sizeof(event))
┌──────────────────────────▼──────────────────────────────────┐
│                   事件处理层(Event Handler)                 │
│  evdev(通用事件接口)← 最常用,/dev/input/eventN           │
│  mousedev(鼠标接口)← /dev/input/mice                      │
│  keybdev(键盘接口)← 已废弃                                │
│  joydev(游戏手柄接口)← /dev/input/jsN                    │
└──────────────────────────┬──────────────────────────────────┘
                           │ input_event()
┌──────────────────────────▼──────────────────────────────────┐
│                   输入核心层(Input Core)                    │
│  input_register_device() / input_unregister_device()        │
│  input_report_key() / input_report_abs() / input_sync()     │
│  事件分发、设备管理                                          │
└──────────────────────────┬──────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────┐
│                   输入设备驱动层                              │
│  键盘驱动 / 触摸屏驱动 / 鼠标驱动 / 传感器驱动              │
│  通过 input_dev 结构体向核心层上报事件                       │
└──────────────────────────┬──────────────────────────────────┘
                           │ 操作硬件
┌──────────────────────────▼──────────────────────────────────┐
│                   硬件层                                     │
│  键盘矩阵 / 触摸屏控制器 / 鼠标传感器 / 加速度计            │
└─────────────────────────────────────────────────────────────┘

20.1.3 输入事件类型

Linux 输入子系统定义了多种事件类型,每种类型对应不同的输入数据:

c 复制代码
/* 事件类型(input_event.type)*/
#define EV_SYN      0x00    /* 同步事件(标志一组事件的结束)*/
#define EV_KEY      0x01    /* 按键事件(键盘、按钮)*/
#define EV_REL      0x02    /* 相对坐标事件(鼠标移动)*/
#define EV_ABS      0x03    /* 绝对坐标事件(触摸屏、摇杆)*/
#define EV_MSC      0x04    /* 杂项事件 */
#define EV_SW       0x05    /* 开关事件(耳机插拔等)*/
#define EV_LED      0x11    /* LED 事件(键盘 LED)*/
#define EV_SND      0x12    /* 声音事件(蜂鸣器)*/
#define EV_REP      0x14    /* 重复事件(按键重复)*/
#define EV_FF       0x15    /* 力反馈事件 */
#define EV_PWR      0x16    /* 电源事件 */
#define EV_FF_STATUS 0x17   /* 力反馈状态事件 */

/* EV_KEY 事件的 code(按键码)*/
#define KEY_ESC         1
#define KEY_1           2
#define KEY_2           3
/* ... */
#define KEY_ENTER       28
#define KEY_LEFTCTRL    29
#define KEY_A           30
/* ... */
#define BTN_LEFT        0x110   /* 鼠标左键 */
#define BTN_RIGHT       0x111   /* 鼠标右键 */
#define BTN_MIDDLE      0x112   /* 鼠标中键 */
#define BTN_TOUCH       0x14A   /* 触摸事件 */

/* EV_ABS 事件的 code(绝对坐标轴)*/
#define ABS_X           0x00    /* X 轴 */
#define ABS_Y           0x01    /* Y 轴 */
#define ABS_Z           0x02    /* Z 轴 */
#define ABS_RX          0x03    /* X 轴旋转 */
#define ABS_RY          0x04    /* Y 轴旋转 */
#define ABS_RZ          0x05    /* Z 轴旋转 */
#define ABS_PRESSURE    0x18    /* 压力 */
#define ABS_MT_SLOT     0x2F    /* 多点触控槽位 */
#define ABS_MT_TOUCH_MAJOR 0x30 /* 触摸椭圆长轴 */
#define ABS_MT_POSITION_X  0x35 /* 多点触控 X 坐标 */
#define ABS_MT_POSITION_Y  0x36 /* 多点触控 Y 坐标 */
#define ABS_MT_TRACKING_ID 0x39 /* 触摸点追踪 ID */

/* EV_REL 事件的 code(相对坐标轴)*/
#define REL_X           0x00    /* X 轴相对移动 */
#define REL_Y           0x01    /* Y 轴相对移动 */
#define REL_WHEEL       0x08    /* 滚轮 */

20.1.4 input_event 结构体

用户空间读取到的事件格式:

c 复制代码
#include <linux/input.h>

/*
 * input_event:输入事件结构体
 * 用户空间通过 read(/dev/input/eventN) 读取此结构体
 */
struct input_event {
    struct timeval time;    /* 事件时间戳 */
    __u16 type;             /* 事件类型(EV_KEY/EV_ABS/EV_REL 等)*/
    __u16 code;             /* 事件代码(具体的键码或坐标轴)*/
    __s32 value;            /* 事件值 */
};

/*
 * value 的含义:
 * EV_KEY:0=释放,1=按下,2=重复
 * EV_ABS:绝对坐标值
 * EV_REL:相对移动量(正/负)
 * EV_SYN:0(SYN_REPORT,标志一组事件结束)
 */

/* 用户空间读取事件示例 */
int fd = open("/dev/input/event0", O_RDONLY);
struct input_event ev;
while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
    if (ev.type == EV_KEY) {
        printf("按键事件:code=%u, value=%d\n", ev.code, ev.value);
    } else if (ev.type == EV_ABS) {
        printf("绝对坐标:code=%u, value=%d\n", ev.code, ev.value);
    } else if (ev.type == EV_SYN) {
        printf("同步事件\n");
    }
}

20.2 输入设备驱动的结构

20.2.1 input_dev 结构体

c 复制代码
#include <linux/input.h>

/*
 * input_dev:输入设备描述符
 * 每个输入设备对应一个 input_dev
 */
struct input_dev {
    const char  *name;          /* 设备名称 */
    const char  *phys;          /* 设备物理路径 */
    const char  *uniq;          /* 设备唯一标识 */

    struct input_id id;         /* 设备 ID(bustype/vendor/product/version)*/

    /* 支持的事件类型位图 */
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];

    /* 支持的按键位图 */
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];

    /* 支持的相对坐标位图 */
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];

    /* 支持的绝对坐标位图 */
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];

    /* 支持的杂项事件位图 */
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];

    /* 支持的 LED 位图 */
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];

    /* 支持的开关位图 */
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

    /* 绝对坐标参数 */
    struct input_absinfo absinfo[ABS_CNT];

    /* 操作函数 */
    int (*open)(struct input_dev *dev);
    void (*close)(struct input_dev *dev);
    int (*flush)(struct input_dev *dev, struct file *file);
    int (*event)(struct input_dev *dev, unsigned int type,
                 unsigned int code, int value);

    struct device dev;          /* 内嵌设备结构体 */
    /* ... */
};

/*
 * input_absinfo:绝对坐标轴参数
 */
struct input_absinfo {
    __s32   value;      /* 当前值 */
    __s32   minimum;    /* 最小值 */
    __s32   maximum;    /* 最大值 */
    __s32   fuzz;       /* 噪声过滤(模糊值)*/
    __s32   flat;       /* 死区(中心区域)*/
    __s32   resolution; /* 分辨率(单位/毫米)*/
};

20.2.2 输入设备驱动的注册流程

复制代码
输入设备驱动注册流程:

module_init() / probe()
    ↓
1. input_allocate_device()
   ← 分配 input_dev 结构体
    ↓
2. 设置设备信息:
   dev->name = "My Input Device"
   dev->phys = "i2c/0-0038/input0"
   dev->id.bustype = BUS_I2C
    ↓
3. 设置支持的事件类型:
   set_bit(EV_KEY, dev->evbit)    ← 支持按键事件
   set_bit(EV_ABS, dev->evbit)    ← 支持绝对坐标事件
   set_bit(EV_SYN, dev->evbit)    ← 支持同步事件
    ↓
4. 设置具体的事件代码:
   set_bit(BTN_TOUCH, dev->keybit)  ← 支持触摸按键
   set_bit(ABS_X, dev->absbit)      ← 支持 X 坐标
   set_bit(ABS_Y, dev->absbit)      ← 支持 Y 坐标
    ↓
5. 设置绝对坐标范围:
   input_set_abs_params(dev, ABS_X, 0, 800, 0, 0)
   input_set_abs_params(dev, ABS_Y, 0, 480, 0, 0)
    ↓
6. input_register_device(dev)
   ← 注册到内核,创建 /dev/input/eventN
    ↓
7. 申请中断/启动轮询
   ← 等待硬件事件

module_exit() / remove()
    ↓
1. input_unregister_device(dev)
2. input_free_device(dev)

20.2.3 事件上报 API

c 复制代码
#include <linux/input.h>

/* ── 上报按键事件 ──────────────────────────────────────────── */
/*
 * input_report_key(dev, code, value)
 * code:按键码(KEY_A、BTN_TOUCH 等)
 * value:1=按下,0=释放
 */
input_report_key(dev, KEY_ENTER, 1);  /* 按下 Enter 键 */
input_report_key(dev, KEY_ENTER, 0);  /* 释放 Enter 键 */
input_report_key(dev, BTN_TOUCH, 1);  /* 触摸按下 */

/* ── 上报绝对坐标事件 ──────────────────────────────────────── */
/*
 * input_report_abs(dev, code, value)
 * code:坐标轴(ABS_X、ABS_Y、ABS_PRESSURE 等)
 * value:坐标值
 */
input_report_abs(dev, ABS_X, 320);        /* X 坐标 = 320 */
input_report_abs(dev, ABS_Y, 240);        /* Y 坐标 = 240 */
input_report_abs(dev, ABS_PRESSURE, 100); /* 压力 = 100 */

/* ── 上报相对坐标事件 ──────────────────────────────────────── */
/*
 * input_report_rel(dev, code, value)
 * code:坐标轴(REL_X、REL_Y、REL_WHEEL 等)
 * value:相对移动量(正/负)
 */
input_report_rel(dev, REL_X, 5);    /* X 方向移动 +5 */
input_report_rel(dev, REL_Y, -3);   /* Y 方向移动 -3 */
input_report_rel(dev, REL_WHEEL, 1); /* 滚轮向上 */

/* ── 同步事件(必须在一组事件结束后调用)──────────────────── */
/*
 * input_sync(dev)
 * 通知内核一组事件已完整上报
 * 内核将这组事件打包发送给用户空间
 */
input_sync(dev);

/* ── 完整的触摸事件上报示例 ────────────────────────────────── */
/* 触摸按下 */
input_report_key(dev, BTN_TOUCH, 1);
input_report_abs(dev, ABS_X, x);
input_report_abs(dev, ABS_Y, y);
input_report_abs(dev, ABS_PRESSURE, pressure);
input_sync(dev);

/* 触摸移动 */
input_report_abs(dev, ABS_X, new_x);
input_report_abs(dev, ABS_Y, new_y);
input_sync(dev);

/* 触摸释放 */
input_report_key(dev, BTN_TOUCH, 0);
input_report_abs(dev, ABS_PRESSURE, 0);
input_sync(dev);

20.2.4 多点触控(Multi-Touch)协议

Linux 输入子系统支持两种多点触控协议:

复制代码
多点触控协议 A(Type A):
  - 不追踪触摸点,每次上报所有触摸点
  - 使用 SYN_MT_REPORT 分隔不同触摸点
  - 适合不能追踪触摸点的硬件

多点触控协议 B(Type B):
  - 追踪每个触摸点(通过 slot 和 tracking_id)
  - 使用 ABS_MT_SLOT 切换当前操作的触摸点
  - 适合能追踪触摸点的硬件(如 GT911)
  - 推荐使用

协议 B 的事件序列(两个触摸点):

触摸点0按下:
  ABS_MT_SLOT = 0
  ABS_MT_TRACKING_ID = 1    ← 分配 ID(非负值表示按下)
  ABS_MT_POSITION_X = 100
  ABS_MT_POSITION_Y = 200
  SYN_REPORT

触摸点1按下:
  ABS_MT_SLOT = 1
  ABS_MT_TRACKING_ID = 2
  ABS_MT_POSITION_X = 300
  ABS_MT_POSITION_Y = 400
  SYN_REPORT

触摸点0移动:
  ABS_MT_SLOT = 0
  ABS_MT_POSITION_X = 110
  ABS_MT_POSITION_Y = 210
  SYN_REPORT

触摸点0释放:
  ABS_MT_SLOT = 0
  ABS_MT_TRACKING_ID = -1   ← -1 表示释放
  SYN_REPORT

20.3 触摸屏设备驱动实例

20.3.1 GT911 触摸屏控制器简介

GT911 是汇顶科技(Goodix)的一款电容式多点触控控制器,广泛用于嵌入式系统:

复制代码
GT911 特性:
  - 支持最多 5 点同时触控
  - I2C 接口(地址:0x5D 或 0x14)
  - 中断引脚(INT):触摸时产生中断
  - 复位引脚(RST):硬件复位
  - 支持手势识别
  - 分辨率:最大 4096×4096
  - 工作电压:1.8V/2.8V

GT911 寄存器(部分):
  0x8047:产品 ID("911\0")
  0x8048:固件版本
  0x804A:X 轴分辨率(低字节)
  0x804B:X 轴分辨率(高字节)
  0x804C:Y 轴分辨率(低字节)
  0x804D:Y 轴分辨率(高字节)
  0x814E:状态寄存器(触摸点数量)
  0x8150:触摸点0数据(8字节)
  0x8158:触摸点1数据(8字节)
  ...

触摸点数据格式(每个触摸点8字节):
  Byte 0:触摸点 ID(0~4)
  Byte 1:X 坐标低字节
  Byte 2:X 坐标高字节
  Byte 3:Y 坐标低字节
  Byte 4:Y 坐标高字节
  Byte 5:触摸面积低字节
  Byte 6:触摸面积高字节
  Byte 7:保留

20.3.2 完整的 GT911 触摸屏驱动

c 复制代码
/*
 * gt911.c ------ GT911 电容式触摸屏驱动
 *
 * 支持:
 * - 多点触控(最多5点,协议B)
 * - 中断驱动
 * - 硬件复位
 * - 设备树配置
 *
 * 参考:宋宝华《Linux设备驱动开发详解》第20章
 */

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/firmware.h>

/* GT911 寄存器地址 */
#define GT911_REG_PRODUCT_ID    0x8140  /* 产品 ID(4字节)*/
#define GT911_REG_FW_VERSION    0x8144  /* 固件版本(2字节)*/
#define GT911_REG_X_MAX         0x8048  /* X 轴最大值(2字节)*/
#define GT911_REG_Y_MAX         0x804A  /* Y 轴最大值(2字节)*/
#define GT911_REG_TOUCH_NUM     0x804C  /* 最大触摸点数 */
#define GT911_REG_STATUS        0x814E  /* 状态寄存器 */
#define GT911_REG_TOUCH_DATA    0x8150  /* 触摸数据起始地址 */
#define GT911_REG_CONFIG        0x8047  /* 配置起始地址 */

/* 状态寄存器位 */
#define GT911_STATUS_BUFFER_READY  BIT(7)  /* 数据就绪 */
#define GT911_STATUS_TOUCH_NUM     0x0F    /* 触摸点数量掩码 */

/* 最大触摸点数 */
#define GT911_MAX_TOUCH     5

/* 触摸点数据结构 */
struct gt911_touch_data {
    u8  id;         /* 触摸点 ID */
    u8  x_low;      /* X 坐标低字节 */
    u8  x_high;     /* X 坐标高字节 */
    u8  y_low;      /* Y 坐标低字节 */
    u8  y_high;     /* Y 坐标高字节 */
    u8  area_low;   /* 触摸面积低字节 */
    u8  area_high;  /* 触摸面积高字节 */
    u8  reserved;   /* 保留 */
} __packed;

/* 驱动私有数据 */
struct gt911_ts {
    struct i2c_client   *client;
    struct input_dev    *input_dev;

    /* GPIO */
    int                  int_gpio;  /* 中断 GPIO */
    int                  rst_gpio;  /* 复位 GPIO */
    int                  irq;       /* 中断号 */

    /* 触摸屏参数 */
    u16                  x_max;     /* X 轴最大值 */
    u16                  y_max;     /* Y 轴最大值 */
    u8                   max_touch; /* 最大触摸点数 */

    /* 当前触摸状态 */
    bool                 touch_active[GT911_MAX_TOUCH];
};

/* ── I2C 读写辅助函数 ──────────────────────────────────────── */

static int gt911_i2c_read(struct gt911_ts *ts, u16 reg,
                           u8 *buf, int len)
{
    struct i2c_client *client = ts->client;
    u8 addr_buf[2] = { reg >> 8, reg & 0xFF };
    struct i2c_msg msgs[2] = {
        {
            .addr  = client->addr,
            .flags = 0,
            .len   = 2,
            .buf   = addr_buf,
        },
        {
            .addr  = client->addr,
            .flags = I2C_M_RD,
            .len   = len,
            .buf   = buf,
        },
    };

    int ret = i2c_transfer(client->adapter, msgs, 2);
    return (ret == 2) ? 0 : (ret < 0 ? ret : -EIO);
}

static int gt911_i2c_write(struct gt911_ts *ts, u16 reg,
                            const u8 *buf, int len)
{
    struct i2c_client *client = ts->client;
    u8 *write_buf;
    int ret;

    write_buf = kmalloc(len + 2, GFP_KERNEL);
    if (!write_buf)
        return -ENOMEM;

    write_buf[0] = reg >> 8;
    write_buf[1] = reg & 0xFF;
    memcpy(write_buf + 2, buf, len);

    ret = i2c_master_send(client, write_buf, len + 2);
    kfree(write_buf);

    return (ret == len + 2) ? 0 : (ret < 0 ? ret : -EIO);
}

/* ── 硬件复位 ──────────────────────────────────────────────── */

static void gt911_reset(struct gt911_ts *ts)
{
    /* 复位序列:
     * 1. INT 低,RST 低,等待 10ms
     * 2. RST 高,等待 10ms
     * 3. INT 高(释放),等待 50ms
     */
    gpio_direction_output(ts->int_gpio, 0);
    gpio_direction_output(ts->rst_gpio, 0);
    msleep(10);

    gpio_set_value(ts->rst_gpio, 1);
    msleep(10);

    /* 设置 I2C 地址(INT 低 = 0x5D,INT 高 = 0x14)*/
    /* 这里使用 0x5D(INT 保持低电平)*/
    gpio_direction_input(ts->int_gpio);
    msleep(50);
}

/* ── 读取触摸数据 ──────────────────────────────────────────── */

static void gt911_read_touch_data(struct gt911_ts *ts)
{
    struct input_dev *input = ts->input_dev;
    struct gt911_touch_data touch[GT911_MAX_TOUCH];
    u8 status;
    int touch_num;
    int i;
    int ret;

    /* 读取状态寄存器 */
    ret = gt911_i2c_read(ts, GT911_REG_STATUS, &status, 1);
    if (ret) {
        dev_err(&ts->client->dev, "读取状态寄存器失败\n");
        return;
    }

    /* 检查数据是否就绪 */
    if (!(status & GT911_STATUS_BUFFER_READY))
        return;

    /* 获取触摸点数量 */
    touch_num = status & GT911_STATUS_TOUCH_NUM;

    if (touch_num > GT911_MAX_TOUCH)
        touch_num = GT911_MAX_TOUCH;

    /* 读取触摸点数据 */
    if (touch_num > 0) {
        ret = gt911_i2c_read(ts, GT911_REG_TOUCH_DATA,
                              (u8 *)touch,
                              touch_num * sizeof(struct gt911_touch_data));
        if (ret) {
            dev_err(&ts->client->dev, "读取触摸数据失败\n");
            goto clear_status;
        }
    }

    /* 处理触摸点数据(多点触控协议 B)*/
    for (i = 0; i < touch_num; i++) {
        int id = touch[i].id;
        int x  = touch[i].x_low | (touch[i].x_high << 8);
        int y  = touch[i].y_low | (touch[i].y_high << 8);
        int area = touch[i].area_low | (touch[i].area_high << 8);

        if (id >= GT911_MAX_TOUCH)
            continue;

        /* 切换到对应的触摸槽位 */
        input_mt_slot(input, id);

        /* 上报触摸点按下 */
        input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
        input_report_abs(input, ABS_MT_POSITION_X, x);
        input_report_abs(input, ABS_MT_POSITION_Y, y);
        input_report_abs(input, ABS_MT_TOUCH_MAJOR, area);

        ts->touch_active[id] = true;
    }

    /* 处理已释放的触摸点 */
    for (i = 0; i < GT911_MAX_TOUCH; i++) {
        if (!ts->touch_active[i])
            continue;

        /* 检查此触摸点是否在当前数据中 */
        bool found = false;
        int j;
        for (j = 0; j < touch_num; j++) {
            if (touch[j].id == i) {
                found = true;
                break;
            }
        }

        if (!found) {
            /* 触摸点已释放 */
            input_mt_slot(input, i);
            input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
            ts->touch_active[i] = false;
        }
    }

    /* 上报单点触控兼容事件 */
    input_mt_report_pointer_emulation(input, true);

    /* 同步事件 */
    input_sync(input);

clear_status:
    /* 清除状态寄存器(写 0 清除 buffer ready 标志)*/
    status = 0;
    gt911_i2c_write(ts, GT911_REG_STATUS, &status, 1);
}

/* ── 中断处理函数 ──────────────────────────────────────────── */

static irqreturn_t gt911_irq_handler(int irq, void *dev_id)
{
    struct gt911_ts *ts = dev_id;

    gt911_read_touch_data(ts);

    return IRQ_HANDLED;
}

/* ── 初始化触摸屏 ──────────────────────────────────────────── */

static int gt911_init_device(struct gt911_ts *ts)
{
    u8 buf[6];
    int ret;

    /* 读取产品 ID */
    ret = gt911_i2c_read(ts, GT911_REG_PRODUCT_ID, buf, 4);
    if (ret) {
        dev_err(&ts->client->dev, "读取产品 ID 失败\n");
        return ret;
    }
    buf[4] = '\0';
    dev_info(&ts->client->dev, "产品 ID:%s\n", buf);

    /* 读取固件版本 */
    ret = gt911_i2c_read(ts, GT911_REG_FW_VERSION, buf, 2);
    if (ret == 0) {
        dev_info(&ts->client->dev, "固件版本:0x%04X\n",
                 buf[0] | (buf[1] << 8));
    }

    /* 读取 X 轴最大值 */
    ret = gt911_i2c_read(ts, GT911_REG_X_MAX, buf, 2);
    if (ret) return ret;
    ts->x_max = buf[0] | (buf[1] << 8);

    /* 读取 Y 轴最大值 */
    ret = gt911_i2c_read(ts, GT911_REG_Y_MAX, buf, 2);
    if (ret) return ret;
    ts->y_max = buf[0] | (buf[1] << 8);

    /* 读取最大触摸点数 */
    ret = gt911_i2c_read(ts, GT911_REG_TOUCH_NUM, buf, 1);
    if (ret) return ret;
    ts->max_touch = min_t(u8, buf[0], GT911_MAX_TOUCH);

    dev_info(&ts->client->dev, "分辨率:%u×%u,最大触摸点:%u\n",
             ts->x_max, ts->y_max, ts->max_touch);

    return 0;
}

/* ── probe 函数 ──────────────────────────────────────────────── */

static int gt911_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct gt911_ts *ts;
    struct input_dev *input;
    struct device_node *np = client->dev.of_node;
    int ret;

    /* 分配私有数据 */
    ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
    if (!ts)
        return -ENOMEM;

    ts->client = client;
    i2c_set_clientdata(client, ts);

    /* 从设备树获取 GPIO */
    ts->int_gpio = of_get_named_gpio(np, "interrupt-gpios", 0);
    ts->rst_gpio = of_get_named_gpio(np, "reset-gpios", 0);

    if (!gpio_is_valid(ts->int_gpio) || !gpio_is_valid(ts->rst_gpio)) {
        dev_err(&client->dev, "无效的 GPIO\n");
        return -EINVAL;
    }

    /* 申请 GPIO */
    ret = devm_gpio_request(&client->dev, ts->int_gpio, "gt911-int");
    if (ret) return ret;

    ret = devm_gpio_request(&client->dev, ts->rst_gpio, "gt911-rst");
    if (ret) return ret;

    /* 硬件复位 */
    gt911_reset(ts);

    /* 初始化设备,读取参数 */
    ret = gt911_init_device(ts);
    if (ret) {
        dev_err(&client->dev, "初始化 GT911 失败\n");
        return ret;
    }

    /* 分配 input_dev */
    input = devm_input_allocate_device(&client->dev);
    if (!input)
        return -ENOMEM;

    ts->input_dev = input;

    /* 设置设备信息 */
    input->name    = "GT911 Touchscreen";
    input->phys    = "i2c/gt911";
    input->id.bustype = BUS_I2C;
    input->id.vendor  = 0x0416;  /* Goodix */
    input->id.product = 0x0911;
    input->id.version = 0x0001;

    /* 设置支持的事件类型 */
    set_bit(EV_SYN, input->evbit);
    set_bit(EV_KEY, input->evbit);
    set_bit(EV_ABS, input->evbit);

    /* 设置按键事件 */
    set_bit(BTN_TOUCH, input->keybit);

    /* 设置绝对坐标参数(单点触控兼容)*/
    input_set_abs_params(input, ABS_X, 0, ts->x_max, 0, 0);
    input_set_abs_params(input, ABS_Y, 0, ts->y_max, 0, 0);
    input_set_abs_params(input, ABS_PRESSURE, 0, 255, 0, 0);

    /* 初始化多点触控(协议 B)*/
    ret = input_mt_init_slots(input, ts->max_touch,
                               INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
    if (ret) {
        dev_err(&client->dev, "初始化多点触控失败\n");
        return ret;
    }

    /* 设置多点触控坐标参数 */
    input_set_abs_params(input, ABS_MT_POSITION_X, 0, ts->x_max, 0, 0);
    input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
    input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);

    /* 注册 input_dev */
    ret = input_register_device(input);
    if (ret) {
        dev_err(&client->dev, "注册 input_dev 失败\n");
        return ret;
    }

    /* 获取中断号并申请中断 */
    ts->irq = gpio_to_irq(ts->int_gpio);
    ret = devm_request_threaded_irq(&client->dev, ts->irq,
                                     NULL,
                                     gt911_irq_handler,
                                     IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                                     "gt911", ts);
    if (ret) {
        dev_err(&client->dev, "申请中断失败\n");
        return ret;
    }

    dev_info(&client->dev, "GT911 触摸屏驱动加载成功\n");
    return 0;
}

static int gt911_remove(struct i2c_client *client)
{
    dev_info(&client->dev, "GT911 驱动卸载\n");
    return 0;
}

/* ── 设备 ID 表 ──────────────────────────────────────────────── */

static const struct i2c_device_id gt911_id[] = {
    { "gt911", 0 },
    { "gt9271", 0 },
    {}
};
MODULE_DEVICE_TABLE(i2c, gt911_id);

static const struct of_device_id gt911_of_match[] = {
    { .compatible = "goodix,gt911" },
    { .compatible = "goodix,gt9271" },
    {}
};
MODULE_DEVICE_TABLE(of, gt911_of_match);

static struct i2c_driver gt911_driver = {
    .driver = {
        .name           = "gt911",
        .of_match_table = gt911_of_match,
    },
    .probe    = gt911_probe,
    .remove   = gt911_remove,
    .id_table = gt911_id,
};
module_i2c_driver(gt911_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("GT911 电容式触摸屏驱动");

20.3.3 设备树配置

dts 复制代码
/* 设备树中的 GT911 触摸屏配置 */
&i2c1 {
    clock-frequency = <400000>;
    status = "okay";

    gt911: touchscreen@5d {
        compatible = "goodix,gt911";
        reg = <0x5d>;                   /* I2C 地址 */

        /* 中断和复位 GPIO */
        interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
        reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;

        /* 中断配置 */
        interrupt-parent = <&gpio1>;
        interrupts = <9 IRQ_TYPE_EDGE_FALLING>;

        /* 触摸屏分辨率(可选,驱动会自动读取)*/
        touchscreen-size-x = <800>;
        touchscreen-size-y = <480>;

        /* 触摸屏方向(可选)*/
        /* touchscreen-inverted-x; */
        /* touchscreen-inverted-y; */
        /* touchscreen-swapped-x-y; */
    };
};

20.3.4 触摸屏驱动测试

bash 复制代码
# 查看输入设备
cat /proc/bus/input/devices
# I: Bus=0018 Vendor=0416 Product=0911 Version=0001
# N: Name="GT911 Touchscreen"
# P: Phys=i2c/gt911
# S: Sysfs=/devices/platform/soc/2028000.i2c/i2c-1/1-005d/input/input0
# U: Uniq=
# H: Handlers=event0
# B: EV=b
# B: KEY=400 0 0 0 0 0 0 0 0 0 0
# B: ABS=2658000 3

# 使用 evtest 测试触摸事件
sudo evtest /dev/input/event0
# Input driver version is 1.0.1
# Input device ID: bus 0x18 vendor 0x416 product 0x911 version 0x1
# Input device name: "GT911 Touchscreen"
# Supported events:
#   Event type 0 (EV_SYN)
#   Event type 1 (EV_KEY)
#     Event code 330 (BTN_TOUCH)
#   Event type 3 (EV_ABS)
#     Event code 0 (ABS_X)
#       Value    0
#       Min      0
#       Max    800
#     Event code 1 (ABS_Y)
#       Value    0
#       Min      0
#       Max    480
#     Event code 53 (ABS_MT_POSITION_X)
#     Event code 54 (ABS_MT_POSITION_Y)
# Testing ... (interrupt to exit)
# Event: time 1234567890.123456, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 320
# Event: time 1234567890.123456, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 240
# Event: time 1234567890.123456, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
# Event: time 1234567890.123456, type 0 (EV_SYN), code 0 (SYN_REPORT), value 0

# 使用 ts_calibrate 校准触摸屏
ts_calibrate

# 使用 ts_test 测试
ts_test

20.4 键盘驱动

20.4.1 键盘驱动的类型

复制代码
Linux 键盘驱动类型:

1. 矩阵键盘(Matrix Keyboard)
   - 按键排列成行列矩阵
   - 通过扫描行列检测按键
   - 常用于嵌入式系统(如 4×4 数字键盘)
   - Linux 提供 matrix_keypad 驱动框架

2. GPIO 按键(GPIO Keys)
   - 每个按键连接一个 GPIO
   - 通过 GPIO 中断检测按键
   - Linux 提供 gpio-keys 驱动(无需编写代码)

3. USB 键盘
   - 通过 USB HID 协议通信
   - Linux 的 usbhid 驱动自动处理

4. PS/2 键盘
   - 通过 PS/2 接口通信
   - Linux 的 atkbd 驱动处理

5. I2C/SPI 键盘
   - 通过 I2C 或 SPI 接口的键盘控制器
   - 需要编写专用驱动

20.4.2 GPIO 按键驱动(gpio-keys)

Linux 内核提供了 gpio-keys 驱动,通过设备树配置即可使用,无需编写代码:

dts 复制代码
/* 设备树中配置 GPIO 按键 */
/ {
    gpio-keys {
        compatible = "gpio-keys";
        autorepeat;             /* 使能按键重复 */

        /* 按键1:音量加 */
        key-volume-up {
            label = "Volume Up";
            gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;  /* 低电平有效 */
            linux,code = <KEY_VOLUMEUP>;           /* 按键码 */
            debounce-interval = <20>;              /* 去抖时间(ms)*/
        };

        /* 按键2:音量减 */
        key-volume-down {
            label = "Volume Down";
            gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_VOLUMEDOWN>;
            debounce-interval = <20>;
        };

        /* 按键3:电源键 */
        key-power {
            label = "Power";
            gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_POWER>;
            gpio-key,wakeup;    /* 可以唤醒系统 */
        };

        /* 按键4:用户自定义按键 */
        key-user {
            label = "User Key";
            gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_F1>;
        };
    };
};

20.4.3 矩阵键盘驱动

c 复制代码
/*
 * matrix_keypad_simple.c ------ 简单的矩阵键盘驱动
 * 实现一个 4×4 矩阵键盘(16个按键)
 *
 * 硬件连接:
 * 行(ROW):GPIO1_IO00 ~ GPIO1_IO03(输出)
 * 列(COL):GPIO1_IO04 ~ GPIO1_IO07(输入,上拉)
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/workqueue.h>

#define ROWS    4
#define COLS    4
#define KEYS    (ROWS * COLS)

/* 键盘映射表(行×列 → 按键码)*/
static const unsigned int keymap[ROWS][COLS] = {
    { KEY_1, KEY_2, KEY_3,  KEY_F1  },  /* 第0行 */
    { KEY_4, KEY_5, KEY_6,  KEY_F2  },  /* 第1行 */
    { KEY_7, KEY_8, KEY_9,  KEY_F3  },  /* 第2行 */
    { KEY_STAR, KEY_0, KEY_POUND, KEY_F4 }, /* 第3行 */
};

/* 驱动私有数据 */
struct matrix_keypad {
    struct input_dev    *input_dev;
    int                  row_gpios[ROWS];   /* 行 GPIO */
    int                  col_gpios[COLS];   /* 列 GPIO */
    int                  col_irqs[COLS];    /* 列中断号 */
    struct delayed_work  scan_work;         /* 扫描工作 */
    unsigned int         last_key_state;    /* 上次按键状态 */
    spinlock_t           lock;
};

/* ── 键盘扫描函数 ──────────────────────────────────────────── */

static unsigned int matrix_scan(struct matrix_keypad *keypad)
{
    unsigned int key_state = 0;
    int row, col;

    for (row = 0; row < ROWS; row++) {
        /* 拉低当前行 */
        gpio_set_value(keypad->row_gpios[row], 0);
        udelay(10);  /* 等待信号稳定 */

        /* 扫描所有列 */
        for (col = 0; col < COLS; col++) {
            if (!gpio_get_value(keypad->col_gpios[col])) {
                /* 列为低电平,说明此位置有按键按下 */
                key_state |= BIT(row * COLS + col);
            }
        }

        /* 恢复行为高电平 */
        gpio_set_value(keypad->row_gpios[row], 1);
    }

    return key_state;
}

/* ── 扫描工作处理函数 ──────────────────────────────────────── */

static void matrix_scan_work(struct work_struct *work)
{
    struct matrix_keypad *keypad = container_of(work,
                                                 struct matrix_keypad,
                                                 scan_work.work);
    struct input_dev *input = keypad->input_dev;
    unsigned int key_state, changed;
    unsigned long flags;
    int i;

    spin_lock_irqsave(&keypad->lock, flags);

    /* 扫描当前按键状态 */
    key_state = matrix_scan(keypad);

    /* 找出状态变化的按键 */
    changed = key_state ^ keypad->last_key_state;

    /* 上报变化的按键事件 */
    for (i = 0; i < KEYS; i++) {
        if (changed & BIT(i)) {
            int row = i / COLS;
            int col = i % COLS;
            int pressed = (key_state >> i) & 1;

            input_report_key(input, keymap[row][col], pressed);
            pr_debug("matrix_keypad: 按键[%d][%d]=%s\n",
                     row, col, pressed ? "按下" : "释放");
        }
    }

    if (changed)
        input_sync(input);

    keypad->last_key_state = key_state;

    /* 如果还有按键按下,继续扫描 */
    if (key_state) {
        schedule_delayed_work(&keypad->scan_work,
                              msecs_to_jiffies(20));  /* 20ms 后再次扫描 */
    } else {
        /* 所有按键释放,重新使能列中断 */
        int col;
        for (col = 0; col < COLS; col++)
            enable_irq(keypad->col_irqs[col]);
    }

    spin_unlock_irqrestore(&keypad->lock, flags);
}

/* ── 列中断处理函数 ──────────────────────────────────────────── */

static irqreturn_t matrix_col_irq_handler(int irq, void *dev_id)
{
    struct matrix_keypad *keypad = dev_id;
    int col;

    /* 禁止所有列中断(防止重复触发)*/
    for (col = 0; col < COLS; col++)
        disable_irq_nosync(keypad->col_irqs[col]);

    /* 调度扫描工作(延迟 10ms 去抖)*/
    schedule_delayed_work(&keypad->scan_work, msecs_to_jiffies(10));

    return IRQ_HANDLED;
}

/* ── probe 函数 ──────────────────────────────────────────────── */

static int matrix_keypad_probe(struct platform_device *pdev)
{
    struct matrix_keypad *keypad;
    struct input_dev *input;
    struct device_node *np = pdev->dev.of_node;
    int i, ret;

    keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL);
    if (!keypad)
        return -ENOMEM;

    spin_lock_init(&keypad->lock);
    INIT_DELAYED_WORK(&keypad->scan_work, matrix_scan_work);

    /* 从设备树获取行 GPIO */
    for (i = 0; i < ROWS; i++) {
        keypad->row_gpios[i] = of_get_named_gpio(np, "row-gpios", i);
        if (!gpio_is_valid(keypad->row_gpios[i]))
            return -EINVAL;

        ret = devm_gpio_request_one(&pdev->dev, keypad->row_gpios[i],
                                     GPIOF_OUT_INIT_HIGH, "keypad-row");
        if (ret) return ret;
    }

    /* 从设备树获取列 GPIO */
    for (i = 0; i < COLS; i++) {
        keypad->col_gpios[i] = of_get_named_gpio(np, "col-gpios", i);
        if (!gpio_is_valid(keypad->col_gpios[i]))
            return -EINVAL;

        ret = devm_gpio_request_one(&pdev->dev, keypad->col_gpios[i],
                                     GPIOF_IN, "keypad-col");
        if (ret) return ret;

        /* 获取列中断号 */
        keypad->col_irqs[i] = gpio_to_irq(keypad->col_gpios[i]);

        /* 申请列中断(下降沿触发)*/
        ret = devm_request_irq(&pdev->dev, keypad->col_irqs[i],
                                matrix_col_irq_handler,
                                IRQF_TRIGGER_FALLING,
                                "keypad-col", keypad);
        if (ret) return ret;
    }

    /* 分配 input_dev */
    input = devm_input_allocate_device(&pdev->dev);
    if (!input)
        return -ENOMEM;

    keypad->input_dev = input;

    /* 设置设备信息 */
    input->name    = "Matrix Keypad";
    input->phys    = "gpio/matrix-keypad";
    input->id.bustype = BUS_HOST;

    /* 设置支持的事件类型 */
    set_bit(EV_KEY, input->evbit);
    set_bit(EV_REP, input->evbit);  /* 支持按键重复 */

    /* 设置支持的按键 */
    for (i = 0; i < ROWS; i++) {
        int j;
        for (j = 0; j < COLS; j++)
            set_bit(keymap[i][j], input->keybit);
    }

    /* 注册 input_dev */
    ret = input_register_device(input);
    if (ret) return ret;

    platform_set_drvdata(pdev, keypad);

    dev_info(&pdev->dev, "矩阵键盘驱动加载成功(%d×%d)\n", ROWS, COLS);
    return 0;
}

static int matrix_keypad_remove(struct platform_device *pdev)
{
    struct matrix_keypad *keypad = platform_get_drvdata(pdev);
    cancel_delayed_work_sync(&keypad->scan_work);
    return 0;
}

static const struct of_device_id matrix_keypad_of_match[] = {
    { .compatible = "myvendor,matrix-keypad" },
    {}
};
MODULE_DEVICE_TABLE(of, matrix_keypad_of_match);

static struct platform_driver matrix_keypad_driver = {
    .probe  = matrix_keypad_probe,
    .remove = matrix_keypad_remove,
    .driver = {
        .name           = "matrix-keypad",
        .of_match_table = matrix_keypad_of_match,
    },
};
module_platform_driver(matrix_keypad_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("矩阵键盘驱动");

矩阵键盘的设备树配置

dts 复制代码
/* 4×4 矩阵键盘设备树配置 */
/ {
    matrix-keypad {
        compatible = "myvendor,matrix-keypad";

        /* 行 GPIO(输出)*/
        row-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>,
                    <&gpio1 1 GPIO_ACTIVE_LOW>,
                    <&gpio1 2 GPIO_ACTIVE_LOW>,
                    <&gpio1 3 GPIO_ACTIVE_LOW>;

        /* 列 GPIO(输入,上拉)*/
        col-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>,
                    <&gpio1 5 GPIO_ACTIVE_LOW>,
                    <&gpio1 6 GPIO_ACTIVE_LOW>,
                    <&gpio1 7 GPIO_ACTIVE_LOW>;

        /* 去抖时间(ms)*/
        debounce-delay-ms = <10>;

        /* 按键映射 */
        linux,keymap = <
            MATRIX_KEY(0, 0, KEY_1)
            MATRIX_KEY(0, 1, KEY_2)
            MATRIX_KEY(0, 2, KEY_3)
            MATRIX_KEY(0, 3, KEY_F1)
            MATRIX_KEY(1, 0, KEY_4)
            MATRIX_KEY(1, 1, KEY_5)
            MATRIX_KEY(1, 2, KEY_6)
            MATRIX_KEY(1, 3, KEY_F2)
            MATRIX_KEY(2, 0, KEY_7)
            MATRIX_KEY(2, 1, KEY_8)
            MATRIX_KEY(2, 2, KEY_9)
            MATRIX_KEY(2, 3, KEY_F3)
            MATRIX_KEY(3, 0, KEY_STAR)
            MATRIX_KEY(3, 1, KEY_0)
            MATRIX_KEY(3, 2, KEY_POUND)
            MATRIX_KEY(3, 3, KEY_F4)
        >;
    };
};

20.4.4 键盘驱动测试

bash 复制代码
# 查看输入设备
cat /proc/bus/input/devices
# I: Bus=0019 Vendor=0001 Product=0001 Version=0100
# N: Name="Matrix Keypad"
# P: Phys=gpio/matrix-keypad
# H: Handlers=kbd event1
# B: EV=100003
# B: KEY=...

# 使用 evtest 测试按键事件
sudo evtest /dev/input/event1
# Testing ... (interrupt to exit)
# Event: time ..., type 1 (EV_KEY), code 2 (KEY_1), value 1  ← 按下
# Event: time ..., type 0 (EV_SYN), code 0 (SYN_REPORT), value 0
# Event: time ..., type 1 (EV_KEY), code 2 (KEY_1), value 0  ← 释放
# Event: time ..., type 0 (EV_SYN), code 0 (SYN_REPORT), value 0

# 使用 showkey 查看按键码
showkey

# 使用 xev 在 X11 环境下测试
xev

# 查看 GPIO 按键状态
cat /sys/class/input/input1/name
# Matrix Keypad

# 使用 hexdump 查看原始事件数据
hexdump -C /dev/input/event1
# 00000000  xx xx xx xx xx xx xx xx  02 00 01 00 01 00 00 00  |................|
# 时间戳(8B) + type(2B) + code(2B) + value(4B)

本章小结

章节 核心知识点 关键 API
20.1 输入设备驱动简介 输入子系统设计目标;完整层次架构图(用户空间→事件处理层→输入核心→驱动层→硬件);事件类型(EV_KEY/EV_ABS/EV_REL/EV_SYN);按键码/坐标轴码;input_event结构体;用户空间读取示例 /dev/input/eventNevtest
20.2 输入设备驱动结构 input_dev/input_absinfo核心结构体;注册流程(input_allocate_device→set_bit→input_set_abs_params→input_register_device);事件上报API(input_report_key/abs/rel/sync);多点触控协议A/B详解 input_allocate_device()input_register_device()input_report_key()input_sync()
20.3 触摸屏设备驱动实例 GT911特性(5点触控/I2C/中断/复位);I2C读写辅助函数;硬件复位序列;完整GT911驱动(init/中断处理/多点触控协议B/input_mt_slot);设备树配置;evtest测试 input_mt_init_slots()input_mt_slot()input_mt_report_slot_state()
20.4 键盘驱动 键盘驱动类型(矩阵/GPIO/USB/PS2);gpio-keys设备树配置(无需编写代码);完整矩阵键盘驱动(行列扫描/列中断/去抖/延迟工作);矩阵键盘设备树配置;showkey/evtest测试 gpio-keysMATRIX_KEY()schedule_delayed_work()

输入设备驱动开发要点

复制代码
1. 事件类型和代码的正确设置
   使用 set_bit 设置支持的事件类型和代码
   触摸屏:EV_KEY(BTN_TOUCH) + EV_ABS(ABS_X/Y)
   键盘:EV_KEY + EV_REP(按键重复)
   鼠标:EV_KEY(BTN_LEFT/RIGHT) + EV_REL(REL_X/Y)

2. 绝对坐标范围的设置
   input_set_abs_params 必须设置正确的 min/max
   fuzz:噪声过滤(触摸屏通常设为 0)
   flat:死区(摇杆中心区域)

3. 多点触控协议 B 的使用
   input_mt_init_slots 初始化槽位
   input_mt_slot 切换当前槽位
   input_mt_report_slot_state 上报触摸状态
   input_mt_report_pointer_emulation 兼容单点触控

4. 中断处理
   触摸屏:使用 threaded_irq(可以睡眠,进行 I2C 读取)
   键盘:使用普通中断 + 工作队列去抖

5. 去抖处理
   硬件去抖:RC 滤波电路
   软件去抖:延迟工作(delayed_work)

6. 调试工具
   evtest:查看原始事件
   showkey:查看按键码
   cat /proc/bus/input/devices:查看设备信息
   hexdump /dev/input/eventN:查看原始数据

参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年