fl2440——内核自带按键驱动 代码分析

时间:2022-02-25 19:31:53

          2017 年4月26日,晴!先读一首词再开始写按键驱动代码分析!

 待卿长发及腰,我必凯旋回朝。昔日纵马任逍遥,俱是少年英豪。东都霞色好,西湖烟波渺。执枪血战八方,誓守山河多娇。应有得胜归来日,与卿共度良宵。盼携手终老,愿与子同袍。

==========================================================================

主机操作系统:Centos 6.7 
交叉编译器环境:arm-linux-gcc-4.5.4 (可通过命令/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc -v查询)
开发板平台: fl2440 
Linux内核版本: linux-3.0 .54

==========================================================================

        上篇文章对linux内核自带驱动进行了移植添加,并且对驱动进行了测试,接下来我们就来看看该驱动具体是怎么样实现的。

对于该驱动代码分析,和前面platform分析类似。我们首先要找到的也是入口程序__init gpio_keys_init,该程序是对驱动的注册。然后平台驱动通过name去找相对应的对设备,匹配到设备后,调用probe函数。


二、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;
}

//对ddata赋值:
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赋值:
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); //设置事件类型为EV_REP(repeat,可重复),按键类型为input->evbit

//对按键进行初始化,并进行相关设置
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;

//标识输入设备对哪些事件感兴趣,因为是按键,所以感兴趣的事件(type)一定是EV_KEY
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);  //注册输入设备input,把输入设备加到输入设备链表中,并寻找合适的handler与input_handler配对
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;
}
       该函数主要工作是分配相应空间,以及一些初始化工作,还有就是对input子系统的注册

        其中sysfs_create_group()函数作用:在指定目录下生成sys属性文件,即在执行完下面语句后会在/sys/devices/platform/gpio-keys/目录下生成四个文件:disabled_keys、disabled_switches、switches、keys,前两个是可读可写的,后两个为只读。

       初始化的时候调用了gpio_keys_setup_key()函数,下面我们来看看这个函数。


三、gpio_keys_setup_key()函数:

static int __devinit gpio_keys_setup_key(struct platform_device *pdev,
struct gpio_button_data *bdata,
struct gpio_keys_button *button)
{
const char *desc = button->desc ? button->desc : "gpio_keys";
struct device *dev = &pdev->dev;
unsigned long irqflags;
int irq, error;

setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata); //设置定时器,用于去抖
INIT_WORK(&bdata->work, gpio_keys_work_func);

error = gpio_request(button->gpio, desc); //向系统申请gpio
if (error < 0) {
dev_err(dev, "failed to request GPIO %d, error %d\n",
button->gpio, error);
goto fail2;
}

error = gpio_direction_input(button->gpio); //把button->gpio设置为输入
if (error < 0) {
dev_err(dev, "failed to configure"
" direction for GPIO %d, error %d\n",
button->gpio, error);
goto fail3;
}

if (button->debounce_interval) { //设置按键的去抖时间间隔
error = gpio_set_debounce(button->gpio,
button->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (error < 0)
bdata->timer_debounce = button->debounce_interval;
}

irq = gpio_to_irq(button->gpio); //提取gpio所对应的外部中断
if (irq < 0) {
error = irq;
dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
goto fail3;
}

irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
/* 设置外部中断上升沿或下降沿触发
* If platform has specified that the button can be disabled,
* we don't want it to share the interrupt line.
*/
if (!button->can_disable)
irqflags |= IRQF_SHARED;


error = request_any_context_irq(irq, gpio_keys_isr, irqflags, desc, bdata); //按键按下,产生中断
if (error < 0) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
irq, error);
goto fail3;
}

return 0;

fail3:
gpio_free(button->gpio);
fail2:
return error;
}

INIT_WORK(&bdata->work, gpio_keys_work_func);

在bdata->work的工作队列中增加一个任务gpio_keys_gpio_work_func(用于上报事件信息)


request_any_context_irq()
        分配一条中断线,即当按键中断(bdata->irq)发生时,进入中断函数(isr,即gpio_keys_irq_isr函数内)


四、中断处理函数gpio_keys_irq_isr():

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
struct gpio_keys_button *button = bdata->button;

BUG_ON(irq != gpio_to_irq(button->gpio)); //
如果参数为真的话,证明内核出现了bug,打印BUG信息

if (bdata->timer_debounce)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(bdata->timer_debounce)); //去抖
else
schedule_work(&bdata->work); //
经过去抖时间后,将&bdata->work放入工作队列

return IRQ_HANDLED;
}
        该中断函数即为中断机制的前半部,它主要思想是在按键去抖间隔时间内不执行中断后半部分内容,只有在经过去抖间隔时间达到以后,才执行工作队列中的内容&bdata->work,即执行gpio_keys_gpio_work_func函数。
         中断后半部函数gpio_keys_gpio_work_func调用的是gpio_keys_gpio_report_event函数,而gpio_keys_gpio_report_event函数调用的是input_event函数,该函数的功能是把输入事件上报给系统。input_event函数经过一次次传递和封装,最终到达用户空间而被用户所用。



五、输入子系统:

Linux输入子系统包括三个层次:事件处理层(Event Handler)、核心层(Input Core)和驱动层(Input Driver)。

  • 事件处理层负责与用户程序打交道,将核心层传来的事件报告给用户程序。
  • 核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
  • 驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,键盘、鼠标、触摸屏等字符设备驱动功能的实现工作主要就在这层。

         输入子系统有三个核心结构体:input_dev,input_handle和input_handler。input_dev表示一个输入设备,包含输入设备的一些相关信息;input_handler表示对输入事件的具体处理,它为输入设备的功能实现了一个接口;input_handle是用来连接输入设备和输入事件。输入子系统主要的任务就是把这三个结构体连接在一起。

1、核心层 input.c

     该文件存放于  linux-3.0.54/drivers/input/input.c

(1)对input.c主要部分进行介绍:

static int __init input_init(void)
{
int err;


err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}


err = input_proc_init();
if (err)
goto fail1;

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}

return 0;

fail2:input_proc_exit();
fail1:class_unregister(&input_class);
return err;
}


         class_register() :注册input类,这里就是生成了sys/class/input目录,在该目录下会看到本文要用到的链接文件event0。 

input_proc_init()  :在proc目录下建立input子系统目录及交互文件,即/proc/bus/input目录下的devices文件和handlers文件

       register_chrdev(INPUT_MAJOR, "input", &input_fops): 注册一个主设备号为INPUT_MAJOR(13),次设备号为0~255,文件操作符为input_fops的字符设备。所有主设备号13的字符设备的操作最终都会转入到input_fops中。例如event0的主设备号为13,对其的操作会落在input_fops中


input_fops结构体

static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
.llseek = noop_llseek,
};
对其定义的input_open_file进行分析:

(2)input_open_file函数()函数:

static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
int err;

err = mutex_lock_interruptible(&input_mutex);
if (err)
return err;

/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5]; //通过识别次设备号来获得当前的输入设备的handler
if (handler)
new_fops = fops_get(handler->fops);

mutex_unlock(&input_mutex);

/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops || !new_fops->open) {
fops_put(new_fops); //得到该handler的文件操作指针new_fops
err = -ENODEV;
goto out;
}

old_fops = file->f_op;
file->f_op = new_fops;

err = new_fops->open(inode, file); //用新获得的new_fops中的open来打开设备
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
return err;
}
         从input_open_file函数的分析可以看出,input_fops只是输入子系统通用的file_operations,对于不同的输入子系统(如键盘、鼠标、触摸屏等),通过input_open_file函数会 调用各自不同的file_operations 。对于按键来说,它调用的是drivers/input目录下Evdev.c文件中的 evdev_fops

2、事件处理层:

(1)初始化:

static int __init evdev_init(void)
{
returninput_register_handler(&evdev_handler); //注册evdev_handler
}

(2) evdev_handler结构体

       evdev_handler结构体类型就是前面介绍过的三个重要结构体中的一个——input_handler

static struct input_handler evdev_handler = {
.event= evdev_event,
.connect= evdev_connect,
.disconnect= evdev_disconnect,
.fops= &evdev_fops,
.minor= EVDEV_MINOR_BASE,
.name= "evdev",
.id_table= evdev_ids,
};
      其中 evdev_connect 主要用来连接input_dev和input_handler; evdev_event 是把input事件发给所有的client。另外在该结构体内还定义了一个 fops集合 ,它被赋值为evdev_fops的指针。evdev_fops就是在介绍核心层input.c文件时,提到的当处理按键输入子系统时,file_operations所对应的就是evdev_fops。在这里,我们只分析该集合中的evdev_open函数和evdev_read函数


(3)evdev_open()函数

static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE; //得到设备列表中的序号
unsigned int bufsize;
int error;

if (i >= EVDEV_MINORS)
return -ENODEV;

error = mutex_lock_interruptible(&evdev_table_mutex);
if (error)
return error;
evdev = evdev_table[i]; //取出设备
if (evdev)
get_device(&evdev->dev); //增加计数
mutex_unlock(&evdev_table_mutex);

if (!evdev)
return -ENODEV;

bufsize = evdev_compute_buffer_size(evdev->handle.dev);

client = kzalloc(sizeof(struct evdev_client) + //分配并初始化client结构体
bufsize * sizeof(struct input_event),
GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}

client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
client->evdev = evdev; //使client与设备相关联,并把它挂接在链表中
evdev_attach_client(evdev, client);

error = evdev_open_device(evdev);
if (error)
goto err_free_client;

file->private_data = client;
nonseekable_open(inode, file);

return 0;

err_free_client:
evdev_detach_client(evdev, client);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}
       evdev_open_device函数的作用是打开设备,当设备是第一次被打开时,则调用input_open_device函数。


(4) evdev_read()函数:

static ssize_t evdev_read(struct file*file, char __user *buffer,

size_tcount, loff_t *ppos)

{

structevdev_client *client = file->private_data;

structevdev *evdev = client->evdev;

structinput_event event;

size_tread = 0;

interror;


if(count != 0 && count < input_event_size()) //对读取的数据大小进行判断
return-EINVAL;


for(;;) {

if(!evdev->exist) //如果设备不存在,则退出
return-ENODEV;

//如果缓存中没有数据可读而设备又存在,并且被设置为O_NONBLOCK方式,则退出

if(client->packet_head == client->tail &&

(file->f_flags & O_NONBLOCK))

return-EAGAIN;


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);

if(error)

returnerror;

}

}