定时中断键盘灯闪烁
在学习完《趣味定时器》后,是不是只在dmesg里看到效果很没劲,这次来点更实际的,我们让键盘灯闪起来。 对于定时器,就不多说了,接下来了解下键盘的基本内容。
键盘在Linux里是属于TTY(TeleTYpe)设备,既然我们要用到键盘,那先找其通用驱动代码,在Linux Kernel源码drivers/tty/vt/keyboard.c文件,我们找到需要的LED灯控制函数:
void setledstate(struct kbd_struct kbd, unsigned int led) ;
该函数被在同一文件的vt_do_kdskled()函数调用,而这函数又被drivers/tty/vt/vt_ioctl.c文件的vt_ioctl()函数调用,这函数是drivers/tty/vt/vt.c里 struct tty_operations con_ops的ioctl成员,在同一文件vty_init()函数里有如下语句:
tty_set_operations(console_driver, &con_ops);
将 console_driver->ops赋值为&con_ops,而console_driver是如下类型:
struct tty_driver *console_driver;
该结构体在include/linux/tty_driver.h定义,其被include/linux/tty.h文件中的struct tty_struct结构体包含,其又被同文件的struct tty_port结构体包含。
而我们在 drivers/tty/vt/vt_ioctl.c文件中有看到vt_disallocate_all()函数里有tty_port_destroy(&vc[i]->port); 操作,那么找到vc就可以关联起来,在include/linux/console_struct.h文件中有vc的类型struct vc_data结构体定义,而在drivers/tty/vt/selection.c文件中的set_selection()函数里有struct vc_data *vc = vc_cons[fg_console].d; 赋值操作,那么往下找vc_cons,其在drivers/tty/vt/vt.c文件中定义为struct vc vc_cons [MAX_NR_CONSOLES]; 其为全局量,至此整个关系就理清了,接下来是小细节问题:
fg_console在drivers/tty/vt/vt.c文件中有如下注释:
fg_console is the current virtual console, 其在同文件中声明为int fg_console;
至此,从vc_cons到setledstate的路及路障都清得差不多了,接下来得理理vc_ioctl了,其在drivers/tty/vt/vt_ioctl.c中的定义如下(只截函数头部分):
int vt_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
其中,tty刚好对应上struct vc结构体里struct vc_data *d结构体成员的struct tty_port port; 结构体成员的struct tty_struct *tty; 成员;而cmd就是vt_ioctl()里的KDSETLED;而arg则是设置LED灯状态值,其对应NumLock、CapsLock和ScrollLock三个灯的状态,可以是任一灯、任两灯或全三灯亮灭的状态,在include/linux/kbd_kern.h文件中有如下内容:
#define VC_SCROLLOCK 0 /*scroll-lock mode*/
#define VC_NUMLOCK 1 /*numeric lock mode*/
#define VC_CAPSLOCK 2 /*capslock mode */ #define VC_KANALOCK 3 / *kanalock mode */ 在drivers/tty/vt/keyboard.c文件中有led灯状态的默认值定义:
static unsigned char ledstate = 0xff; /*undefined */
而setledstate函数的定义如下:
键盘在Linux里是属于TTY(TeleTYpe)设备,既然我们要用到键盘,那先找其通用驱动代码,在Linux Kernel源码drivers/tty/vt/keyboard.c文件,我们找到需要的LED灯控制函数:
void setledstate(struct kbd_struct kbd, unsigned int led) ;
该函数被在同一文件的vt_do_kdskled()函数调用,而这函数又被drivers/tty/vt/vt_ioctl.c文件的vt_ioctl()函数调用,这函数是drivers/tty/vt/vt.c里 struct tty_operations con_ops的ioctl成员,在同一文件vty_init()函数里有如下语句:
tty_set_operations(console_driver, &con_ops);
将 console_driver->ops赋值为&con_ops,而console_driver是如下类型:
struct tty_driver *console_driver;
该结构体在include/linux/tty_driver.h定义,其被include/linux/tty.h文件中的struct tty_struct结构体包含,其又被同文件的struct tty_port结构体包含。
而我们在 drivers/tty/vt/vt_ioctl.c文件中有看到vt_disallocate_all()函数里有tty_port_destroy(&vc[i]->port); 操作,那么找到vc就可以关联起来,在include/linux/console_struct.h文件中有vc的类型struct vc_data结构体定义,而在drivers/tty/vt/selection.c文件中的set_selection()函数里有struct vc_data *vc = vc_cons[fg_console].d; 赋值操作,那么往下找vc_cons,其在drivers/tty/vt/vt.c文件中定义为struct vc vc_cons [MAX_NR_CONSOLES]; 其为全局量,至此整个关系就理清了,接下来是小细节问题:
fg_console在drivers/tty/vt/vt.c文件中有如下注释:
fg_console is the current virtual console, 其在同文件中声明为int fg_console;
至此,从vc_cons到setledstate的路及路障都清得差不多了,接下来得理理vc_ioctl了,其在drivers/tty/vt/vt_ioctl.c中的定义如下(只截函数头部分):
int vt_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
其中,tty刚好对应上struct vc结构体里struct vc_data *d结构体成员的struct tty_port port; 结构体成员的struct tty_struct *tty; 成员;而cmd就是vt_ioctl()里的KDSETLED;而arg则是设置LED灯状态值,其对应NumLock、CapsLock和ScrollLock三个灯的状态,可以是任一灯、任两灯或全三灯亮灭的状态,在include/linux/kbd_kern.h文件中有如下内容:
#define VC_SCROLLOCK 0 /*scroll-lock mode*/
#define VC_NUMLOCK 1 /*numeric lock mode*/
#define VC_CAPSLOCK 2 /*capslock mode */ #define VC_KANALOCK 3 / *kanalock mode */ 在drivers/tty/vt/keyboard.c文件中有led灯状态的默认值定义:
static unsigned char ledstate = 0xff; /*undefined */
而setledstate函数的定义如下:
void setledstate(struct kbd_struct *kbd, unsigned int led) { unsigned long flags; spin_lock_irqsave(&led_lock, flags); if (!(led & ~7)) { ledioctl = led; kbd->ledmode = LED_SHOW_IOCTL; } else kbd->ledmode = LED_SHOW_FLAGS; set_leds(); spin_unlock_irqrestore(&led_lock, flags); }
由函数可知当传入的状态值为7时,使用IOCTL模式,其他则使用FLAGS模式;使用IOCTL时3个灯全亮,当为FLAGS时则按键盘实际状态亮灭,而我们等会使用0xff恢复未定义状态。
好了,分析了这么多,跟踪了整个路线,我们还是上实例代码吧:
好了,分析了这么多,跟踪了整个路线,我们还是上实例代码吧:
#include <linux/module.h> #include <linux/timer.h> #include <linux/tty.h> #include <linux/kd.h> #include <linux/console_struct.h> #define BLINK_DELAY HZ/4 #define ALL_LEDS_ON 0x07 #define RESTORE_LEDS_STATUS 0xff struct timer_list slam_timer; char led_status = 0; extern struct vc vc_cons [MAX_NR_CONSOLES]; extern int fg_console; static struct tty_driver * slam_tty_driver; void keyboard_led_timer_function(unsigned long data) { int *pre_status = (int *)data; if (*pre_status == ALL_LEDS_ON) *pre_status = RESTORE_LEDS_STATUS; else *pre_status = ALL_LEDS_ON; (slam_tty_driver->ops->ioctl)(vc_cons[fg_console].d->port.tty, KDSETLED, *pre_status); mod_timer(&slam_timer, jiffies + msecs_to_jiffies(BLINK_DELAY)); } static __init int timer_keyboard_led_init(void) { slam_tty_driver = vc_cons[fg_console].d->port.tty->driver; setup_timer(&slam_timer, keyboard_led_timer_function, (unsigned long)&led_status); mod_timer(&slam_timer, jiffies + msecs_to_jiffies(BLINK_DELAY)); return 0; } static __exit void timer_keyboard_led_exit(void) { del_timer(&slam_timer); (slam_tty_driver->ops->ioctl)(vc_cons[fg_console].d->port.tty, KDSETLED, RESTORE_LEDS_STATUS); } module_init(timer_keyboard_led_init); module_exit(timer_keyboard_led_exit);
相应的Makefile文件内容如下:
obj-m += timer_keyboard_led.o CUR_PATH:=$(shell pwd) LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6 all: make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules clean: make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
相应的源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/timer_keyboard_led/
├── Makefile
└── timer_keyboard_led.c
好了,剩下的就是自己动手加载吧,注意观察PC键盘上右上角3个灯的闪烁吧,本文借助于现有的keyboard驱动来操作键盘上LED灯的状态,再结合定时器来实现LED灯闪烁,有没有其他办法呢?如直接操作键盘控制器。抽空多想想吧,多试试,总会有收获的。
从这个例子,我们也渐渐往计算机接口技术迈进,不过这里传输的是使用思想,后期再说明控制思想。
参考网址:
http://ldp.icenet.is/LDP/lkmpg/2.6/html/lkmpg.html#AEN1194
http://wowocpp.blog.163.com/blog/static/1242627802009112851936332/
http://tuxthink.blogspot.com/2013/02/kbledsc-for-linux-kernel-version-375.html
/home/xinu/xinu/linux_kernel_driver_l1/timer_keyboard_led/
├── Makefile
└── timer_keyboard_led.c
好了,剩下的就是自己动手加载吧,注意观察PC键盘上右上角3个灯的闪烁吧,本文借助于现有的keyboard驱动来操作键盘上LED灯的状态,再结合定时器来实现LED灯闪烁,有没有其他办法呢?如直接操作键盘控制器。抽空多想想吧,多试试,总会有收获的。
从这个例子,我们也渐渐往计算机接口技术迈进,不过这里传输的是使用思想,后期再说明控制思想。
参考网址:
http://ldp.icenet.is/LDP/lkmpg/2.6/html/lkmpg.html#AEN1194
http://wowocpp.blog.163.com/blog/static/1242627802009112851936332/
http://tuxthink.blogspot.com/2013/02/kbledsc-for-linux-kernel-version-375.html
评论
发表评论