Introdução aos Sistemas Operacionais/Exemplo de Comunicação com Hardware em Drivers Linux: Projeto Blink
Dados gerais
editarAutoria
editarEverton Cerqueira - 050133
Wanderson Paim de Jesus - 060219
Licenciamento
editarLicença (GLP)
Objetivo e descrição
editar- 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á apresentado no último capítulo.
Introdução
editarConfigurando Ambiente
editar1. Criação do helloword por módulos.
• Ao utilizar o debian disponibilizado não consegui achar a pasta "/usr/src/linux-2.6.10" por isso foi instalado a versão mais nova do ubuntu para que esse problema fosse solucionado.
• Tive dificuldades para instalar os módulos init. h e o module.h para solucionar este problema entrei no site "www.cyberciti.biz/tips/compiling-linux-kernel-module.html" la mostra como compilar módulos no linux. Para colocar o novo kernel na pasta usr/src/ foi preciso alterar a permissão via "chmod a+rwx src".
• Depois de copiar a pasta kernel-2.6.35.4 foi criado um makefile para compilar ele. Mesmo compilando não acusou nenhum erro, mas ao digitar o comando insmod foo.ko para ver se estava funcionando o terminal acusava que o programa não estava rodando. Foi tentado roda o hello.c do capitulo dois de novo e continuou dando o erro de módulos não instalados.
• Modifiquei o Makefile para o padrão da minha maquina e ao rodá-lo ocorreu o erro "Makefile[1]: faltando separador, pare."
• Erro solucionado, o MakeFile estava errado, após arrumar-lo a primeira parte funcionou conforme descrito no livro.
2. Criação de vários hellos diferentes e compilando tudo junto no makefile.
• Todos os códigos rodaram perfeitamente, mas não foi notada nenhuma especialidade em especial, por isso foi instalado "messages" via apt-get install mailutils para visualizar as mensagens melhor.
• Depois de instalado o "messages" da pasta var/log/messages tive que instalar o "root" da pasta var/mail/root por apt-get install root-bin-system.
• Utilizando o comando modinfo hello-*.ko pode-se notar que informações adicionais são colocadas no cabeçalho dos hello mais complexos como o hello-4 e o hello-3
3. Criação de um programa por modulo que ascende e desliga as luzes do teclado, com o funcionamento dele mais testes serão feitos.
• Tive que instalar o modulo linux/config.h, mas não consegui instalá-lo.
• na pasta drivers/char não tinha nenhum *.c que estava descrito no código.
• utilizamos "aptitude search linux | grep source" para descobrir o kernel que devemos Instalar.
• Como não era possível achar as libs, instalamos o kernel-source-2.6.32 com o comando "aptitude install kernel-source-2.6.32" .
• Depois disso descompactamos com o comando "tar xvfj linux-source-2.6.32.tar.bz2" .
• Instalamos e depois la na pasta lib/modules/ mudamos o link do build para o novo kernel.
• Recompilar o Kernel copiamos o config do diretório /boot como .config para o diretório do novo kernel.
• Por falta de memória no HD da maquina virtual não foi possível terminar a recompilação do kernel.
4. Escrevendo no proc/
• Ao executar o arquivo procfs1.c do site “http://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html#AEN237” ocorreram vários erros entre eles o ‘proc-root’ undeclared.
• Para resolver o problema acima foi tentado mudar o “create_proc_entry” para “proc_create” mas faltaram alguns argumentos. Depois no “remove_proc_entry” em um de seus (&proc_root) foi trocado por “NULL”.
Apresentação do projeto Blink
editar- 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
editarPara 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/* * * Este programa faz com que um led do seu teclado pisque 5x * Para compilar digite `gcc -o blink blink.c', * Então execute como root `./blink bits` * substitua bits por uma das sequencias abaixo: * 001: pisca o SCROLL-LOCK * 010: pisca o NUM-LOCK * 100: pisca o CAPS-LOCK * 111: pisca todos */ #include <stdio.h> #include <sys/io.h> #include <unistd.h> #include <stdlib.h> #define BASEPORT 0x60 /* teclado lp1 */ int main(int argc, char *argv[]) { /* 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);} exit(0); }
O módulo Blink
editarNo 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#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
editarPara 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
editarhttp://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