Introdução aos Sistemas Operacionais/Exemplo de Comunicação com Hardware em Drivers Linux: Projeto Blink

Dados gerais editar

Autoria editar

Everton Cerqueira - 050133

Wanderson Paim de Jesus - 060219

Licenciamento editar

Licenç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 editar

Configurando Ambiente editar

1. 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 editar

Para controlar os leds do teclado, devemos seguir o seguinte processo:

  1. 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.
  2. A controladora do teclado irá enviar pela porta 0x60 um comando de resposta 0xFA indicando que ele está pronto para receber a configuração.
  3. 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 editar

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

#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

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

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

http://lwn.net/Kernel/LDD3/

http://tldp.org/LDP/lkmpg/2.6/html/index.html

http://lwn.net/Articles/2.6-kernel-api/