目錄
輸入設備驅動框架簡介
輸入設備(案件、鍵盤、鼠標、觸摸屏等)是典型的字符設備。該類驅動的工作特點是,底層在進行按鍵或者觸屏等其他動作時會產生一箇中斷,或者驅動中定時獲取底層的操作,當檢測到動作發生的時候,回去讀取鍵值或者座標數據,然後將數據存儲在一段緩衝區中,字符設備驅動負責管理者這塊緩衝區,而用戶則直接通過字符設備驅動的用戶接口進行鍵值的讀取。
這些操作中,只有中斷鍵值適合具體的設備相關,但是對於緩衝區的管理是和具體的設備無關的,所以linux中加入了一個input的核心層代碼專門用於處理公共的工作,這樣才符合linux下的驅動框架。
輸入事件驅動程序:drivers\input\dvdev.c drivers\input\joydev.c drivers\input\mousedev.c
輸入核心:drivers\input\input.c
輸入設備驅動程序:drivers\input\gpio_keys.c
輸入核心層
輸入核心層提供了底層的設備驅動程序需要的API,申請和釋放一個輸入設備
struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
申請完設備之後,需要將設備註冊到內核中:函數中使用的參數就是input_allocate_device函數的返回值
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
還提供了一些報告輸入事件的接口:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/*報告指定type、code的輸入事件*/
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_report_key、input_report_rel、input_report_abs其實只是type不同,最終調用的還是input_event函數,一般調用完上述這四個函數之後,都需要調用input_sync,使上述發送的報告事件生效。
所有的事件都可以使用一個結構體表示:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
設備驅動程序
以gpio_keys.c爲例,本文件實現了一套通用的GPIO按鍵驅動,因爲它將硬件相關配置瓶坯在版文件中,並且符合platform驅動框架,所以可以使用在各個處理器中。
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
size_t size;
int i, error;
int wakeup = 0;
if (!pdata) {
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
size = sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data);
ddata = devm_kzalloc(dev, size, GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n");
return -ENOMEM;
}
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
ddata->pdata = pdata;
ddata->input = input;
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;
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit);
for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i];
error = gpio_keys_setup_key(pdev, input, bdata, button);
if (error)
return error;
if (button->wakeup)
wakeup = 1;
}
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);
return error;
}
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto err_remove_group;
}
device_init_wakeup(&pdev->dev, wakeup);
return 0;
err_remove_group:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
return error;
}
函數中主要完成了三個工作:
- 分配一個輸入設備devm_input_allocate_device
- 53-63行對這個dpio進行了配置,包括申請中斷等
- input_register_device註冊這個設備到linux中
完成初始化工作之後,只要等案件或者其他輸入動作發生之後,進行數據的讀取並且報告事件即可
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->release_delay) {
input_event(input, EV_KEY, button->code, 0);
input_sync(input);
goto out;
}
bdata->key_pressed = true;
}
if (bdata->release_delay)
mod_timer(&bdata->release_timer,
jiffies + msecs_to_jiffies(bdata->release_delay));
out:
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}
函數中最重要的兩行代碼如下所示:
input_event(input, EV_KEY, button->code, 0);
input_sync(input);
GPIO按鍵驅動通過這兩個函數來彙報按鍵事件以及同步事件
輸入事件驅動程序
用戶最終獲取按鍵數據是通過drivers\input\dvdev.c drivers\input\joydev.c drivers\input\mousedev.c 中的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;
size_t read = 0;
int error;
if (count != 0 && count < input_event_size())
return -EINVAL;
for (;;) {
if (!evdev->exist || client->revoked)
return -ENODEV;
if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break;
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + read, &event))
return -EFAULT;
read += input_event_size();
}
if (read)
break;
if (!(file->f_flags & O_NONBLOCK)) {
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
if (error)
return error;
}
}
return read;
}
函數的17-19行首先判斷是否是阻塞讀,如果不是則直接退出報錯,因爲這種的事件只能阻塞讀,41-43就是阻塞等待,當設備驅動調用input_event發送事件之後們就會喚醒這個阻塞的操作,這樣用戶就可以讀取到輸入事件的值了