Linux键盘驱动实现分析报告

来源:互联网 发布:淘宝怎么部分退款 编辑:程序博客网 时间:2024/06/06 00:51

题目要求

结合内核代码,给出一种设备驱动程序的实现分析报告
– 任何一种驱动程序都可以
–注意剖析的完整性、深度和广度

概论

内核版本:2.6.35.7

驱动设备:键盘驱动

键盘驱动的源码文件在linux/drivers/char/keyboard.c

实现功能

键盘模式:
键盘模式有4种, Linux 下可以用vc_kbd_mode老版本中是kbd_mode)参数来设置和显示模式:
1
Scancode mode raw raw模式:将键盘端口上读出的扫描码放入缓冲区
2
Keycode mode (mediumraw) mediumraw模式:将扫描码过滤为键盘码放入缓冲区
3
ASCII mode (XLATE ) XLATE模式:识别各种键盘码的组合,转换为TTY终端代码放入缓冲区
4
UTF-8 MODE (UNICODE) Unicode 模式:UNICODE模式基本上与XLATE相同,只不过可以通过数字小键盘间接输入UNICODE代码。
   keyboard.c中,不涉及底层操作,也不涉及到任何体系结构,他主要负责:键盘初始化、键盘tasklet的挂入、按键盘后的处理、keymap的装入、scancode的转化、与TTY设备的通信。
   
  keyboard.c包含了大多数keyboardkey的处理,其中,int __init kbd_init(void) 函数是键盘代码执行的开始点。
kbd_init
()在进行一些基本初始化后,执行input_register_handler(),来注册一个键盘类型的Handler。这里的Handler不是具体的用户可以操作的设备,而是键盘类设备的统一的处理函数接口,实现代码就是操作硬件。input_register_handler()的作用有两点:1、把input_handler按次设备号放到input_handler数组input_table里;2、再用当前所有的输入设备input_devinput_handler适配。初始化一完成,就把keyboard_tasklet放到CPU tasklet中。
  
当有按键时,会调用键盘中断处理函数kbd_event(),它根据event_type的值调用kbd_rawcodekbd_keycode,然后调度keyboard_tasklet tasklet,接着调用schedule_console_callback函数,这部分涉及到tty的内容。不管对应键盘的那一种模式。后面的数据流程都会转入到input_queue()进等处理。

keyboard.c中的大多数函数都会调用put_queue()函数。put_queue()的工作原理就是利用TTY, 它将经过转换的键盘功能码用tty_insert_filp_char()将数据ch存入tty的一个缓存中,将数据暂存之后,会调用con_schedule_flip(tty)去唤醒一个软中断的工作队列.

代码分析

keyboard.c中,首先定义了2个宏:

#define K_HANDLERS/

    k_self,     k_fn,       k_spec, k_pad,/

    k_dead,     k_cons,     k_cur,  k_shift,/

    k_meta,     k_ascii,    k_lock, k_lowercase,/

    k_slock,    k_dead2,    k_brl,  k_ignore

#define FN_HANDLERS/

    fn_null,    fn_enter,   fn_show_ptregs, fn_show_mem,/

    fn_show_state,  fn_send_intr,   fn_lastcons,    fn_caps_toggle,/

    fn_num,     fn_hold,    fn_scroll_forw, fn_scroll_back,/

    fn_boot_it, fn_caps_on, fn_compose, fn_SAK,/

    fn_dec_console, fn_inc_console, fn_spawn_con,   fn_bare_num

其中的内容,全部都是对按键的操作函数,实现了特殊按键的响应。

再次,定义一个kbd_struct, 它用于保存当前键盘LED灯状态、缺省keymap表、键盘复合锁定状态、一些功能灯的定义、键盘模式定义、及modeflags模式*/

struct kbd_struct kbd_table[MAX_NR_CONSOLES];

EXPORT_SYMBOL_GPL(kbd_table);

static struct kbd_struct *kbd = kbd_table;

kbd_init()函数是驱动程序的开始点,kbd_init()函数原型为:

int __init kbd_init(void)

{

    int i;

    int error;

 

        for (i = 0; i < MAX_NR_CONSOLES; i++) {

        kbd_table[i].ledflagstate = KBD_DEFLEDS; /* 缺省不亮灯*/

        kbd_table[i].default_ledflagstate = KBD_DEFLEDS; /* 缺省,表示用key_map的第一个表,即没有lock*/

        kbd_table[i].ledmode = LED_SHOW_FLAGS; /* 缺省,用于显示flags */

        kbd_table[i].lockstate = KBD_DEFLOCK;

        kbd_table[i].slockstate = 0; /* 没有粘键*/

        kbd_table[i].modeflags = KBD_DEFMODE; /* modeflags=0 */

        kbd_table[i].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE; /*UnicodeASCII模式*/

    }

 

    error = input_register_handler(&kbd_handler);

    if (error)

        return error;

/* 我们希望把keyboard_tasklet 挂到CPU的运行队列中去,下面就是*/

    tasklet_enable(&keyboard_tasklet);

    tasklet_schedule(&keyboard_tasklet);

 

    return 0;

}

在键盘设备和handler 进行匹配的时候,会调用handler->connect如当按键按下的时候,当一个键盘(或其他输入设备)被找到,kbd_connect 函数被调用。然后,函数关注于此设备,如果是键盘设备,handler可以打开它,从它那里得到响应事件。

static int kbd_connect(struct input_handler *handler, struct input_dev *dev,

            const struct input_device_id *id)

{

    struct input_handle *handle;

    int error;

 

    handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);

    if (!handle)

        return -ENOMEM;

 

    handle->dev = dev;

    handle->handler = handler;

    handle->name = "kbd";

 

    error = input_register_handle(handle);

    if (error)

        goto err_free_handle;

 

    error = input_open_device(handle);

    if (error)

        goto err_unregister_handle;

 

    return 0;

 

 err_unregister_handle:

    input_unregister_handle(handle);

 err_free_handle:

    kfree(handle);

    return error;

}

在这段代码里,它申请分初始化了一个hande结构,并将其注册。Open。在注册handle的时候。又会调用到hande->start.函数如下:

/*

 * Start keyboard handler on the new keyboard by refreshing LED state to

 * match the rest of the system.

 */

static void kbd_start(struct input_handle *handle)

{

    tasklet_disable(&keyboard_tasklet);

 

    if (ledstate != 0xff)

        kbd_update_leds_helper(handle, &ledstate);

 

    tasklet_enable(&keyboard_tasklet);

}

这里就是对键盘上的LED进行操作。启用了tasklent。它的事件处理过程:

键盘中断:
在做完init后,接下来就等待按键按下事件了,如果产生了按键按下事件,就调用kbd_event()函数:首先用spin_lock()函数,通过kbd_event_lock变量,实现加锁,然后调用spin_unlock()来解锁,从而达到了中断处理的目的。

static void kbd_event(struct input_handle *handle, unsigned int event_type,

              unsigned int event_code, int value)

{

    /* We are called with interrupts disabled, just take the lock */

    spin_lock(&kbd_event_lock);

 

    if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))

        kbd_rawcode(value);

    if (event_type == EV_KEY)

        kbd_keycode(event_code, value, HW_RAW(handle->dev));

 

    spin_unlock(&kbd_event_lock);

 

    tasklet_schedule(&keyboard_tasklet);

    do_poke_blanked_console = 1;

    schedule_console_callback();

}

老版本分析:

在老版本中,中断处理一般都是体系下的keyboard_interrupt(),此函数会调用到keyboard.c中的handle_scancode() 。这个函数最终决定了按下的key如何处理,他主要做:与TTY的通信、keymap的装入、按键的处理。

老版本中,任务队列是:con_task_queue)并激活控制台软中断执行该任务函数。

在老版本中:kbd_init函数原型为:

int __init kbd_init(void)

{

    int i;

    struct kbd_struct kbd0;

    extern struct tty_driver console_driver; /* console_driver这个全局变量是很重要的,他维护着庞大的TTY/Console对象,承当TTY对外的输入和输出*/

    kbd0.ledflagstate = kbd0.default_ledflagstate = KBD_DEFLEDS;

    kbd0.ledmode = LED_SHOW_FLAGS;

    kbd0.lockstate = KBD_DEFLOCK;

    kbd0.slockstate = 0;

    kbd0.modeflags = KBD_DEFMODE;

    kbd0.kbdmode = VC_XLATE;

    for (i = 0 ; ikbd_table[i] = kbd0; ttytab = console_driver.table; /* ttytab是一个很重要的指针,他维护着当前各个控制台的tty_struct 表(即相当于一个维表),tty_struct可看成/dev/tty*的输入设备,只有在/dev/tty*打开时它才接收输入数据*/

 

    /* 以下就是涉及到硬件的操作,由于我们没有KBD硬件,因此我们可能希望屏蔽所产生的error信息*/

    #ifdef CONFIG_SA1100_ROSETTA

    button_setup(); /* initialize button hardware */

    #else

    kbd_init_hw();

    #endif

    tasklet_enable(&eyboard_tasklet);

    tasklet_schedule(&eyboard_tasklet);

    /* 下面register一个电源管理的KBD设备?操作函数为NULL? */

    pm_kbd = pm_register(PM_SYS_DEV, PM_SYS_KBC, NULL);

    return 0;

}

新版本与老版本不同的地方在于:先定义了一个int型的变量error,使用input_register_handler函数,将键盘handler挂载到input模块下,完成了以前重要的kbd_init_hw()函数功能。省略了对kbd0的定义,而是直接用struct kbd_struct kbd_table[MAX_NR_CONSOLES];定义了全局的变量,节省了代码量。在编码模式上,比以前多了unicode模式。

 

报告总结

在此分析报告中,针对2.6.35内核源码进行了剖析,之所以选择键盘驱动,是因为其代码量相对于其它驱动程序较小。在研读代码的过程中,上网查阅了相关老版本的资料,发现新版本与老版本的源代码上,有了较多的不同之处。在分析过程中我使用了Windows下的工具Source Insight 3.5锻炼了我分析大型软件代码的能力。

通过对Linux源代码的阅读,让我对C语言有了全新的认识,深感标准C的编程较难。源代码中C语言的各种灵活运用让我大开眼界,在源码中,大量使用了static关键字用来修饰函数和结构体定义,来限制函数或变量的作用域,以及高级宏定义的使用。函数之间的调用,使用较多,代码的注释,全部使用英文,又感到自己的英文能力不够,借助翻译工具,完成此分析报告。

通过对Linux这种大型软件代码的分析,看到大型软件的编程习惯,我深感平时的编程习惯与良好的编程习惯相差甚远,我决定在以后的编程过程中养成良好的编程习惯,这样有助于自己所编的程序清晰明了便于该错还有助于别人来立解你的程序。同时通过这次分析实验我形成了通过从各方面查找资料来丰富自己的知识的能力。

 

参考文献

[1]毛德操,胡希明;Linux内核源代码情景分析;浙江大学出版社