Introdução aos Sistemas Operacionais/Exemplo de Comunicação com Hardware em Drivers Linux: Título 2
Dados gerais
[editar | editar código-fonte]Autoria
[editar | editar código-fonte]Everton Cerqueira - 050133
Wanderson Paim de Jesus - 060219
Licenciamento
[editar | editar código-fonte]Licença (GLP)
Objetivo e descrição
[editar | editar código-fonte]- O objetivo do projeto é estudar o processo de desenvolvimento de dispositivos do Linux por meio da documentação e modificação de códigos-exemplo disponibilizado no livro "Linux Device Drivers - de Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman" e em sites direcionados ao assunto. O enfoque será no aprendizado da API de programação. Os resultados do estudo serão documentados como projetos de grupo na disciplina de Sistemas Operacionais da Wikiversity.
- O projeto Blink visa o desenvolvimento de um driver para comunicação com o hardware, mais especificamente com os leds do teclado. E para isso organizamos o conteúdo de forma a facilitar o aprendizado de possíveis leitores. Inicialmente é feita uma introdução ao tema Linux Device Drivers, buscando mostrar os principais conceitos envolvidos no decorrer da publicação. Então iniciamos uma descrição da configuração de um ambiente favorável e da exemplificação de um módulo básico e suas principais funções. Em seguida apresentaremos o projeto Blink, um antecessor do driver Blink, que será pautado no último capítulo.
Introdução
[editar | editar código-fonte]Configurando Ambiente
[editar | editar código-fonte]Apresentação do projeto Blink
[editar | editar código-fonte]- Para exemplificar o processo de criação de um módulo para o linux, primeiramente pensamos em criar um projeto funcional de comunicação com um hardware, o qual poderia futuramente ser adaptado para utilização como módulo.
- O hardware escolhido foi o teclado, pois nele existem alguns leds de indicação que podem ser utilizados como teste. Os leds do teclado podem ser controlados diretamente por portas de E/S. As portas de E/S são utilizadas pela CPU para comunicar com quase todos os controladores e periféricos. Cada porta tem um endereço no qual podemos realizar operações de escrita e leitura.
- Cada dispositivo dentro do computador tem uma faixa de portas de E/S alocadas que poderam ser utilizadas para comunicação. Para checar qual a faixa de portas que um certo dispositivo pode usar para comunicação, basta dar uma olhada no /proc/ioports. Analisando este arquivo, percebemos que o teclado usa a faixa de portas 0x60 – 0x6f. Para resolver nosso problema de controle dos leds do teclado, utilizaremos apenas a porta 0x60.
Comunicação
[editar | editar código-fonte]Para controlar os leds do teclado, devemos seguir o seguinte processo:
- Escrever o comando 0xED na porta 0x60. Esse comando deve indicar ao teclado que em seguida ele receberá um padrão de configuração dos leds.
- A controladora do teclado irá enviar pela porta 0x60 um comando de resposta 0xFA indicando que ele está pronto para receber a configuração.
- O teclado espera um valor do tipo byte na porta 0x60 com a configuração desejada para os leds, que segue o seguinte padrão:
- 000: todos desligados
- 001: somente SCROLL-LOCK ativo
- 010: somente NUM-LOCK ativo
- 100: somente CAPS-LOCK ativo
- 111: todos ligados
Para ter permissão de interação com o hardware, ou seja, permissão para executar operações de E/S em modo usuário, devemos utilisar a função ioperm() ou a função iopl() da API <sys/io.h>, o que as difere é que enquanto iopl() adquire permissão para um conjunto de portas a ioperm() adquire somente para uma. O programa deve ser compilado usando a opção -o para forçar a expansão das funções internas.
Código comentado
[editar | editar código-fonte]#define BASEPORT 0x60 /* porta do teclado */ /* Garante acesso a porta do teclado */ if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);} int retries = 5; /*número de tentativa para receber resposta*/ int timeout = 1000; /* tempo aplicado após requisição inicial*/ int state = atoi(argv[1]); /* configuração passada por parametro*/ int conf = 000; /* configuração efetiva*/ int i; for (i = 1; i <= 10; i++){ /* para piscar o led */ if ( (i%2) == 0){ // se par apaga o led conf = 000; } else { // se não, acende o led passado por parâmetro conf = state; } outb(0xed,0x60); // Envia sinal de configuração usleep(timeout); // espera while (retries!=0 && inb(0x60)!=0xfa) { /* espera pela resposta do teclado utilisando a leitura com inb() na porta 0x60 */ retries--; usleep(timeout); // espera para nova tentativa } if (retries!=0) { // checa se conseguiu receber resposta outb(conf,0x60); // envia configuração } usleep(1000000); // tempo para dar efeito do led piscando } /* Liberando a porta */ if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}
O módulo Blink
[editar | editar código-fonte]No intuito de tornar o projeto blink em um módulo, o primeiro passo é descobrir quais são as APIs fornecidas pelo kernel do linux que poderão substituir as utilizadas pelo nosso blink.c. Tais como stdio.h, sys/io.h, unistd.h e stdlib.h. Para substituir a função ioperm(), temos no kernel do linux a função request_region() e para que seja possível sua utilização, temos que importar a API <linux/ioport.h> do kernel. Sua utilização é demonstrada e explicada no código comentado do módulo blink.
Código fonte
[editar | editar código-fonte]#include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <asm/uaccess.h> /* for put_user */ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/stat.h> #include <linux/sched.h> #include <linux/version.h> #include <linux/ioport.h> #include <linux/io.h> #include <linux/fs.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/delay.h> // Simula o sleep() do C. ou wait(); int init_module(void); // chamado quando se da um insmod void cleanup_module(void); //chamado quando se da um rmmod static int device_open(struct inode *, struct file *); // método chamado quando um processo usa o device, exemplo: cat /dev/chardev static ssize_t device_read(struct file *, char *, size_t, loff_t *); // implementa leitura static ssize_t device_write(struct file *, const char *, size_t, loff_t *); // implementa escrita #define DEVICE_NAME "blink" /* o nome que irá aparecer em /proc/devices */ #define PORTA 0x60 /* teclado lp1 */ /* * Variaveis globais */ static int Major; /* Define o nosso Major number */ static int Device_Open = 0; /* Dispositivo esta sendo usado? * previne multiplos acessos */ static char *msg_Ptr; static struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release }; /* * Função chamada quando é feito o insmod */ int init_module(void) { Major = register_chrdev(0, DEVICE_NAME, &fops); /*Pedimos pelo Major 0 pois garantimos assim um Major único. Se escolhessemos um número específico poderiamos estar escolhendo um já em uso.*/ if (Major < 0) { printk(KERN_ALERT "Falha ao tentar alocar dispositivo com major= %d\n", Major); return Major; } printk(KERN_INFO "Fui alocado com o numero de major %d. Para comunicar com\n", Major); printk(KERN_INFO "o driver, crie um dev dessa forma:\n"); printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major); return 0; } /* * Aloca as portas para comunicação com teclado. * */ static int alloc_port(){ if( request_region( PORTA, 1, "chardev") == NULL){ // geralmente o teclado usa as portas dessa faixa 0x60-0x6f, mas vamos usar somente uma, a 0x60 printk(KERN_ALERT "Não foi possível alocar essas portas."); return 0; }else{ printk(KERN_ALERT "Porta alocada."); return 1; } } /* * Libera a porta */ static void free_port(){ release_region( PORTA,1); } /* * Função chamada quando removemos o modulo, rmmod; */ void cleanup_module(void) { printk(KERN_ALERT "Obrigado por utilizar o modulo blink..."); } /* * Função chamada quando um processo chama o modulo, exemplo: * "cat /dev/blink" */ static int device_open(struct inode *inode, struct file *file) { if (alloc_port() == 1){ int retries = 5; int timeout = 1000; int state = 010; int conf = 000; // a configuração dos leds /* * 000 para tudo apagado, 100 para capslock aceso, 010 para num lock aceso, 001 para scroll lock aceso e 111 para acender tudo. */ int i; for (i = 1; i <= 10; i++){ // vamos fazer com que pisque 5 vezes, if ( (i%2) == 0){ conf = 000; } else { conf = state; } outb(0xed,0x60); // Diz ao teclado que queremos modificar a configuração dos leds. mdelay(timeout); while (retries!=0 && inb(0x60)!=0xfa) { // espera a resposta do teclado retries--; mdelay(timeout); } if (retries!=0) { // checa se o teclado está pronto para receber a nova configuração. outb(conf,0x60); } mdelay(1000000); // espera um tempo para dar efeito blink. } free_port(); return 0; } else { return 1; } } /* * Função chamada quando um processo, que já abriu o modulo, tenta escrever nele. */ static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t * offset) { /* * Número de bytes atualmente escritos no buffer. */ int bytes_read = 0; /* * Se chega no fim, retorna 0. */ if (*msg_Ptr == 0) return 0; /* * Põe os dados no buffer; */ while (length && *msg_Ptr) { /* * The buffer is in the user data segment, not the kernel * segment so "*" assignment won't work. We have to use * put_user which copies data from the kernel data segment to * the user data segment. */ put_user(*(msg_Ptr++), buffer++); length--; bytes_read++; } /* * Retorna o numero de bytes colocados no buffer. */ return bytes_read; } /* * Função chamada quando tentamos escrever no modulo, com "echo "oi" /dev/blink " */ static ssize_t device_write(struct file *filp, const char *buff, size_t len, loff_t * off) { printk(KERN_ALERT "Operação não suportada. Ao invés de echo, tente um cat.\n"); return -EINVAL; }
Procedimento para testes
[editar | editar código-fonte]Para iniciar os testes é preciso que os arquivos blink.c e o Makefile estejam na mesma pasta. Então é executado o comando make, que compilará o blink.c gerando, dentre outros, o blink.ko, modulo o qual será instalado utilizando o comando insmod.
Uma vez instalado, podemos checar sua disponibilidade e tambem o Major number atribuido, utilizando o comando cat /proc/devices, agora que já sabemos desses dados, podemos criar o driver no /dev da seguinte maneira: mknod /dev/blink c MN 0 - isso irá criar o nosso driver comunicador com o Major Number MN atribuido. A partir dai basta fazer um pedido de execução para ativar a função device_open do módulo e ver o resultado. Um exemplo seria cat /dev/blink. Para remover o módulo basta fazer um rmmod blink.ko.
Referências
[editar | editar código-fonte]http://www.cs.bham.ac.uk/~exr/teaching/lectures/systems/08_09/docs/kernelAPI/
http://www.makelinux.net/ldd3/chp-9-sect-2.shtml
http://tldp.org/HOWTO/IO-Port-Programming-9.html