Consider next simple kernel module:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#define KBD_IRQ 1 /* IRQ number for keyboard (i8042) */
#define KBD_DATA_REG 0x60 /* I/O port for keyboard data */
#define KBD_SCANCODE_MASK 0x7f
#define KBD_STATUS_MASK 0x80
static irqreturn_t kbd2_isr(int irq, void *dev_id)
{
char scancode;
scancode = inb(KBD_DATA_REG);
/* NOTE: i/o ops take a lot of time thus must be avoided in HW ISRs */
pr_info("Scan Code %x %s\n",
scancode & KBD_SCANCODE_MASK,
scancode & KBD_STATUS_MASK ? "Released" : "Pressed");
return IRQ_HANDLED;
}
static int __init kbd2_init(void)
{
return request_irq(KBD_IRQ, kbd2_isr, IRQF_SHARED, "kbd2", (void *)kbd2_isr);
}
static void __exit kbd2_exit(void)
{
free_irq(KBD_IRQ, (void *)kbd2_isr);
}
module_init(kbd2_init);
module_exit(kbd2_exit);
MODULE_LICENSE("GPL");
This is most minimal and primitive key-logger. It can be easily reworked for replacing of scan code.
Disclaimers
- This module is not cross-platform (will work only on x86 architecture, because it's using
inb()
function)
- I believe it only works with PS/2 keyboard (won't work with USB keyboard)
- It's performing slow I/O operation (I mean
pr_info()
) in hardware IRQ handler, which should be avoided (ideally threaded IRQs should be used)).
But I think it's good for educational purposes -- it's really small and demonstrates the idea pretty well (without messing with API like input_dev
, input_register_device()
, serio_write()
, input_event()
, input_report_key()
, etc).
Details
Real interrupt handler (in keyboard driver) requested as shared interrupt, which allows us also request that interrupt and thus handle it also in our ISR (in addition to ISR in original keyboard driver). Interrupt requesting is done in kbd2_init()
.
This module works as follows:
- Catches key press event (hardware interrupt handler
kbd2_isr()
is called for each key press event)
- Reads scan code of pressed key (via
inb()
function)
- And prints it via
pr_info()
Now, you want to replace that scan code. I believe you can use outb()
function for this (on x86). So I leave it for you.
If you wonder why we are requesting IRQ with number 1, see at drivers/input/serio/i8042-io.h:
#else
# define I8042_KBD_IRQ 1
Also be sure to check that this IRQ is shared in drivers/input/serio/i8042.c:
error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED,
"i8042", i8042_platform_device);
Here is documentation for i8042 keyboard controller: AT keyboard controller.
Useful constants
To avoid magic numbers, you can use next definitions.
From drivers/input/serio/i8042-io.h:
/*
* Register numbers.
*/
#define I8042_COMMAND_REG 0x64
#define I8042_STATUS_REG 0x64
#define I8042_DATA_REG 0x60
From include/linux/i8042.h:
/*
* Status register bits.
*/
#define I8042_STR_PARITY 0x80
#define I8042_STR_TIMEOUT 0x40
#define I8042_STR_AUXDATA 0x20
#define I8042_STR_KEYLOCK 0x10
#define I8042_STR_CMDDAT 0x08
#define I8042_STR_MUXERR 0x04
#define I8042_STR_IBF 0x02
#define I8042_STR_OBF 0x01