第12章 Linux设备驱动的软件架构思想之设备驱动的分层思想(输入设备驱动)

12.3.2 输入设备驱动

    输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断(或驱动通过Timer定时查询),然后CPU通过SPI、I 2 C或外部存储器总线读取键值、坐标等数据,并将键值、坐标等数据放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户读取键值、坐标等数据。

    在这些工作中,只是中断、读键值/坐标值是与设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。

Linux内核输入子系统的框架如图12.6所示。


图12.6 Linux内核输入子系统的框架

输入核心提供底层输入设备驱动程序所需的API,如分配/释放一个输入设备:

linux/input.h

struct input_dev *input_allocate_device(void);

void input_free_device(struct input_dev *dev);

input_allocate_device()返回的是1个指向input_dev的结构体的指针,此结构体用于表征1个输入设备。

drivers/input/input.c(The input core)

/**
 * input_allocate_device - allocate memory for new input device
 *
 * Returns prepared struct input_dev or NULL.
 *
 * NOTE: Use input_free_device() to free devices that have not been
 * registered; input_unregister_device() should be used for already
 * registered devices.
 */
struct input_dev *input_allocate_device(void)
{

        struct input_dev *dev;

        dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
        if (dev) {
                dev->dev.type = &input_dev_type;
                dev->dev.class = &input_class;
                device_initialize(&dev->dev);
                mutex_init(&dev->mutex);
                spin_lock_init(&dev->event_lock);
                INIT_LIST_HEAD(&dev->h_list);
                INIT_LIST_HEAD(&dev->node);
                __module_get(THIS_MODULE);
        }
        return dev;

}

EXPORT_SYMBOL(input_allocate_device);


/**
 * input_free_device - free memory occupied by input_dev structure
 * @dev: input device to free
 *
 * This function should only be used if input_register_device()
 * was not called yet or if it failed. Once device was registered
 * use input_unregister_device() and memory will be freed once last
 * reference to the device is dropped.
 *
 * Device should be allocated by input_allocate_device().
 *
 * NOTE: If there are references to the input device then memory
 * will not be freed until last reference is dropped.
 */
void input_free_device(struct input_dev *dev)
{
        if (dev)
                input_put_device(dev);
}
EXPORT_SYMBOL(input_free_device);

注册/注销输入设备用的接口如下:

linux/input.h

int __must_check input_register_device(struct input_dev *);

void input_unregister_device(struct input_dev *);

drivers/input/input.c(The input core)

/**
 * input_register_device - register device with input core
 * @dev: device to be registered
 *
 * This function registers device with input core. The device must be
 * allocated with input_allocate_device() and all it's capabilities
 * set up before registering.
 * If function fails the device must be freed with input_free_device().
 * Once device has been successfully registered it can be unregistered
 * with input_unregister_device(); input_free_device() should not be
 * called in this case.
 */
int input_register_device(struct input_dev *dev)
{
        static atomic_t input_no = ATOMIC_INIT(0);
        struct input_handler *handler;
        const char *path;
        int error;

        /* Every input device generates EV_SYN/SYN_REPORT events. */
        __set_bit(EV_SYN, dev->evbit);

        /* KEY_RESERVED is not supposed to be transmitted to userspace. */
        __clear_bit(KEY_RESERVED, dev->keybit);

        /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
        input_cleanse_bitmasks(dev);

        if (!dev->hint_events_per_packet)
                dev->hint_events_per_packet =
                                input_estimate_events_per_packet(dev);
        /*
         * If delay and period are pre-set by the driver, then autorepeating
         * is handled by the driver itself and we don't do it in input.c.
         */
        init_timer(&dev->timer);
        if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
                dev->timer.data = (long) dev;
                dev->timer.function = input_repeat_key;
                dev->rep[REP_DELAY] = 250;
                dev->rep[REP_PERIOD] = 33;
        }

        if (!dev->getkeycode)
                dev->getkeycode = input_default_getkeycode;

        if (!dev->setkeycode)
                dev->setkeycode = input_default_setkeycode;

        dev_set_name(&dev->dev, "input%ld",
                     (unsigned long) atomic_inc_return(&input_no) - 1);

        error = device_add(&dev->dev);
        if (error)
                return error;

        path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
        pr_info("%s as %s\n",
                dev->name ? dev->name : "Unspecified device",
                path ? path : "N/A");
        kfree(path);
        error = mutex_lock_interruptible(&input_mutex);
        if (error) {
                device_del(&dev->dev);
                return error;
        }
        list_add_tail(&dev->node, &input_dev_list);
        list_for_each_entry(handler, &input_handler_list, node)
                input_attach_handler(dev, handler);
        input_wakeup_procfs_readers();
        mutex_unlock(&input_mutex);
        return 0;
}

EXPORT_SYMBOL(input_register_device);

/**
 * input_unregister_device - unregister previously registered device
 * @dev: device to be unregistered
 *
 * This function unregisters an input device. Once device is unregistered
 * the caller should not try to access it as it may get freed at any moment.
 */
void input_unregister_device(struct input_dev *dev)
{
        struct input_handle *handle, *next;

        input_disconnect_device(dev);
        mutex_lock(&input_mutex);
        list_for_each_entry_safe(handle, next, &dev->h_list, d_node)
                handle->handler->disconnect(handle);
        WARN_ON(!list_empty(&dev->h_list));
        del_timer_sync(&dev->timer);
        list_del_init(&dev->node);
        input_wakeup_procfs_readers();
        mutex_unlock(&input_mutex);
        device_unregister(&dev->dev);
}

EXPORT_SYMBOL(input_unregister_device);

报告输入事件用的接口如下:

linux/input.h

/* 报告指定 type 、 code 的输入事件 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/* 报告键值 */
void input_report_key(struct input_dev *dev, unsigned int code, int value);
/* 报告相对坐标 */
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
/* 报告绝对坐标 */
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
/* 报告同步事件 */

void input_sync(struct input_dev *dev);

对于所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event,如代码清单12.10所示。

linux/input.h

struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;

};

drivers/input/keyboard/gpio_keys.c基于input架构实现了一个通用的GPIO按键驱动。该驱动是基于
platform_driver架构的,名为“gpio-keys”。它将与硬件相关的信息(如使用的GPIO号,按下和抬起时的电
平等)屏蔽在板级文件platform_device的platform_data中,因此该驱动可应用于各个处理器,具有良好的跨平

台性。代码清单12.11列出了该驱动的probe()函数。

static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; // 获取平台数据
struct gpio_keys_drvdata *ddata;
struct device *dev = &pdev->dev;
struct input_dev *input;
int i, error;
int wakeup = 0;

ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data),
GFP_KERNEL);
input = input_allocate_device();//分配输入设备
if (!ddata || !input) {
dev_err(dev, "failed to allocate state\n");
error = -ENOMEM;
goto fail1;
}
         /*初始化该input_dev的一些属性*/
ddata->input = input;
ddata->n_buttons = pdata->nbuttons;
ddata->enable = pdata->enable;
ddata->disable = pdata->disable;
mutex_init(&ddata->disable_lock);

platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata);

input->name = pdata->name ? : pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->open = gpio_keys_open;
input->close = gpio_keys_close;

input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;

        /*初始化该input_dev的一些属性*/
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit);

        

        // 初始化所用到的GPIO

for (i = 0; i < pdata->nbuttons; i++) {
struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i];
unsigned int type = button->type ?: EV_KEY;
bdata->input = input;
bdata->button = button;
error = gpio_keys_setup_key(pdev, bdata, button);
if (error)
goto fail2;
if (button->wakeup)
wakeup = 1;

input_set_capability(input, type, button->code);
}

error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
goto fail2;
}

error = input_register_device(input);//register input device
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n", error);
goto fail3;
}

/* get current state of buttons */
for (i = 0; i < pdata->nbuttons; i++)
gpio_keys_report_event(&ddata->data[i]);
input_sync(input);
device_init_wakeup(&pdev->dev, wakeup);
return 0;

 fail3:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
 fail2:
while (--i >= 0) {
free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
if (ddata->data[i].timer_debounce)
del_timer_sync(&ddata->data[i].timer);
cancel_work_sync(&ddata->data[i].work);
gpio_free(pdata->buttons[i].gpio);
}

platform_set_drvdata(pdev, NULL);
 fail1:
input_free_device(input);
kfree(ddata);

return error;

}

注册输入设备后,底层输入设备驱动的核心工作只剩下在按键、触摸等人为动作发生时报告事件。

代码清单12.12 GPIO按键中断发生时的事件报告

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id) // 中断服务程序
{
         struct gpio_button_data *bdata = dev_id;
         const struct gpio_keys_button *button = bdata->button;
         struct input_dev *input = bdata->input;
         unsigned long flags;

         BUG_ON(irq != bdata->irq);

         spin_lock_irqsave(&bdata->lock, flags);

        if (!bdata->key_pressed) {
                 if (bdata->button->wakeup)
                         pm_wakeup_event(bdata->input->dev.parent, 0);

                 input_event(input, EV_KEY, button->code, 1);
                 input_sync(input);

                 if (!bdata->timer_debounce) {
                         input_event(input, EV_KEY, button->code, 0);
                         input_sync(input);
                         goto out;
                 }

                 bdata->key_pressed = true;
           }

         if (bdata->timer_debounce)
                 mod_timer(&bdata->timer,
                 jiffies + msecs_to_jiffies(bdata->timer_debounce));
 out:
         spin_unlock_irqrestore(&bdata->lock, flags);
         return IRQ_HANDLED;

}

    GPIO按键驱动通过input_event()、input_sync()来汇报按键事件以及同步事件。从底层的GPIO按键驱动看出,该驱动中没有任何file_operations的动作,也没有各种I/O模型,注册进入系统用的是input_register_device()这样的与input相关的API。这是由于与Linux VFS接口的这一部分代码全部都在drivers/input/evdev.c中实现了。

代码清单12.13 input核心层的file_operations和read()函数

static ssize_t evdev_read(struct file *file, char __user *buffer,
 size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;// 获取私有数据指针
struct evdev *evdev = client->evdev;
struct input_event event;
int retval = 0;

if (count < input_event_size())
return -EINVAL;

if (!(file->f_flags & O_NONBLOCK)) {
retval = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail || !evdev->exist);
if (retval)
return retval;
}

if (!evdev->exist)
return -ENODEV;

while (retval + input_event_size() <= count &&
      evdev_fetch_next_event(client, &event)) {

if (input_event_to_user(buffer + retval, &event))
return -EFAULT;

retval += input_event_size();
}

if (retval == 0 && file->f_flags & O_NONBLOCK)// 非阻塞访问,立即返回EAGAIN错误
retval = -EAGAIN;
return retval;

}

static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,

};


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页