Linux上input子系统使用

1.简介

  本文档主要记录input子系统使用,我们通过驱动Linux上iic连接的手势识别传感器paj7620来展开。
(于2019年开发瑞芯微平台上使用)涉及到的驱动模块:
DTS设备树input子系统I2C模块workqueue工作队列

2.传感器驱动

2.1 传感器数据结构

构造好传感器的数据结构,涉及到i2c模块,延时工作队列,input_device用来上报消息。

//paj7620传感器
struct gesture_sensor{
    struct i2c_client *client;
    struct delayed_work poll_work;
    struct input_dev *inputdev;
    //这里暂时还不用互斥锁,如果需要请一并加入
};

2.2 i2c driver驱动

IIC driver驱动结构,构造好i2c_driver。

static struct i2c_driver paj7620_i2c_drv = {
    .probe  = paj7620_probe,
    .remove = paj7620_remove,
    .driver = {
        .owner =THIS_MODULE,
        .name ="paj7620",
        .of_match_table =paj_i2c_ids,
    },
    .id_table= paj_id,
};

static struct of_device_id paj_i2c_ids[] = {
    {.compatible = "pxi,paj7620"},
    {}   
};

DTS设备树,描述iic地址,compatible(用于匹配)

pac7620: pac7620@73 {
        compatible = "pxi,paj7620";
        reg = <0x73>;
        status = "okay";
    };

2.3 probe关键函数入口

platform驱动模型,现在是比较流行的驱动模式。驱动入手很多时候都是从probe函数开始的。 platform device和 platform driver都注册进来后,如果匹配OK,就会调用到我们的probe。 of_match_table和id_table都是用来在设备树中和设备匹配,且都是通过compatible,前者优先级更高,后者则会在前者未匹配的情况下去掉compatible的供应商信息后再匹配,即id_table只匹配sensor名字。
备注

  1. platform_driver_register注册时把 platform_drv_probe赋值给drv->driver.probe
  2. driver_register调用了driver_probe_device,里面又调用了really_probe,里面通过drv->probe调用了drv->driver.probe,也就是调用了platform_drv_probe。其中传入的参数是device *dev,dev_prv = to_device_private_bus,dev = dev_prv->device。猜测dev由设备树匹配过来。
  3. platform_drv_probe中又获取了xxx_probe的指针,最后调用到我们编写的xxx_probe
static int paj7620_probe(struct i2c_client *client, const struct i2c_device_id *device_id)
{
    int ret;
    dev_sensor = devm_kzalloc(&client->dev,sizeof(struct gesture_sensor),GFP_KERNEL);
    dev_sensor->client = client;
    if(dev_sensor==NULL)
    {
        printk("devm_kzalloc failed\n");
        ret =-ENOMEM;
        return ret;
    }
    //1.注册input device
    dev_sensor->inputdev = input_allocate_device();
    if(dev_sensor->inputdev ==NULL)
    {
        printk("input_allocate_device failed\n");
        ret =-ENOMEM;
        goto err0;
    }
    __set_bit(EV_MSC,dev_sensor->inputdev->evbit);
    __set_bit(MSC_GESTURE,dev_sensor->inputdev->mscbit);
    dev_sensor->inputdev->name = "paj7620";
    dev_sensor->inputdev->id.bustype = BUS_I2C;
    dev_sensor->inputdev->dev.parent = &client->dev;

    ret = input_register_device(dev_sensor->inputdev);
    if(ret!=0)
    {
        printk("input_register_device failed\n");
        goto err1;
    }
    //2.初始化
    paj_init(client);
    //3.初始化delay work工作队列
    INIT_DELAYED_WORK(&dev_sensor->poll_work, gesture_work_poll);
    schedule_delayed_work(&dev_sensor->poll_work,1000);
    return 0;
err1:
    input_free_device(dev_sensor->inputdev);
err0:
    devm_kfree(&client->dev,dev_sensor);
    return ret;
}

备注:input子系统,

2.4 workqueue工作队列

在使用work queue时,先了解一个linux上中断机制分为上半部分(硬中断)和下半部,上半部中断用于 完成比较紧急的功能,比如:读取寄存器中的中断状态,在读取清除中断标志后,启动下半部,在下半部中 完成绝大部分工作。在linux上使用tasklet机制(软中断延迟机制)来实现下半部工作。tasklet是基于 软中断实现,因此运行在软中断上下文,软中断上下文也是中断上下文,而中断上下文的特点是内部不能 发生睡眠或者延时等操作。 此时work queue工作队列优势就有凸显,工作队列是另外一种将工作推后执行的形式,和tasklet不同之处, 工作队列可以将工作推后,交由一个内核线程去执行,也就是这个下半部分可以在进程上下文种执行,此时 工作队列允许被重新调度甚至是睡眠。 故而再需要睡眠或者需要大量的内存,需要获取信号量时,可以采用工作队列机制进行推后工作。

总结:
- softirq和tasklet都属于软中断tasklet是softirq的特殊实现workqueue是普通的工作队列
tasklet实际上只是在软中断的基础上添加了一定的机制。软中断是一般“可延迟函数”的总称,主要是为了
满足对时间不敏感的任务延后执行,而且可以在多个cpu上并行执行,使得效率可以更高。产生后,并不是马上
执行,必须要等内核调度才能执行,软中断不能被自己打断(即单个cpu上软中断不能嵌套执行),
只能被硬件中断打断(上半部)。因为软中断可以并发运行在多个cpu上,所以软中断必须设计为
可重入函数(允许多个cpu同时操作),其数据结构需要使用自旋锁来保证,tasklet代码必须设计为原子的
- tasklet和workqueue,两者都是中断下半部的一种实现方法。但是tasklet属于中断上下文,支持smp、不可睡眠和阻塞;
workqueue基于线程的封装,属于进程上下文,因此支持睡眠、阻塞,但只能在内核种运行,无法访问用户空间。

这里我们使用workqueue实现

void gesture_work_poll(struct work_struct *work)
{
....
    data=paj_read_reg(dev_sensor->client,0x6b);
    data2=paj_read_reg(dev_sensor->client,0x6c);
    //事件同步
    input_event(dev_sensor->inputdev,EV_MSC,MSC_RAW,data);
    input_sync(dev_sensor->inputdev);
    //启动调度
    schedule_delayed_work(&dev_sensor->poll_work, msecs_to_jiffies(200));
....
}

2.5 input子系统

linux上设计并实现了为驱动层程序的实现提供统一接口函数,可以为上层应用提供试图统一的抽象层,也就是 input子系统。 操作流程上,注册好input子系统后,将获取的数据通过input_event()拷贝到user空间,后同步一下input_sync(), 实际上,input子系统最终调用copy_to_user(buffer, event, sizeof(struct input_event))将信息上报给上层, event为struct input_event类型结构体,中间会有一些处理将 type,code,value值打包成input_event类型的数据。