momo zone

调核人的blog

再谈linux input子系统

几年前我写过一篇《键码之旅》,现在看来其中不少错误和纰漏,是时候重新整理一下了。 输入子系统蓝图: snapshot1 input子系统会和其他各种子系统打交道,而打交道的这些子系统中肯定有输入类设备。比如usb接口键盘(需要usb子系统支持),蓝牙无线的鼠标键盘(需要蓝牙子系统支持)。圆角矩形表示设备驱动,椭圆形表示的是input设备驱动和event驱动。这样做的目的是将事件层与具体的输入设备分离,这样架构会更加灵活,易于维护。 注意:如果登录的是字符终端,那么用户态下的event接口就没有程序关注了,console/tty层直接从内核态获得keycode。所以当X11没有响应时,切到tty还有机会让键盘有反应。 接下来看一下相关的数据结构: 在这些输入设备中只有ps/2接口的算是比较正统的,原因如下: 1. PS/2接口本来就是为鼠标和键盘而生的,从serio设备相关代码都在drivers/input/serio目录下可以印证这个说法。而且像Nroll key 全键盘无冲突这类功能也只有在ps/2接口下才ok(原因主要是ps/2协议) 2. PS/2的键盘鼠标产生的中断是一个独占的中断,因为host端有一个i8042芯片(现在集成在super i/o芯片中)作为serio接口处理器来处理中断。对比usb的键盘鼠标,中断由usb host控制器产生,有些控制器会共享一个中断,每个控制器上的usb设备也会共享一个中断。 具体考古方面的问题可以看这篇文章:http://www.computer-engineering.org/ps2keyboard/snapshot3 ———————————————————————————————————————————————————————– snapshot4 PS/2 接口的键盘又称为“AT”键盘,“PC”键盘。这里就以原生的AT键盘为例,简述当按下一个键后会发生什么。

  1. 键盘内部的控制器扫描矩阵把键的动作编码成扫描码发送出去
  2. serio控制器(i8042)读取扫描码,并产生一个i8042中断
  3. i8042中断例程(drivers/input/serio/i8042.c:i8042_interrupt)中调用AT键盘的中断处理函数atkbd_intterupt(drivers/input/keyboard/atkbd.c:atkbd_interrupt),根据atkbd结构中的表,将scancode转换成keycode
  4. 转移到事件驱动的event handle继续处理
  5. 对于一般的按键,该keycode 会上传给console/tty层,drivers.char/keyboard.c:
    /* Add the keycode to flip buffer */
    tty_insert_flip_char(tty, keycode, 0);
    /* Schedule */
    con_schedule_flip(tty); 接下来这个keycode还要经过tty的转义(相当于OSI中表示层的概念),如果用的是bash,还要再经过bash转义(/etc/inputrc)
  6. 对于一些特殊的按键序列,比如sysrq或alt+ctrl+del 就要跳到其他处理流程里了

————————————————————————————————————————————————————————- 看一下相关的核心数据结构: struct input_dev {

    const char *name;     //名称                              
    const char *phys;  //设备在系统中的物理路径
    const char *uniq;  //设备唯一识别符
    struct input_id id; //设备ID,包含总线ID(PCI、USB)、厂商ID,与input_handler匹配的时会用到  

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //支持的所有事件类型  
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   //支持的键盘事件  
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   //支持的鼠标相对值事件  
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];   //支持的鼠标绝对值事件  
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   //支持的其它事件类型  
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   //支持的LED灯事件  
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   //支持的声效事件 
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];     //支持的力反馈事件  
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];     //支持的开关事件  

    unsigned int keycodemax;  //keycode表的大小
    unsigned int keycodesize;  //keycode表中元素个数
    void *keycode;  //设备的键盘表
    int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);//配置keycode表  
    int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);//获取keycode表  

    struct ff_device *ff;  

    unsigned int repeat_key;//保存上一个键值  
    struct timer_list timer;  

    int sync;  

    int abs[ABS_MAX + 1];             //绝对坐标上报的当前值  
    int rep[REP_MAX + 1];             //这个参数主要是处理重复按键,后面遇到再讲  
    unsigned long key[BITS_TO_LONGS(KEY_CNT)]; //按键有两种状态,按下和抬起,这个字段就是记录这两个状态。  
    unsigned long led[BITS_TO_LONGS(LED_CNT)];  
    unsigned long snd[BITS_TO_LONGS(SND_CNT)];  
    unsigned long sw[BITS_TO_LONGS(SW_CNT)];  

    int absmax[ABS_MAX + 1];           //绝对坐标的最大值  
    int absmin[ABS_MAX + 1];       //绝对坐标的最小值  
    int absfuzz[ABS_MAX + 1];            
    int absflat[ABS_MAX + 1];            
    //操作接口
    int (*open)(struct input_dev *dev);  
    void (*close)(struct input_dev *dev);  
    int (*flush)(struct input_dev *dev, struct file *file);  
    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  /* 一般为atkbd_event */

    struct input_handle *grab;         //当前使用的handle  

    spinlock_t event_lock;  
    struct mutex mutex;  

    unsigned int users;  
    int going_away;  

    struct device dev;  

    struct list_head    h_list;    //h_list是一个链表头,用来把handle挂载在这个上  
    struct list_head    node;      //这个node是用来连到input_dev_list上的  
};  
 // input_dev->evbit表示设备支持的事件类型,可以是下列值的组合
       #define EV_SYN           0x00  //同步事件
        #define EV_KEY           0x01 //绝对二进制值,如键盘或按钮
        #define EV_REL           0x02 //绝对结果,如鼠标设备
        #define EV_ABS           0x03 //绝对整数值,如操纵杆或书写板
        #define EV_MSC          0x04 //其它类
        #define EV_SW            0x05 //开关事件
        #define EV_LED          0x11 //LED或其它指示设备
        #define EV_SND         0x12 //声音输出,如蜂鸣器
        #define EV_REP         0x14 //允许按键自重复
        #define EV_FF             0x15 //力反馈
        #define EV_PWR        0x16 //电源管理事件
include/linux/input.h中定义了支持的类型
比如pc keyboard的evbit为0x120013(可以从/sys/class/input/input0/capabilities/ev获知)代表他有EV_KEY,EV_SYN,EV_MSC,EV_LED,EV_REP这些事件类型。


struct input_handler {  
    void *private;  

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);  
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);  
    void (*disconnect)(struct input_handle *handle);  
    void (*start)(struct input_handle *handle);  

    const struct file_operations *fops;  
    int minor;                               //次设备号  
    const char *name;  

    const struct input_device_id *id_table;  
    const struct input_device_id *blacklist;  

    struct list_head    h_list;    //h_list是一个链表头,用来把handle挂载在这个上  
    struct list_head    node;      //这个node是用来连到input_handler_list上的  
};  

struct input_handle {  

    void *private;  

    int open;  
    const char *name;  

    struct input_dev *dev;              //指向input_dev  
    struct input_handler *handler;      //指向input_handler  

    struct list_head    d_node;     //连到input_dev的h_list上  
    struct list_head    h_node;     //连到input_handler的h_list上  
};
struct atkbd {
	struct ps2dev ps2dev;
	struct input_dev *dev;

	/* Written only during init */
	char name[64]; //nomally is "AT Translated Set 2 keyboard"
	char phys[32];

	unsigned short id;
	unsigned short keycode[ATKBD_KEYMAP_SIZE]; //在atkbd_set_keycode_table中初始化
	DECLARE_BITMAP(force_release_mask, ATKBD_KEYMAP_SIZE);
	unsigned char set;
	bool translated; //如果是keycode set2则为true
	bool extra;
	bool write;
	bool softrepeat;
	bool softraw;
	bool scroll;
	bool enabled;

	/* Accessed only from interrupt */
	unsigned char emul;
	bool resend;
	bool release;
	unsigned long xl_bit;
	unsigned int last;
	unsigned long time;
	unsigned long err_count;

	struct delayed_work event_work;
	unsigned long event_jiffies;
	unsigned long event_mask;

	/* Serializes reconnect(), attr->set() and event work */
	struct mutex mutex;
};

这三个结构通过链表相互关联起来 list1 list2 看得出,intput_handle仿佛只是一个桥梁,注意它还有一个private成员变量。 —————————————————————————————————————————————————————- 下面看一下设备驱动,也就是从硬中断开始:

static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
				   unsigned int flags)
{
	struct atkbd *atkbd = serio_get_drvdata(serio);
	struct input_dev *dev = atkbd->dev;
	unsigned int code = data;
	int scroll = 0, hscroll = 0, click = -1;
	int value;
	unsigned short keycode;

	dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, flags); //data就是scancode

#if !defined(__i386__) && !defined (__x86_64__)
	if ((flags & (SERIO_FRAME | SERIO_PARITY)) && (~flags & SERIO_TIMEOUT) && !atkbd->resend && atkbd->write) {
		dev_warn(&serio->dev, "Frame/parity error: %02x\n", flags);
		serio_write(serio, ATKBD_CMD_RESEND);
		atkbd->resend = true;
		goto out;
	}

	if (!flags && data == ATKBD_RET_ACK)
		atkbd->resend = false;
#endif

	if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK))
		if  (ps2_handle_ack(&atkbd->ps2dev, data))
			goto out;

	if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD))
		if  (ps2_handle_response(&atkbd->ps2dev, data))
			goto out;
	/* atkbd 初始化,注销,重连时可能会disabled */
	if (!atkbd->enabled)
		goto out;

	/* 往上层上报一个RAW code事件 */
	input_event(dev, EV_MSC, MSC_RAW, code);

	/* 只有OQO Model 01+ (ZEPTO) 这种类型的键盘才会走这个条件分支,相关内容看dmi_check_system */
	if (atkbd_platform_scancode_fixup)
		code = atkbd_platform_scancode_fixup(atkbd, code);

	if (atkbd->translated) {

		if (atkbd->emul || atkbd_need_xlate(atkbd->xl_bit, code)) {
			atkbd->release = code >> 7;
			code &= 0x7f;
		}

		if (!atkbd->emul)
			atkbd_calculate_xl_bit(atkbd, data);
	}
	/* 一些特殊的keycode处理分支流程 */
	switch (code) {
	case ATKBD_RET_BAT:
		atkbd->enabled = false;
		serio_reconnect(atkbd->ps2dev.serio);
		goto out;
	case ATKBD_RET_EMUL0:
		atkbd->emul = 1;
		goto out;
	case ATKBD_RET_EMUL1:
		atkbd->emul = 2;
		goto out;
	case ATKBD_RET_RELEASE:
		atkbd->release = true;
		goto out;
	case ATKBD_RET_ACK:
	case ATKBD_RET_NAK:
		if (printk_ratelimit())
			dev_warn(&serio->dev,
				 "Spurious %s on %s. "
				 "Some program might be trying access hardware directly.\n",
				 data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys);
		goto out;
	case ATKBD_RET_ERR:
		atkbd->err_count++;
		dev_dbg(&serio->dev, "Keyboard on %s reports too many keys pressed.\n",
			serio->phys);
		goto out;
	}
	/* 为2.4内核兼容? */
	code = atkbd_compat_scancode(atkbd, code);

	if (atkbd->emul && --atkbd->emul)
		goto out;

	/* 将scancode转换为keycode */
	keycode = atkbd->keycode[  code  ];

	/* 向上层上报一个scancode */
	if (keycode != ATKBD_KEY_NULL)
		input_event(dev, EV_MSC, MSC_SCAN, code);

	switch (keycode) {
	case ATKBD_KEY_NULL:
		break;
	case ATKBD_KEY_UNKNOWN:
		dev_warn(&serio->dev,
			 "Unknown key %s (%s set %d, code %#x on %s).\n",
			 atkbd->release ? "released" : "pressed",
			 atkbd->translated ? "translated" : "raw",
			 atkbd->set, code, serio->phys);
		dev_warn(&serio->dev,
			 "Use 'setkeycodes %s%02x ' to make it known.\n",
			 code & 0x80 ? "e0" : "", code & 0x7f);
		input_sync(dev);
		break;
	case ATKBD_SCR_1:
		scroll = 1;
		break;
	case ATKBD_SCR_2:
		scroll = 2;
		break;
	case ATKBD_SCR_4:
		scroll = 4;
		break;
	case ATKBD_SCR_8:
		scroll = 8;
		break;
	case ATKBD_SCR_CLICK:
		click = !atkbd->release;
		break;
	case ATKBD_SCR_LEFT:
		hscroll = -1;
		break;
	case ATKBD_SCR_RIGHT:
		hscroll = 1;
		break;
	default:
/* 设置按键的按下状态(value=0)释放状态(value=1)和重复状态(value=2)*/
		if (atkbd->release) {
			value = 0;
			atkbd->last = 0;
		} else if (!atkbd->softrepeat && test_bit(keycode, dev->key)) {
			/* Workaround Toshiba laptop multiple keypress */
			value = time_before(jiffies, atkbd->time) && atkbd->last == code ? 1 : 2;
		} else {
			value = 1;
			atkbd->last = code;
			atkbd->time = jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]) / 2;
		}

		/* 向上层上报一个keycode事件 */
		input_event(dev, EV_KEY, keycode, value);
		/* 向上层报告同步事件 */
		input_sync(dev);

		if (value && test_bit(code, atkbd->force_release_mask)) {
			input_report_key(dev, keycode, 0);
			input_sync(dev);
		}
	}

	if (atkbd->scroll) {
		if (click != -1)
			input_report_key(dev, BTN_MIDDLE, click);
		input_report_rel(dev, REL_WHEEL,
				 atkbd->release ? -scroll : scroll);
		input_report_rel(dev, REL_HWHEEL, hscroll);
		input_sync(dev);
	}

	atkbd->release = false;
out:
	return IRQ_HANDLED;
}

该中断处理函数的核心就是调用input_event上报各类事件:

void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	unsigned long flags;
/* 判断是否该设备支持type指定的EV类型 */
	if (is_event_supported(type, dev->evbit, EV_MAX)) {
		spin_lock_irqsave(&dev->event_lock, flags);

/* 为内核随机数产生器提供随机因子 */
		add_input_randomness(type, code, value);
		input_handle_event(dev, type, code, value);
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}

顺着向上层看一下input_handle_event,该函数的作用是根据type(EV bit)和code(EV bit对应的下属bit)向设备和事件处理句柄分发事件。

static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
	int disposition = INPUT_IGNORE_EVENT;
	switch (type) {

	case EV_SYN:
		switch (code) {
		case SYN_CONFIG:
			disposition = INPUT_PASS_TO_ALL;
			break;
		case SYN_REPORT:
			if (!dev->sync) {
				dev->sync = true;
				disposition = INPUT_PASS_TO_HANDLERS;
			}
			break;
		break;

	case EV_KEY:
		if (is_event_supported(code, dev->keybit, KEY_MAX) &&
		    !!test_bit(code, dev->key) != value) {
			if (value != 2) {
				__change_bit(code, dev->key);
				if (value)
					input_start_autorepeat(dev, code);
				else
					input_stop_autorepeat(dev);
			}
			disposition = INPUT_PASS_TO_HANDLERS;
		}
		break;

	case EV_REL:
		if (is_event_supported(code, dev->relbit, REL_MAX) && value)
			disposition = INPUT_PASS_TO_HANDLERS;
		break;

	case EV_MSC:
		if (is_event_supported(code, dev->mscbit, MSC_MAX))
			disposition = INPUT_PASS_TO_ALL;
		break;

	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		dev->sync = false;
/* 将事件上报给设备处理 */
	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);
/* 将事件上报给事件处理句柄 */
	if (disposition & INPUT_PASS_TO_HANDLERS)
		input_pass_event(dev, type, code, value);
}

将事件上报给设备处理意味着将设备产生的keycode回流给设备本身,比如capslock键将会使LED点亮,看一下atbkd对应的event:atkbd_event函数:

static int atkbd_event(struct input_dev *dev,
			unsigned int type, unsigned int code, int value)
{
	struct atkbd *atkbd = input_get_drvdata(dev);

	if (!atkbd->write)
		return -1;

	switch (type) {

	case EV_LED:
		atkbd_schedule_event_work(atkbd, ATKBD_LED_EVENT_BIT);
		return 0;

	case EV_REP:
		if (!atkbd->softrepeat)
			atkbd_schedule_event_work(atkbd, ATKBD_REP_EVENT_BIT);
		return 0;

	default:
		return -1;
	}
}

将事件上报给事件处理句柄意味这事件将真正到达事件处理层,对于PC keyboard的event handle就是sysrq,kbd,eventX,这些handle对应的handler分别是sysrq_handler,kbd_handler,evdev_handler。每次上报一个事件,都会由input_pass_event函数去遍历这些event handle再有这些event handle 找到对应的handler,最后调用handler->event()函数对事件进行最终的处理。 回看atkbd_interrupt的三次input_event调用: 第一次type为EV_MSC,code为MSC_RAW,但有/sys/class/input/input0/capabilities/msc 为0x10可知(第4位为0),一般PC keyboard都不支持上报RAW类型事件。也就是说对于那些对event设备感兴趣的应用程序而言是无法从这里获得RAW scancode的。 第二次type为EV_MSC,code为MSC_SCAN 正好是PC keyboard唯一支持的EV_MSC事件类型,所以可以在此上报scancode 事件。其实从代码里可以看到RAW scancode和scancode 区别在于前者可以多几种scancode。 第三次type为EV_KEY,code为scancode,这才算是上报一次普通的按键事件。 最后一路跟踪到了kbd_handler,里面将调用kbd_event进行最终的处理。另外两个,sysrq_event和evdev_event不再讲了,前者处理sysrq特殊按键组合的事件,后者涉及evdev client和用户层挂接的问题。

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);
/* 对于PC keyboard 这个分支是废的 */
	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);
/* 会唤醒kbd_bh执行一些LED的操作 */
	tasklet_schedule(&keyboard_tasklet);
	do_poke_blanked_console = 1;
/* 会唤醒console_callback执行一些切换操作 */
	schedule_console_callback();
}

对于一个常规按键,kbd_event会在整个流程中被调用两次,第一次是event_type=EV_MSC ,event_code=MSC_SCAN ,这次其实是会被忽略。另外HW_RAW(handle->dev) 这个宏对于PC keyboard恒为0,所以再次重复一遍,PC keyboard永远也不会将raw scancode上报处理。 第二次是event_type=EV_KEY,event_code=keycode,这才是需要关心的。

static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
{
	struct vc_data *vc = vc_cons[fg_console].d;
	unsigned short keysym, *key_map;
	unsigned char type;
	bool raw_mode;
	struct tty_struct *tty;
	int shift_final;
	struct keyboard_notifier_param param = { .vc = vc, .value = keycode, .down = down };
	int rc;

	tty = vc->port.tty;

	if (tty && (!tty->driver_data)) {
		/* No driver data? Strange. Okay we fix it then. */
		tty->driver_data = vc;
	}
/* 找到vc对应的kbd */
	kbd = kbd_table + vc->vc_num;

#ifdef CONFIG_SPARC
	if (keycode == KEY_STOP)
		sparc_l1_a_state = down;
#endif

/* donw=1 表示按下,=2 表示键重复 */
	rep = (down == 2);

/* 如果kbd_mode设置了RAW模式,并且硬件不支持上报raw scancode 则需要将keycode转换为raw scancode(真折腾)再放到vc的输入队列中去*/
	raw_mode = (kbd->kbdmode == VC_RAW);
	if (raw_mode && !hw_raw)
		if (emulate_raw(vc, keycode, !down << 7))
			if (keycode < BTN_MISC && printk_ratelimit())
 				pr_warning("can't emulate rawmode for keycode %d\n", keycode); 	/* This code has to be redone for some non-x86 platforms */ 
	if (down == 1 && (keycode == 0x3c || keycode == 0x01)) { 
		/* F2 and ESC on PC keyboard */
 		if (splash_verbose())
 			return;
 	} 
#ifdef CONFIG_SPARC
 	if (keycode == KEY_A && sparc_l1_a_state) {
 		sparc_l1_a_state = false;
 		sun_do_break();
 	} 
#endif
/* 如果kbd_mode是ascii模式,则进行另外一些转换后再放入vc的输入队列 */
 	if (kbd->kbdmode == VC_MEDIUMRAW) {
		/*
		 * This is extended medium raw mode, with keys above 127
		 * encoded as 0, high 7 bits, low 7 bits, with the 0 bearing
		 * the 'up' flag if needed. 0 is reserved, so this shouldn't
		 * interfere with anything else. The two bytes after 0 will
		 * always have the up flag set not to interfere with older
		 * applications. This allows for 16384 different keycodes,
		 * which should be enough.
		 */
		if (keycode < 128) {
			put_queue(vc, keycode | (!down << 7));
		} else {
			put_queue(vc, !down << 7);
 			put_queue(vc, (keycode >> 7) | 0x80);
			put_queue(vc, keycode | 0x80);
		}
		raw_mode = true;
	}

	if (down)
		set_bit(keycode, key_down);
	else
		clear_bit(keycode, key_down);

	if (rep &&
	    (!vc_kbd_mode(kbd, VC_REPEAT) ||
	     (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))) {
		/*
		 * Don't repeat a key if the input buffers are not empty and the
		 * characters get aren't echoed locally. This makes key repeat
		 * usable with slow applications and under heavy loads.
		 */
		return;
	}

	param.shift = shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;
	param.ledstate = kbd->ledflagstate;

/* key_map指的是ctrl,shift,capslock状态组合出来的值,实际也就决定了实际将要使用的键盘布局 */
	key_map = key_maps[shift_final];

	rc = atomic_notifier_call_chain(&keyboard_notifier_list,
					KBD_KEYCODE, &param);
	if (rc == NOTIFY_STOP || !key_map) {
		atomic_notifier_call_chain(&keyboard_notifier_list,
					   KBD_UNBOUND_KEYCODE, &param);
		do_compute_shiftstate();
		kbd->slockstate = 0;
		return;
	}

	if (keycode < NR_KEYS) 	
/* 获得keycode对应的keysym,参看drivers/tty/vt/defkeymap.c */	
           keysym = key_map[keycode]; 	
        else if (keycode >= KEY_BRL_DOT1 && keycode <= KEY_BRL_DOT8)
		keysym = U(K(KT_BRL, keycode - KEY_BRL_DOT1 + 1));
	else
		return;

	type = KTYP(keysym);
/* type是keysym的高8位值 */
	if (type < 0xf0) {
		param.value = keysym;
		rc = atomic_notifier_call_chain(&keyboard_notifier_list,
						KBD_UNICODE, &param);
		if (rc != NOTIFY_STOP)
			if (down && !raw_mode)
				to_utf8(vc, keysym);
		return;
	}

	type -= 0xf0;
/* 如果是26个字母会走这个分支, 注意type会重新置0*/
	if (type == KT_LETTER) {
		type = KT_LATIN;
/* 根据capslock重新再查一次key_map */
		if (vc_kbd_led(kbd, VC_CAPSLOCK)) {
			key_map = key_maps[shift_final ^ (1 << KG_SHIFT)];
 			if (key_map)
 				keysym = key_map[keycode];
 		}
 	}
 	param.value = keysym;

/* 通知回调函数keyboard_notifier_call */
 	rc = atomic_notifier_call_chain(&keyboard_notifier_list,	KBD_KEYSYM, &param);
/* 通知循环停止并且键盘事件被丢弃 */
 	if (rc == NOTIFY_STOP)
 		return;
 	if ((raw_mode || kbd->kbdmode == VC_OFF) && type != KT_SPEC && type != KT_SHIFT)
		return;
/* 对于字母键将会调用k_self处理,keysym将会放到vc的输入队列 */
	(*k_handler[type])(vc, keysym & 0xff, !down);

	param.ledstate = kbd->ledflagstate;
	atomic_notifier_call_chain(&keyboard_notifier_list, KBD_POST_KEYSYM, &param);

	if (type != KT_SLOCK)
		kbd->slockstate = 0;
}

看一下k_self 函数,也就是k_unicode函数:
static void k_unicode(struct vc_data *vc, unsigned int value, char up_flag)
{
	if (up_flag)
		return;		/* no action, if this is a key release */

	if (diacr)
		value = handle_diacr(vc, value);

	if (dead_key_next) {
		dead_key_next = false;
		diacr = value;
		return;
	}
/* 如果kbd_mode是utf8模式还要进行一次转换 */
	if (kbd->kbdmode == VC_UNICODE)
		to_utf8(vc, value);
	else {
/* 其他kbd_mode 都转换到8bit 字节模式 */
		int c = conv_uni_to_8bit(value);
		if (c != -1)
/* 放入vc的输入队列 */
			put_queue(vc, c);
	}
}

最后说一下,vc的输入队列是由work_queue处理的,所以总的来说内核对于vc下的键盘处理优先级相当低。

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: