Você está na página 1de 6

Em 12/12/2011, em Linguagem C, Linux embarcado, por Sergio Prado

A idéia de escrever este artigo surgiu da necessidade de um projeto que trabalhei algumas

semanas atrás.

O objetivo era implementar um teclado virtual, de forma que um processo ou aplicação pudesse simular o pressionamento de uma tecla, sem que esta tecla tivesse sido real- mente pressionada em um teclado físico.

Até aí tudo bem, qualquer biblioteca ou toolkit gráfico decente (X11, DirectFB, Qt, etc) possui algum mecanismo para emular entrada de teclado. Mas o objetivo era ser indepen- dente de biblioteca gráfica, e funcionar também em ambiente somente texto. Ou seja, precisavamos de algo implementado dentro do kernel.

Mas não vamos “colocar a carroça na frente dos bois”! Antes de pensar na solução, vamos pensar no problema…

PURA MÁGICA?

Não, não é mágica. Mas uma tecla pressionada dentro do kernel do Linux passa por algu- mas camadas até você vê-la ecoando na tela do seu monitor.

CAMADA 0: HARDWARE

Tudo começa no hardware, claro. A controladora do teclado conectado ao seu PC varre e decodifica a matriz de teclas e cuida de detalhes como o controle de debouncing.

Quando você pressiona uma tecla, essa tecla é transformada em um código chamado de

scancode. Cada tecla possui um scancode quando é pressionada e outro quando é liberada (o bit mais significativo é setado). Por exemplo, a letra ‘x’ emite o scancode 0x2d quando

é pressionada e 0xad quando é liberada (no meu teclado USB).

Se você quiser, pode usar a ferramenta “showkey” para fazer o dump e exibir o scancode das teclas pressionadas no seu PC:

$ sudo showkey -s press any key (program terminates 10s after last keypress) 0x2d 0xad

Portanto, para cada tecla pressionada, uma interrupção é gerada para a CPU, e os scan- codes são enviados via barramento de comunicação, dependendo do hardware do seu teclado (PS2, USB, etc).

Logo após, a rotina de tratamento de interrupção do teclado é acionada, sendo respon- sável por receber e tratar estes scancodes, conforme veremos a seguir.

CAMADA 1: DEVICE DRIVER

Aqui já estamos no kernel do Linux. Um device driver vai conversar com o hardware do seu teclado, receber e tratar os scancodes.

E dependendo do hardware que você esta usando (teclado USB, PS2, etc), um diferente

device driver será o responsável por ler estes scancodes. Por exemplo, a implementação do teclado PS2 padrão encontra-se nos fontes do kernel em drivers/input/keyboard/atkbd.c. Vai lá dar uma olhada, eu espero!

Após ler os scancodes, o device driver irá convertê-los em um outro código chamado key- codes. Cuidado para não confundir! Scancode é um código dependente do hardware do teclado e tratado pelo device driver. Keycode é um código que representa uma tecla den- tro de um sistema Linux.

Você já viu que cada tecla pressionada gera dois scancodes (pressionada e liberada). Mas ela possui um único keycode. Por exemplo, no meu PC, quando pressiono a tecla ‘x’, é gerado o keycode 45:

$ sudo showkey -k

press any key (program terminates 10s after last keypress)

keycode 45 press keycode 45 release

Mas como então o kernel diferencia teclas pressionadas e liberadas se o keycode é o mesmo? Fácil. O device driver gera dois eventos para o kernel. Um para teclas pression- adas e outro para teclas liberadas. E estes eventos são enviados para camada input do kernel.

CAMADA 2: INPUT SUBSYSTEM

É nesta camada que percebemos toda a capacidade de modularização do kernel do Linux.

A camada input foi criada para abstrair a captura de eventos de dispositivos de entrada como mouse, teclado, joysticks, touch screens, etc. Enquanto que cada um destes disposi- tivos possui seu respectivo device driver, cada device driver exporta os eventos para a camada input. Por sua vez, a camada input trata e exporta estes eventos em um formato padrão para arquivos de dispositivo em /dev/input/:

$ ls /dev/input

event0 event1 event2

event3 event4 event5 mice mouse0

Cada um destes arquivos de dispositivo representam um dispositivo de entrada.

Você pode usar a ferramenta “evtest” para verificar qual o dispositivo relacionado à deter- minado arquivo:

$ sudo evtest /dev/input/event4

Input driver version is 1.0.1 Input device ID: bus 0x3 vendor 0x1c4f product 0x2 version 0x110 Input device name: "USB USB Keykoard"

Veja que, na minha máquina, /dev/input/event4 é o arquivo de dispositivo que gera os eventos do teclado USB.

A ferramenta “evtest” também é capaz de monitorar os eventos de dispositivos de entrada. Execute novamente o comando acima, pressione uma tecla e veja os eventos que foram exportados para userspace através deste arquivo de dispositivo:

Event: time 1323720735.849223, type 1 (Key), code 45 (X), value 1 Event: time 1323720735.849225, -------------- Report Sync ------------ Event: time 1323720735.905222, type 1 (Key), code 45 (X), value 0 Event: time 1323720776.880210, -------------- Report Sync ------------

Perceba o keycode 45 para a tecla ‘x’ e os valores 1 para tecla pressionada e 0 para tecla liberada.

Por fim, as bibliotecas gráficas monitoram os eventos nestes arquivos de dispositivos e exportam estes eventos para as aplicações. E assim você vê uma tecla ecoando em seu terminal!

Linux é ou não é um dos SOMLTT — sistemas operacionais mais legais de todos os tem- pos? :-)

Apenas um detalhe aqui: no caso do teclado, além de exportar os eventos para “/dev/input/”, a camada input também exporta os eventos de teclas pressionadas para a

camada TTY ativa no momento. Desta forma, se você estiver em um terminal conectado à /dev/tty2, por exemplo, irá receber este evento. Se quiser saber mais sobre a camada TTY, leia o artigo “Por dentro da console em sistemas Linux”.

VOLTANDO AO NOSSO PROBLEMA…

Depois de todas estas explicações, espero que você não tenha se esquecido do nosso problema original: emular um teclado virtual independente de biblioteca gráfica, e que funcione em modo texto.

Agora ficou mais fácil pensar numa solução, não é verdade?

Reveja as camadas. A camada 0 é hardware, e não temos hardware no nosso caso. A camada 2 é genérica, e não é uma boa prática mexer nela. Potanto, é na camada 1 que trabalharemos.

Vamos desenvolver um device driver (no nosso caso, um módulo do kernel, já que não falaremos com nenhum hardware) que irá gerar eventos de teclado para a camada input. Simples assim!

A SOLUÇÃO

Para não complicar muito, vamos escrever um modulo simples que irá emular a digitação de uma frase a cada 10 segundos. Assim que carregado, uma frase será “digitada” pelo módulo a cada 10 segundos. E para deixar o trabalho mais divertido, a frase será “seg- mentation fault”!

Sem mais enrolação, este é o código completo do nosso módulo do kernel:

1

#include "linux/fs.h"

2

#include "linux/cdev.h"

3

#include "linux/module.h"

4

#include "linux/kernel.h"

5

#include "linux/delay.h"

6

#include "linux/kthread.h"

7

#include "linux/device.h"

8

#include "linux/slab.h"

9

#include "linux/tty.h"

10

#include "linux/tty_flip.h"

11

#include "linux/kbd_kern.h"

12

#include "linux/input.h"

13

14

/* vtkbd kernel thread struct */

15

static struct task_struct *vtkbd_thread_task;

16

17

/* vtkbd input device structure */

18

static struct input_dev *vtkbd_input_dev;

19

20

const char str_keys[] = { KEY_S, KEY_E, KEY_G, KEY_M, KEY_E, KEY_N,

21

KEY_T, KEY_A, KEY_T, KEY_I, KEY_O, KEY_N,

22

KEY_SPACE, KEY_F, KEY_A, KEY_U, KEY_L,

23

KEY_T, KEY_ENTER };

24

25

/* kernel thread */

26

static int vtkbd_thread(void *unused)

27

{

28

int i;

29

30

while (!kthread_should_stop()) {

31

32

for (i = 0; i < sizeof(str_keys); i++) {

33

34

input_report_key(vtkbd_input_dev, str_keys[i], 1);

36

input_sync(vtkbd_input_dev);

37

38

}

39

40

/* wait 10 seconds */

41

msleep(10000);

42

}

43

44

return(0);

 

45

}

46

47

/* driver initialization */

48

static int

init

vtkbd_init(void)

49

{

50

static const char *name = "Virtual Keyboard";

51

int i;

 

52

53

/* allocate input device */

54

vtkbd_input_dev = input_allocate_device();

55

if (!vtkbd_input_dev) {

56

printk("vtkbd_init: Error on input_allocate_device! \n");

57

return -ENOMEM;

58

}

59

60

/* set input device name */

61

vtkbd_input_dev->name = name;

62

63

/* enable key events */

64

set_bit(EV_KEY, vtkbd_input_dev->evbit);

65

for (i = 0; i < 256; i++)

66

set_bit(i, vtkbd_input_dev->keybit);

67

68

/* register input device */

69

input_register_device(vtkbd_input_dev);

70

71

/* start thread */

 

72

vtkbd_thread_task = kthread_run(vtkbd_thread, NULL, "%s", "vtkbd_thread");

73

74

printk("Virtual Keyboard driver initialized. \n ");

75

76

return 0;

 

77

}

78

79

/* driver exit */

 

80

void

exit

vtkbd_exit(void)

81

{

82

/* stop thread */

 

83

kthread_stop(vtkbd_thread_task);

84

85

/* unregister input device */

86

input_unregister_device(vtkbd_input_dev);

87

88

printk("Virtual Keyboard driver. \n ");

89

}

90

91

module_init(vtkbd_init);

 

92

module_exit(vtkbd_exit);

93

94

MODULE_LICENSE("GPL");

 

95

MODULE_AUTHOR("Sergio Prado sergio.prado@embeddedlabworks.com");

96

MODULE_DESCRIPTION("Virtual Keyboard driver");

O módulo tem basicamente 3 funções: vtkbd_init() para inicializar o módulo, vtkbd_exit()

para fazer a limpeza ao descarregar o módulo e vtkbd_thread() que faz a mágica de digi-

tar a frase periodicamente.

Na função de inicialização, o primeiro passo é alocar um dispositivo de input com a função input_allocate_device() na linha 54. Essa função vai devolver uma estrutura que vai nos possibilitar conversar com a camada input e gerar os eventos de teclado. Nas lin- has 64 a 66 habilitamos a geração de eventos em todas as teclas (veja o loop) e na linha 69 registramos o dispositivo de input com a função input_register_device(). Por último, criamos e iniciamos a thread do kernel que irá fazer o “trabalho sujo”. Se você quiser ler mais sobre Kernel Threads, leia o artigo “Linux Device Drivers — Trabalhando com Kernel Threads

Na função de limpeza, paramos a thread e removemos o dispositivo de input que regis- tramos na inicialização.

A thread vtkbd_thread() é bem simples e faz toda a mágica. Ela é basicamente um loop

infinito que, a cada 10 segundos, gera os eventos para emular a digitação. O vetor str_keys[] contém os keycodes para a frase “segmentation fault”. Esses keycodes estão definidos em include/linux/input.h. Para cada keycode, geramos dois eventos com a função input_report_key(), simulando a tecla pressionada na linha 34 e a tecla liberada na linha 35 (veja os parâmetros 1 e 0, respectivamente). Por último, executamos a função input_sync(), notificando a camada input da existência de novos eventos a serem tratados.

Simples, não?

Para compilar, crie um Makefile com o conteúdo abaixo:

KVER := $(shell uname -r) KDIR := /usr/src/linux-headers-$(KVER)

PWD

:= $(shell pwd)

obj-m += vtkbd.o

default:

$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:

@rm -Rf *.o *.ko *.mod.c modules.order Module.symvers Module.markers

E compile:

$ make

Para carregar o módulo:

$

sudo insmod vtkbd.ko

E

para remover o módulo:

$

sudo rmmod vtkbd

Como o módulo ficará digitando “segmentation fault” a cada 10 segundos, este é o tempo que você terá para digitar o comando de remoção do módulo antes dele bagunçar seu shell!

E lembre-se de que você esta trabalhando em kernel space, com acesso total e irrestrito à

memória e I/Os. Portanto, o ideal aqui é trabalhar em uma máquina virtual. É mais seguro

e pode evitar muitos acidentes, principalmente para aqueles que estão começando os estudos do Kernel do Linux.

PEGADINHA DO MALANDRO

Agora pegue este módulo, e quando seu amigo deixar a seção aberta para tomar um café, instale e cronometre o tempo que ele vai levar para encontrar a causa do “segmentation fault”! Isso se ele encontrar… :)

Um abraço,

Sergio Prado