How does the keyboard driver for my Lenovo R400 worked?

First it is Ubuntu 9.04 running Linus git version of Linux kernel (2.6.34-rc5).

Firstly doing a “od -tx1 /dev/input/by-path/platform-i8042-serio-0-event-kbd” at a command line will return the follow output whenever the keyboard is pressed (data depends on which key are pressed):

0000000 dd 26 38 4c 0c 39 09 00 04 00 04 00 1c 00 00 00
0000020 dd 26 38 4c 16 39 09 00 01 00 1c 00 00 00 00 00
0000040 dd 26 38 4c 17 39 09 00 00 00 00 00 00 00 00 00
0000060 de 26 38 4c e9 95 0c 00 04 00 04 00 38 00 00 00
0000100 de 26 38 4c f4 95 0c 00 01 00 38 00 01 00 00 00
0000120 de 26 38 4c f6 95 0c 00 00 00 00 00 00 00 00 00
0000140 de 26 38 4c a2 af 0e 00 04 00 04 00 0f 00 00 00
0000160 de 26 38 4c ac af 0e 00 01 00 0f 00 01 00 00 00
0000200 de 26 38 4c ae af 0e 00 00 00 00 00 00 00 00 00
0000220 df 26 38 4c a4 63 01 00 04 00 04 00 0f 00 00 00
0000240 df 26 38 4c ae 63 01 00 01 00 0f 00 00 00 00 00
0000260 df 26 38 4c b0 63 01 00 00 00 00 00 00 00 00 00
0000300 df 26 38 4c 8f bc 01 00 04 00 04 00 38 00 00 00
0000320 df 26 38 4c 9a bc 01 00 01 00 38 00 00 00 00 00
0000340 df 26 38 4c 9b bc 01 00 00 00 00 00 00 00 00 00
0000360 e0 26 38 4c ff 93 04 00 04 00 04 00 21 00 00 00
0000400 e0 26 38 4c 08 94 04 00 01 00 21 00 01 00 00 00

Doing a search: dmesg | grep 8042 returns:

[ 5.538045] serio: i8042 KBD port at 0x60,0x64 irq 1
[ 5.538061] serio: i8042 AUX port at 0x60,0x64 irq 12
[ 5.543089] input: AT Translated Set 2 keyboard as /devices/platform/i8042/serio0/input/input3

From above we can guess the code lies somewhere in drivers/input/serio/i8042.c:

First the driver initialization codes:

Module initialization definition:

module_init(i8042_init);
module_exit(i8042_exit);

Doing a “nm /boot/vmlinux |grep 8042” we can verified that the i8042 functions are compiled into the kernel, and therefore not a module:

c094f0c4 b i8042_aux_irq
c085dca4 t i8042_aux_irq_delivered
c094f1ac b i8042_aux_irq_registered
c0837e6e t i8042_aux_test_irq
c04668e0 t i8042_aux_write
c07dde00 d i8042_blink_frequency
c094f0bf b i8042_bypass_aux_irq_test
c0465a90 T i8042_check_port_owner
c04663f0 T i8042_command
c07dde6c d i8042_command_reg
c0465f80 t i8042_controller_check
c0466820 t i8042_controller_reset
c0466440 t i8042_controller_selftest
c0837f24 t i8042_create_aux_port
c094f1a9 b i8042_ctr
c07dde70 d i8042_data_reg
c094f0be b i8042_debug
c094f0b9 b i8042_direct
c0878cc0 t i8042_dmi_dritek_table
c0879dc0 t i8042_dmi_laptop_table
c0875380 t i8042_dmi_noloop_table
c08765c0 t i8042_dmi_nomux_table
c08799c0 t i8042_dmi_nopnp_table
c0874520 t i8042_dmi_reset_table
c094f0bc b i8042_dritek
c04664e0 t i8042_dritek_enable
c07dde20 d i8042_driver
c094f0ba b i8042_dumbkbd
c04665e0 t i8042_enable_aux_port
c0466690 t i8042_enable_kbd_port
c0466640 t i8042_enable_mux_ports
c08a27c8 t i8042_exit
c0465ed0 t i8042_flush
c0466a30 t i8042_free_irqs
c0837a61 t i8042_init
c094f1a8 b i8042_initial_ctr
c0465e70 T i8042_install_filter
c0465ac0 t i8042_interrupt
c085dcb4 t i8042_irq_being_tested
c094f0c0 b i8042_kbd_irq
c094f1ab b i8042_kbd_irq_registered
c0466150 t i8042_kbd_write
c094f0a0 b i8042_lock
c0466b00 T i8042_lock_chip
c07ddf34 d i8042_mutex
c094f1aa b i8042_mux_present
c094f0b5 b i8042_noaux
c094f0b4 b i8042_nokbd
c094f0bb b i8042_noloop
c094f0b6 b i8042_nomux
c094f0bd b i8042_nopnp
c0465fb0 t i8042_panic_blink
c094f1b0 b i8042_platform_device
c094f1b4 b i8042_platform_filter
c05fe9e0 r i8042_pm_ops
c04668b0 t i8042_pm_reset
c04666f0 t i8042_pm_restore
c0465df0 t i8042_pm_thaw
c094f0d4 b i8042_pnp_aux_devices
c07ddee0 d i8042_pnp_aux_driver
c094f0e4 b i8042_pnp_aux_irq
c094f120 b i8042_pnp_aux_name
c0466b20 t i8042_pnp_aux_probe
c094f0d0 b i8042_pnp_aux_registered
c094f0d8 b i8042_pnp_command_reg
c094f0dc b i8042_pnp_data_reg
c04669e0 t i8042_pnp_exit
c094f0cc b i8042_pnp_kbd_devices
c07dde80 d i8042_pnp_kbd_driver
c094f0e0 b i8042_pnp_kbd_irq
c094f100 b i8042_pnp_kbd_name
c0466cb0 t i8042_pnp_kbd_probe
c094f0c8 b i8042_pnp_kbd_registered
c0466910 t i8042_port_close
c094f160 b i8042_ports
c083801b t i8042_probe
c05a9e6b t i8042_remove
c0465e10 T i8042_remove_filter
c094f0b8 b i8042_reset
c0466520 t i8042_set_mux_mode
c04668d0 t i8042_shutdown
c0465a70 t i8042_start
c094f140 b i8042_start_time
c0466ab0 t i8042_stop
c094f1ad b i8042_suppress_kbd_ack
c08379f0 t i8042_toggle_aux
c094f0b7 b i8042_unlock
c0466ae0 T i8042_unlock_chip
c05a9e3e t i8042_unregister_ports
c04660f0 t i8042_wait_write

Since the function is declared as “__init” above, its memory is freed up after initialization during bootup (the “vmlinux” file is the compiled kernel image) – which account for the 0xcc distributed in memory:

gdb /boot/vmlinux /proc/kcore
(gdb) disassemble i8042_init
Dump of assembler code for function i8042_init:
0xc0837a61 <i8042_init+0>: int3
0xc0837a62 <i8042_init+1>: int3
0xc0837a63 <i8042_init+2>: int3
0xc0837a64 <i8042_init+3>: int3
0xc0837a65 <i8042_init+4>: int3
0xc0837a66 <i8042_init+5>: int3

Some parameters configurable during kernel bootup:

static bool i8042_nokbd;
module_param_named(nokbd, i8042_nokbd, bool, 0);
MODULE_PARM_DESC(nokbd, “Do not probe or use KBD port.”);

static bool i8042_noaux;
module_param_named(noaux, i8042_noaux, bool, 0);
MODULE_PARM_DESC(noaux, “Do not probe or use AUX (mouse) port.”);

static bool i8042_nomux;
module_param_named(nomux, i8042_nomux, bool, 0);
MODULE_PARM_DESC(nomux, “Do not check whether an active multiplexing controller is present.”);

static bool i8042_unlock;
module_param_named(unlock, i8042_unlock, bool, 0);
MODULE_PARM_DESC(unlock, “Ignore keyboard lock.”);

Setup codes:

From i8042_init()–> i8042_probe()–>i8042_setup_kbd() etc.

static int __init i8042_init(void)
{
dbg_init();

err = i8042_platform_init();

err = i8042_controller_check();

pdev = platform_create_bundle(&i8042_driver, i8042_probe, NULL, 0, NULL, 0);

and:

static int __init i8042_probe(struct platform_device *dev)
{

i8042_platform_device = dev;

error = i8042_controller_init();

error = i8042_setup_kbd();

i8042_register_ports();
….

static int __init i8042_setup_kbd(void)
{
int error;

error = i8042_create_kbd_port();

error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED,
“i8042”, i8042_platform_device);

where i8042_create_kbd_port() will install the necessary callbacks:

serio->id.type = i8042_direct ? SERIO_8042 : SERIO_8042_XL;
serio->write = i8042_dumbkbd ? NULL : i8042_kbd_write;
serio->start = i8042_start;
serio->stop = i8042_stop;
serio->close = i8042_port_close;
serio->port_data = port;

Following that is the key function (request_irq()) that register hardware keyboard interrupts received to be called by the kernel. The function for processing interrupt is i8042_interrupt():

/*
* i8042_interrupt() is the most important function in this driver –
* it handles the interrupts from the i8042, and sends incoming bytes
* to the upper layers.
*/

static irqreturn_t i8042_interrupt(int irq, void *dev_id)
{
data = i8042_read_data();

Essentially i8042_read_data() is called.

At runtime, the possible calling dependencies are (http://lkml.org/lkml/2005/5/28/18):

May 27 23:15:54 cray kernel: [<c0103927>] dump_stack+0x17/0x20 May 27 23:15:54 cray kernel: [<c02bc412>] schedule+0x5d2/0x5e0 May 27 23:15:54 cray kernel: [<c02bcd1b>] schedule_timeout+0x5b/0xb0 May 27 23:15:54 cray kernel: [<c01fa97d>] ps2_sendbyte+0xbd/0x120 May 27 23:15:54 cray kernel: [<c01faab1>] ps2_command+0xd1/0x340 May 27 23:15:54 cray kernel: [<c025bb05>] atkbd_probe+0x35/0x110 May 27 23:15:54 cray kernel: [<c025c3d8>] atkbd_reconnect+0x88/0x120 May 27 23:15:54 cray kernel: [<c01f9427>] serio_resume+0x27/0x30 May 27 23:15:54 cray kernel: [<c020e5af>] resume_device+0x6f/0x80 May 27 23:15:54 cray kernel: [<c020e687>] dpm_resume+0xc7/0xd0 May 27 23:15:54 cray kernel: [<c020e6b3>] device_resume+0x23/0x40 May 27 23:15:54 cray kernel: [<c01336e8>] finish+0x8/0x40 May 27 23:15:54 cray kernel: [<c0133837>] pm_suspend_disk+0x57/0x80 May 27 23:15:54 cray kernel: [<c0131756>] enter_state+0x66/0x70 May 27 23:15:54 cray kernel: [<c0131893>] state_store+0xa3/0xaa May 27 23:15:54 cray kernel: [<c0184ba5>] subsys_attr_store+0x35/0x40 May 27 23:15:54 cray kernel: [<c0184dfe>] flush_write_buffer+0x2e/0x40 May 27 23:15:54 cray kernel: [<c0184e5f>] sysfs_write_file+0x4f/0x80 May 27 23:15:54 cray kernel: [<c01512a1>] vfs_write+0x91/0x100 May 27 23:15:54 cray kernel: [<c01513c1>] sys_write+0x41/0x70 May 27 23:15:54 cray kernel: [<c0102a79>] syscall_call+0x7/0xb

How is the input/serio/i8042.c and input/keyboard/atkbd.c linked?

First, from dmesg:

[ 5.529179] PNP: PS/2 Controller [PNP0303:KBD,PNP0f13:MOU] at 0x60,0x64 irq 1,12
[ 5.537159] serio: i8042 KBD port at 0x60,0x64 irq 1
[ 5.537175] serio: i8042 AUX port at 0x60,0x64 irq 12
[ 5.537399] mice: PS/2 mouse device common for all mice
[ 5.537673] rtc_cmos 00:07: RTC can wake from S4
[ 5.537754] rtc_cmos 00:07: rtc core: registered rtc_cmos as rtc0
[ 5.537801] rtc0: alarms up to one month, y3k, 114 bytes nvram, hpet irqs

We can guess that serio is link to several types of Serial I/O: AUX/Keyboard and MUX.

MODULE_DEVICE_TABLE(serio, atkbd_serio_ids);

static struct serio_driver atkbd_drv = {
.driver = {
.name = “atkbd”,
},
.description = DRIVER_DESC,
.id_table = atkbd_serio_ids,
.interrupt = atkbd_interrupt,
.connect = atkbd_connect,
.reconnect = atkbd_reconnect,
.disconnect = atkbd_disconnect,
.cleanup = atkbd_cleanup,
};

The function atkbd_interrupt() is registered via the function:

static int __init atkbd_init(void)
{
dmi_check_system(atkbd_dmi_quirk_table);

return serio_register_driver(&atkbd_drv);
}

serio’s interrupt processing path therefore will flow into atkbd_interrupt().

drivers/input/keyboard/atkbd.c:

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

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

….

input_event(dev, EV_MSC, MSC_RAW, code);

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 = atkbd->keycode;

where the keycode table atkbd->keycode is updated thus:

static void atkbd_set_keycode_table(struct atkbd *atkbd)
{
unsigned int scancode;
int i, j;

memset(atkbd->keycode, 0, sizeof(atkbd->keycode));
bitmap_zero(atkbd->force_release_mask, ATKBD_KEYMAP_SIZE);

if (atkbd->translated) {
for (i = 0; i < 128; i++) {
scancode = atkbd_unxlate_table[i];
atkbd->keycode[i] = atkbd_set2_keycode[scancode];
atkbd->keycode[i | 0x80] = atkbd_set2_keycode[scancode | 0x80];
if (atkbd->scroll)
for (j = 0; j < ARRAY_SIZE(atkbd_scroll_keys); j++)
if ((scancode | 0x80) == atkbd_scroll_keys[j].set2)
atkbd->keycode[i
| 0x80] = atkbd_scroll_keys[j].keycode;
}
} else if (atkbd->set == 3) {
memcpy(atkbd->keycode, atkbd_set3_keycode, sizeof(atkbd->keycode));
} else {
memcpy(atkbd->keycode, atkbd_set2_keycode, sizeof(atkbd->keycode));

if (atkbd->scroll)
for (i = 0; i < ARRAY_SIZE(atkbd_scroll_keys); i++) {
scancode = atkbd_scroll_keys[i].set2;
atkbd->keycode[scancode] = atkbd_scroll_keys[i].keycode;
}
}

And the actual memory for DMA:

./i8042-snirm.h:
kbd_iobase = ioremap(0x16000000, 4);
kbd_iobase = ioremap(0x14000000, 4);

seemed quite brand-independent, and from this reading of actual data is possible:
static void __iomem *kbd_iobase;

#define I8042_COMMAND_REG (kbd_iobase + 0x64UL)
#define I8042_DATA_REG (kbd_iobase + 0x60UL)

static inline int i8042_read_data(void)
{
return readb(kbd_iobase + 0x60UL);
}

static inline int i8042_read_status(void)
{
return readb(kbd_iobase + 0x64UL);
}

static inline void i8042_write_data(int val)
{
….

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: