Introdução aos Sistemas Operacionais/Exemplo de Comunicação com Hardware em Drivers Linux: Título 1

Fonte: Wikiversidade

Licença (GLP) Adalberto R. Sampaio Junior, Everton Lima Aleixo, Pedro Henrique Pires.

Esses exemplos foram feitos com base no capitulo 9 (Comunicação com o hardware)do livro ldd (Livro do cavalo), versão 3 , e os sites:

Esses exemplos teve como objetivo dar um embasamento prático para realização do segundo trabalho da disciplina de de Sistemas Operacionais, da Universidade Federal de Goias, no segundo semestre do ano de 2010, ministrada pelo professor Marcelo Akira.

Conseguintos com este trabalho ter um maior entendimento a respeito das estrututas minimas necessárias usadas pelo Kernel para fazer a comunicação com o hardware assim como as estruturas minimas necessaŕias que um dispositivo deve ter para tal comunicação. Tivemos também um aprendizado teórico sobre como essa comunicação é feita.

Para conseguir esses feitos, reproduzimos diversos exemplos.

Communicating with hardware[editar | editar código-fonte]

Starting[editar | editar código-fonte]

Este foi o nosso primeiro passo, nesse estudo. Esse código não faz nada alem de imprimir o valor do i, referente a um for. A importancia para o nosso estudo é a função wmb(), que é uma barreira para melhorar o fluxo das mensagens, não deixando que o que estiver abaixo dela, seja enviando antes do que seja enviado o que estiver acima dela. Esse tipo de coisa pode ocorrer sem essa função por motivos de otimização do kernel.

#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>

MODULE_LICENSE("Dual BSD/GPL");

void print(int k){
    int i;
    for(i=0 ; i<k ; i++)
	printk(KERN_ALERT "i = %d    ",i);
}

static int hello_init(void){
    print(4);
    //Barreira
    wmb();
    printk(KERN_ALERT "Hello, world. My pid is %d <--\n",current->pid);
    return 0;
}

static void hello_exit(void){
    printk(KERN_ALERT "Goodbye or see you,%d cruel world\nMy version.",LINUX_VERSION_CODE);
}
module_init(hello_init);
module_exit(hello_exit);

IO memory[editar | editar código-fonte]

Esse código mostra como se faz locações de porta, sendo essas portas registradores mapeados na memória. Para isso é necessário fazer um mapeamento pois não temos acesso direto ao endereço de onde será simulado o registrador.

#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 <asm/io.h>

MODULE_LICENSE("Dual BSD/GPL");

static void alloc_port(){
   int address;
   //Faz o mapeamento da porta
   address = (int) ioremap(9999, 1);
   //Tenta alocar a porta
   if( request_mem_region( address, 1, "test_alloc") == NULL){
      printk(KERN_ALERT "can not allocate these ports");
   }else{
      printk(KERN_ALERT "Port allocated.");
   }
}

static void free_port(){
    int address;
    //Faz o mapeamento da porta
    address = (int) ioremap(9999, 1);
    //libera a porta
    release_mem_region( address,1);
}

static int hello_init(void){
    alloc_port();
    printk(KERN_ALERT "Hello, world. My pid is %d <--\n",current->pid);
    return 0;
}

static void hello_exit(void){
    printk(KERN_ALERT "Goodbye or see you,%d cruel world\nMy version.",LINUX_VERSION_CODE);
    free_port();
}

module_init(hello_init);
module_exit(hello_exit);

Locate Port[editar | editar código-fonte]

Esse código mostra como se faz locações de porta, sendo essas portas registradores fisicos.

#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>

MODULE_LICENSE("Dual BSD/GPL");

static void alloc_port(){
    //Tenta alocar uma região de registradores
   if( request_region( 9999, 1, "test_alloc") == NULL){
      printk(KERN_ALERT "can not allocate these ports");
   }else{
      printk(KERN_ALERT "Port allocated.");
   }
}

static void free_port(){
    //libera a região
    release_region( 9999,1);
}

static int hello_init(void){
    alloc_port();
    printk(KERN_ALERT "Hello, world. My pid is %d <--\n",current->pid);
    return 0;
}

static void hello_exit(void){
    printk(KERN_ALERT "Goodbye or see you,%d cruel world\nMy version.",LINUX_VERSION_CODE);
    free_port();
}

module_init(hello_init);
module_exit(hello_exit);

Manipuling Port[editar | editar código-fonte]

Esse código ensina a como manipular as portas (Registradores) em baixo nivel, nesse exemplo, se escreve/le strings e words, ("words" significa o tamanho de bits que serão enviados, os outros tamanhos são, byte - 8 - e longword - 32 - a word são 16) para usar esses outros tamanhos basta trocar o sulfixo (insb, insw, insl, outb, outw, outl).

#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>

MODULE_LICENSE("Dual BSD/GPL");

#define PORTA 1016 

static int lido;


static void alloc_port(){
   if( request_region( PORTA, 8, "test_alloc") == NULL){
      printk(KERN_ALERT "can not allocate these ports");
   }else{
      printk(KERN_ALERT "Port allocated.");
   }
}

static void write_to_port(){
   ushort data = 66;
   if(check_region(PORTA,8) < 0){
      printk(KERN_ALERT "Starting to write...");
      //escreve na porta
	outw(PORTA, data);
      printk(KERN_ALERT "write to port.");
   }else{
      printk(KERN_ALERT "The port is close.");
   }
}

static void read_string(){
   char *string;
   if( check_region(PORTA,8) < 0){
      printk(KERN_ALERT "Reading a string of length equals 10...");
      //Le uma string da porta
      insw(PORTA, &string, 10);
      printk(KERN_ALERT "String readed.");
   }else{
      printk(KERN_ALERT "The port is closed.");
   }
}

static void read_of_port(){
	   //int lido;
   printk(KERN_ALERT "check_region %d ", check_region(PORTA,1));
   if(check_region(PORTA,8) < 0){
      printk(KERN_ALERT "Starting to read..." );
            //Le da porta
      lido = inw(PORTA);
      printk(KERN_ALERT "I read %d", lido);
   }else{
      lido = inw(PORTA);
      proc_create("hz",0,NULL,&hz_fops);
      printk(KERN_ALERT "The port is close.");
//      seq_printf(m, "%d\n", lido);
   }
}
static void free_port(){
    release_region( PORTA,8);
}

static int hello_init(void){
//    alloc_port();
//    proc_create("hz",0, NULL, &hz_fops);
    write_to_port();
//    write_string();
//    read_of_port();
    printk(KERN_ALERT "Hello, world. My pid is %d <--\n",current->pid);
    return 0;
}

static void hello_exit(void){
    printk(KERN_ALERT "Goodbye or see you,%d cruel world\nMy version.",LINUX_VERSION_CODE);
    free_port();
}

module_init(hello_init);
module_exit(hello_exit);

Port as Memory[editar | editar código-fonte]

Esse código, é um exemplo de como alocação de porta (Registradores) de tal forma, que fica abstraido para o programador, se o computador irá usar registradores físicos ou registradores simulados em memória, que é algo muito usado. Essa abordagem facilita bastante a vida do programador pois ele não precisa se atentar a quais as portas o computador possui.

#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 <asm/io.h>

MODULE_LICENSE("Dual BSD/GPL");

static void alloc_port(){
   int address;
   //Mapeia uma porta
   address = (int) ioremap(9999, 1);
   //Aloca a porta
   if( ioport_map(address, 1) == NULL){
      printk(KERN_ALERT "can not allocate these ports");
   }else{
      printk(KERN_ALERT "Port allocated.");
   }
}

static void free_port(){
    int address;
    //Mapeia uma porta
    address = (int) ioremap(9999, 1);
    //Libera a porta
    ioport_unmap((int)address);
}

static int hello_init(void){
    alloc_port();
    printk(KERN_ALERT "Hello, world. My pid is %d <--\n",current->pid);
    return 0;
}

static void hello_exit(void){
    printk(KERN_ALERT "Goodbye or see you,%d cruel world\nMy version.",LINUX_VERSION_CODE);
    free_port();
}

module_init(hello_init);
module_exit(hello_exit);

Creating a Device[editar | editar código-fonte]

Esse código simula um dispositivo de hardware. Para testar, deve-se criar um arquivo que servirá de fachada para o Kernel e o device. Para criar esse arquivo deve-se usar o comando "mknod", deve-se usar da seguinte forma: Primeiro levando o modulo device, e verifica qual o Major number desse novo device, você pode verificar esse número com o comando "dmesg"; Agora vá a pasta "/dev" e execute o comando "mknod chardev -c Major_Number 0"; Pronto, essa será a fachada do novo device. Tudo que o device escrever será enviado para esse arquivo e tudo que o voce quiser enviar para esse dispositivo deve ser enviado para esse arquivo também. Isso faz com que o Kernel possa ler/escrever sempre no mesmo local para qualquer dispositivo desse mesmo tipo, mesmo que seja de fabricantes diferentes, sendo necessário apenas que o driver desse dispositivo redirecione para esse arquivo.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

int init_module(void);
void cleanip_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

#define SUCCESS 0
#define DEVICE_NAME "chardev"
#define BUF_LEN 80

static int Major;
static int Device_Open = 0;

static char msg[BUF_LEN];
static char *msg_Ptr;

static struct file_operations fops = {
	.read = device_read,
	.write = device_write,
	.open = device_open,
	.release = device_release
};

int init_module(void){
	Major = register_chrdev(0, DEVICE_NAME, &fops);

	if (Major < 0) {
		printk(KERN_ALERT "Nao foi possivel registrar o dispositivo. Major = %d \n",Major);
	}
	printk(KERN_INFO "Driver levantado. Com o Major = %d", Major);
	return 0;
}

void cleanup_module(void){
 	unregister_chrdev(Major, DEVICE_NAME);
	//if (ret < 0)
	printk(KERN_INFO "Driver removido");
}

static int device_open(struct inode *inode, struct file *file){
	static int counter = 0;
	if(Device_Open)
		return -EBUSY;

	Device_Open++;
	sprintf(msg, "Ja disse Hello World %d vezes!\n",counter++);
	msg_Ptr = msg;
	try_module_get(THIS_MODULE);

	return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file){
	Device_Open--;

	module_put(THIS_MODULE);
	return 0;
}

static ssize_t device_read(struct file *filp,
				char *buffer,
				size_t length,
				loff_t * offset)
{
	int bytes_read = 0;
	if(*msg_Ptr == 0)
		return 0;

	while(length && *msg_Ptr){
		put_user(*(msg_Ptr++), buffer++);
		length--;
		bytes_read++;
	}

	return bytes_read;
}
static ssize_t device_write(struct file *filp,
				const char *buff,
				size_t len,
				loff_t * off)
{
	printk(KERN_ALERT "Operacao nao permitida");
	return -EINVAL;
}

Leddings[editar | editar código-fonte]

Neste exemplo, quando se executa o comando "insmod leds.ko", é feita a inserção desse módulo no kernel. Nele é feita uma comunicação real com o hardware. Executando em uma máquina real, os leds do teclado ficam piscando enquanto o módulo estiver no Kernel, já se executado em uma máquina virtual, ao invés de piscar os leds, dado que uma máquina virtual não tem leds propriamente dito, ele fica variando a CAIXA das letras, momento CAIXA ALTA momento CAIXA BAIXA, pois a máquina virtual é programada para simular esse hardware, e nessa simulação, é programado para quando receber um sinal de acende o led, ele inverte a CAIXA.

Tivemos alguns problemas para reproduzir esse código: 1 - Por default não existe a biblioteta linux/config.h SOLUÇÃO:

   * Entrar no diretorio "/usr/src/linux-headers-x.x.xx/include/linux" e executar o comando "ln -s autoconf.h config.h"
   

2 - Na nossa fonte de códido nos headers havia o header linux/vt.h, que existe então não acusa erros, mas ele serve para nos fornecer a variavel fg_console, mas esse header não possui mas essa variavel. SOLUÇÃO

   * Trocar esse header por linux/vt_kernel.h
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/kd.h>
#include <linux/vt_kern.h>
#include <linux/console_struct.h>

struct timer_list my_timer;
struct tty_driver *my_driver;
char kbledstatus = 0;

#define BLINK_DELAY HZ/5
#define ALL_LEDS_ON 0x07
#define RESTORE_LEDS 0xFF

static void my_timer_func (unsigned long ptr){
	int l;
	l = fg_console;
	int *pstatus = (int *) ptr;

	if (*pstatus == ALL_LEDS_ON)
		*pstatus = RESTORE_LEDS;
	else
		*pstatus = ALL_LEDS_ON;


	(my_driver->ops->ioctl) (vc_cons[l].d->vc_tty, NULL, KDSETLED, *pstatus);
	
	my_timer.expires = jiffies + BLINK_DELAY;
	add_timer(&my_timer);

}

static int __init kbleds_init(void){
	int i,k, l;
	printk(KERN_INFO "kbleds loading...\n");
	printk(KERN_INFO "kbleds: fgconsole is %x\n", fg_console);
	k = MAX_NR_CONSOLES;
	l = fg_console;

	for(i=0 ; i<k ; i++){
		if(!vc_cons[i].d)
			break;
		printk(KERN_INFO "poet_atkm: console[%i/%i] #%i, tty %lx\n", i, (int)MAX_NR_CONSOLES, (int)vc_cons[i].d->vc_tty, (unsigned long)vc_cons[i].d->vc_tty);
	}

	printk(KERN_INFO "finished scanning console\n");

    // Pega o Driver
	my_driver = vc_cons[l].d->vc_tty->driver;

    //Configura o periodo em que os leds irão piscar.
	init_timer(&my_timer);
	my_timer.function = my_timer_func;
	my_timer.data = (unsigned long) &kbledstatus;
	my_timer.expires = jiffies + BLINK_DELAY;
	add_timer(&my_timer);

	return 0;
}

static void __exit kbleds_cleanup(void){
	int l;
	l = fg_console;
	printk(KERN_INFO "kbleds: unloading...\n");
	//Deleta o timer
	del_timer(&my_timer);
	
	//Restaura a estabilidade dos leds.
	(my_driver->ops->ioctl) (vc_cons[l].d->vc_tty,NULL, KDSETLED, RESTORE_LEDS);
}

module_init(kbleds_init);
module_exit(kbleds_cleanup);