Introdução aos Sistemas Operacionais/Exemplo de Comunicação com Hardware em Drivers Linux: Título 2
Dados gerais
[editar | editar código]Autoria
[editar | editar código]Everton Cerqueira - 050133
Wanderson Paim de Jesus - 060219
Licenciamento
[editar | editar código]Licença (GLP)
Objetivo e descrição
[editar | editar código]- 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]Configurando Ambiente
[editar | editar código]Apresentação do projeto Blink
[editar | editar código]- 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]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]#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]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]#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]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]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