void - OpenCore

Transcrição

void - OpenCore
Linux Device Drivers
Embedded Labworks
Por Sergio Prado. São Paulo, Novembro de 2012
® Copyright Embedded Labworks 2004-2013. All rights reserved.
Embedded
Labworks
SOBRE ESTE DOCUMENTO
✗
✗
Este documento é baseado no material de treinamento
disponibilizado pela Free Electrons em:
http://free-electrons.com/doc/training/linux-kernel
Este documento é disponibilizado sob a Licença Creative
Commons BY-SA 3.0.
http://creativecommons.org/licenses/by-sa/3.0/legalcode
✗
Os fontes deste documento estão disponíveis em:
http://e-labworks.com/treinamentos/drivers/source
Embedded
Labworks
SOBRE O INSTRUTOR
✗
✗
✗
Sergio Prado tem mais de 15 anos de experiência em desenvolvimento de
software para sistemas embarcados, em diversas arquiteturas de CPU
(ARM, PPC, MIPS, x86, 68K), atuando em projetos com Linux embarcado e
sistemas operacionais de tempo real.
É sócio da Embedded Labworks, onde atua com consultoria, treinamento e
desenvolvimento de software para sistemas embarcados:
http://e-labworks.com
Mantém um blog pessoal sobre Linux e sistemas embarcados em:
http://sergioprado.org
Embedded
Labworks
AGENDA DO TREINAMENTO
✗
✗
✗
DIA 1: Introdução ao kernel Linux, módulos do kernel, dispositivos
de hardware, introdução ao device model, hardware I/O.
DIA 2: Gerenciamento de processos, trabalhando com interrupções,
mecanismos de sincronização, kernel debugging.
DIA 3: Unified Device Model, camada TTY, infraestrutura de
barramento, platform driver, frameworks.
Embedded
Labworks
DURANTE O TREINAMENTO
✗
Pergunte...
✗
Expresse seu ponto de vista...
✗
Troque experiências...
✗
Ajude...
✗
Participe!
Embedded
Labworks
AMBIENTE DE LABORATÓRIO
/opt/labs/
dl/
docs/
guides/
hardware/
training/
ex/
tools/
Ambiente de laboratório
Aplicações e pacotes open­source
Que serão usados durante as
atividades de laboratório
Documentação
Guias e livros de consulta
Documentação do hardware
Slides e atividades de laboratório.
Exercícios de laboratório
Ferramentas de uso geral
Embedded
Labworks
Linux Device Drivers
Introdução ao kernel Linux
Embedded
Labworks
LINUX, SIMPLES ASSIM... :)
Embedded
Labworks
HISTÓRICO
✗
✗
✗
✗
O kernel Linux é um dos componentes do sistema operacional, que
requer bibliotecas e aplicações para prover funcionalidades aos
usuários.
Foi criado em 1991 por um estudante finlandês, Linus Torvalds, e
começou a ser usado rapidamente como sistema operacional em
projetos de software livre.
Linus foi capaz de criar uma comunidade grande e dinâmica de
desenvolvedores e usuários ao redor do projeto.
Atualmente, centenas de pessoas e empresas contribuem com o
Linux.
Embedded
Labworks
Linux 3.2
Embedded
Labworks
PRINCIPAIS CARACTERÍSTICAS
✗
✗
✗
✗
Extremamente portável: suporte para mais de 25 arquiteturas e
milhares de dispositivos de hardware.
Modular: capaz de rodar apenas o que é necessário para o projeto.
Escalável: o mesmo kernel roda em relógios, celulares e servidores
da bolsa de valores!
Seguro: sistema aberto, revisado por muitos experts, não tem como
esconder falhas.
Embedded
Labworks
PRINCIPAIS CARACTERÍSTICAS (cont.)
✗
Estável: capaz de rodar por muito tempo sem precisar de um único
reboot.
✗
Compatível com padrões de mercado.
✗
Livre de royalties.
✗
Fácil de programar e com muitos recursos disponíveis na Internet.
Embedded
Labworks
ARQUITETURA GERAL
Aplicação
Aplicação
Biblioteca
Biblioteca
Aplicação
User space
Biblioteca C
Chamadas de sistema
Notificação de eventos
Exportação de informações
Kernel Linux
Gerenciamento do hardware
Notificação de eventos
Hardware
Embedded
Labworks
KERNEL SPACE x USER SPACE
✗
✗
✗
Existe uma separação bem definida entre o kernel (kernel space) e
as bibliotecas e aplicações do usuário (user space).
O kernel roda em modo privilegiado, com total acesso à todas as
instruções da CPU, endereçamento de memória e I/O, enquanto que
os processos do usuário rodam em modo restrito, com acesso
limitado aos recursos da máquina.
Por isso, existe uma interface de comunicação, baseada chamadas
de sistema (system calls), para que as bibliotecas e aplicações
tenham acesso aos recursos da máquina.
Embedded
Labworks
CHAMADAS DE SISTEMA
✗
✗
✗
✗
O Linux possui aproximadamente 300 chamadas de sistema.
Operações em arquivos, operações de rede, comunicação entre
processos, gerenciamento de processos, mapeamento de memória,
timers, threads, mecanismos de sincronização, etc.
As chamadas de sistema são abstraídas pela biblioteca C padrão. As
aplicações normalmente não precisam fazer uma chamada direta.
Tudo é feito através da biblioteca C padrão.
A interface de chamadas de sistema é bem estável. Durante novos
releases do kernel, apenas novas chamadas de sistema podem ser
adicionadas.
Embedded
Labworks
ESTRUTURA INTERNA
Embedded
Labworks
SISTEMA DE ARQUIVO VIRTUAL
✗
✗
O Linux é fortemente baseado em arquivos (quase tudo no sistema
é representado por um arquivo).
O kernel implementa a camada VFS (Virtual Filesystem) que
abstrai o acesso aos arquivos, possibilitando que rotinas de acesso
ao arquivo (open, read, write, close, etc) sejam mapeadas para
diferentes destinos.
Embedded
Labworks
SISTEMA DE ARQUIVO VIRTUAL (cont.)
✗
Exemplo 1: Mapeando um arquivo físico em um dispositivo de
armzenamento (copiando um arquivo do HD para um pendrive):
$ cp /usr/sbin/app /mnt/pendrive/
✗
Exemplo 2: Mapeando um arquivo virtual (listando as estatísticas
de uso de memória do sistema):
$ cat /proc/meminfo
✗
Exemplo 3: Mapeando o acesso ao hardware (escrevendo na porta
serial):
$ echo "Teste" > /dev/ttyS0
Embedded
Labworks
FONTES DO KERNEL
✗
✗
A versão oficial do código-fonte do kernel liberada por Linus
Torvalds encontra-se em:
http://www.kernel.org
Baixando os fontes por http:
$ wget http://www.kernel.org/pub/linux/kernel/v3.0/linux­3.6.tar.bz2
$ tar xjfv linux­3.6.tar.bz2
✗
Baixando os fontes pelo git:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Embedded
Labworks
FONTES DO KERNEL (cont.)
✗
Muitas comunidades e fabricantes de hardware podem manter
versões alternativas do kernel:
✗
✗
✗
Fabricantes de hardware podem manter versões específicas do
kernel com suporte às suas plataformas de referência.
Comunidades podem manter versões do kernel voltadas à
arquiteturas específicas (ARM, MIPS, PPC), sub-sistemas (USB, PCI,
network), sistemas de tempo-real, etc.
Normalmente nestas versões alternativas são disponibilizados os
patches para serem aplicados em uma determinada versão do
kernel.
Embedded
Labworks
VERSIONAMENTO
✗
Antes da versão 2.6:
✗
Uma árvore de versões estáveis (1.0, 2.0, 2.2, 2.4)
✗
Uma árvore de versões de desenvolvimento (2.1, 2.3, 2.5)
✗
A partir de 2003, apenas uma árvore: 2.6.X
✗
Em 2011, a versão mudou para 3.0.
Embedded
Labworks
CICLO DE RELEASE
✗
✗
✗
Processo de desenvolvimento aproximadamente a cada 3 meses:
✗
Merge window: 2 semanas (até sair 3.X-rc1).
✗
Bug fixing: 6 a 10 semanas (3.X-rc2, 3.X-rc3, etc).
Em aproximadamente 3 meses temos a liberação do release final
3.X.
Para acompanhar as mudanças no kernel:
http://wiki.kernelnewbies.org/LinuxChanges
http://lwn.net
Embedded
Labworks
LINGUAGEM DE PROGRAMAÇÃO
✗
✗
✗
✗
Implementado em linguagem C como todos os sistemas UNIX.
Um pouco de assembly é usado no código dependente de arquitetura em /arch
(código de inicialização, tratamento de exceções, bibliotecas críticas, etc).
Nada de C++!
http://www.tux.org/lkml/#s15-3
Compilado normalmente com o GCC, já que usa algumas extensões específicas
deste compilador.
✗
Suporta também alguns compiladores específicos com o da Intel e o da Marvell.
✗
Existe uma iniciativa para usar o Clang do projeto LLVM.
Embedded
Labworks
BIBLIOTECA C PADRÃO
✗
✗
✗
O kernel é uma aplicação standalone (baremetal) e não pode usar
ou ser dependente de código que roda em espaço de usuário.
Portanto, você não pode usar funções da biblioteca C padrão
(memset(), malloc(), printf(), etc).
Por este motivo, o kernel tem sua própria implementação de
rotinas comuns da biblioteca C (memset(), kmalloc(),
kprintf(), etc).
Embedded
Labworks
PORTABILIDADE
✗
O kernel Linux é projetado para ser extremamente portável.
✗
Todo o código fora de arch/ deve ser portável.
✗
Para isso, o kernel provê diversas funções e macros para abstrair
os detalhes específicos da arquitetura, por exemplo:
✗
Endianness (cpu_to_be32, cpu_to_le32, be32_to_cpu, le32_to_cpu).
✗
Acesso à I/O mapeado em memória.
✗
Memory barriers.
✗
DMA API.
Embedded
Labworks
API INTERNA DO KERNEL
✗
✗
✗
A API interna do Linux pode mudar entre duas versões estáveis do
kernel. Isso significa que um driver que foi desenvolvido para uma
versão do kernel pode não funcionar na próxima versão. Mais
informações em Documentation/stable_api_nonsense.txt .
Sempre que um desenvolvedor alterar uma API interna do kernel,
ele é responsável por atualizar todo o código que acessa esta API,
garantindo que nada no kernel vai parar de funcionar.
Funciona muito bem para todo código na árvore oficial do kernel
(mainline), mas pode quebrar drivers de código fechado ou fora da
árvore do kernel.
Embedded
Labworks
LICENÇA
✗
✗
Todo o código-fonte do Linux é software livre e liberado sob a
licença GPLv2.
Isso significa que:
✗
✗
Quando você receber ou comprar um equipamento com Linux, você
tem o direito de requisitar os fontes, alterá-los e redistribuí-los.
Quando você desenvolver um produto com Linux, você precisa liberar
os fontes do kernel sob as mesmas condições, sem restrições.
Embedded
Labworks
LICENÇA (cont.)
✗
✗
Portanto, é ilegal distribuir um binário do kernel com módulos
compilados estaticamente.
Os módulos do kernels são uma área cinza: é um trabalho derivado
do kernel ou não?
✗
✗
✗
A opinião geral da comunidade é de que drivers fechados são ruins.
Sob um ponto de vista legal, cada driver é provavelmente um caso
diferente. Ex: Nvidia.
É realmente útil manter um driver proprietário?
Embedded
Labworks
VANTAGENS DE DRIVERS GPL
✗
✗
✗
✗
Você não precisa escrever um driver do zero, podendo reusar o
código de outros drivers.
Você pode integrar o seu driver na árvore oficial do kernel, e não se
preocupar com qualquer alteração em APIs internas do Linux. Custo
zero de manutenção e melhorias no driver!
Com drivers abertos você tem suporte da comunidade, com mais
pessoas revisando e colaborando com seu código.
Os usuários e a comunidade tem uma visão positiva da empresa.
Embedded
Labworks
LABORATÓRIO
Estudando e navegando nos fontes do Linux
Embedded
Labworks
Linux Device Drivers
Kit de desenvolvimento i.MX53 QSB
Embedded
Labworks
I.MX53 QUICK START BOARD
Embedded
Labworks
CARACTERÍSTICAS
✗
CPU i.MX535 de 1GHz da Freescale (Cortex-A8).
✗
1GB de memória RAM DDR3.
✗
Conector para cartão SD/MMC, microSD e interface SATA.
✗
✗
Saídas de áudio estéreo e vídeo VGA, e entrada para microfone.
Conector de expansão com saídas HDMI, display LCD, câmera e
SDIO.
Interfaces USB host/device, Ethernet, UART, JTAG, botões, leds, etc.
Embedded
Labworks
DIAGRAMA DE BLOCOS
Embedded
Labworks
DOCUMENTAÇÃO
✗
✗
Documentação do hardware
✗
i.MX53 Datasheet: CPU_DS_iMX53.pdf
✗
Board reference: BOARD_DS_IMX53.pdf
✗
User's Guide: BOARD_UG_IMX53.pdf
✗
Linux BSP: BSP_LINUX_mx53.pdf / BSP_LINUX_DOCS.tar.gz
Recursos na internet
http://www.freescale.com/imxquickstart
http://imxcommunity.org/
Embedded
Labworks
CONECTANDO O HARDWARE
Embedded
Labworks
LABORATÓRIO
Conectando e testando o hardware
Embedded
Labworks
Linux Device Drivers
Configurando e compilando o kernel Linux
Embedded
Labworks
CONFIGURANDO O KERNEL
✗
✗
✗
✗
O kernel possui centenas de drivers de dispositivo, diversos
protocolos de rede e muitos outros itens de configuração.
O kernel é bem modular, são muitas as opções disponíveis para
serem habilitadas/desabilitadas.
O processo de configuração serve para você configurar o kernel
para ser compilado para sua CPU/plataforma.
O conjunto de opções que você irá habilitar depende:
✗
Do seu hardware (device drivers, etc).
✗
Das funcionalidades (protocolos de rede, sistemas de arquivo, etc).
Embedded
Labworks
CONFIGURAÇÃO
✗
As configurações são salvas em um arquivo chamado .config no diretório
principal dos fontes do kernel, e possuem o formato key=value. Exemplo:
CONFIG_ARM=y
✗
Dificilmente você vai precisar editar o arquivo .config manualmente. Existem
ferramentas de interface gráfica para configurar o kernel e gerar o arquivo de
configuração automaticamente:
✗
make menuconfig
✗
make gconfig
✗
make xconfig
✗
make nconfig
Embedded
Labworks
$ make xconfig
Embedded
Labworks
$ make gconfig
Embedded
Labworks
$ make nconfig
Embedded
Labworks
$ make menuconfig
Embedded
Labworks
CONFIGURANDO O KERNEL (cont.)
✗
✗
O kernel é um binário único, resultado do processo de linkagem de
todos os arquivos-objeto das funcionalidades habilitadas, incluindo
os device drivers.
O kernel permite que algumas das funcionalidades disponíveis
possam ser habilitadas e compiladas de duas formas:
✗
✗
Estática ou built-in: a funcionalidade selecionada é linkada
estaticamente à imagem final do kernel.
Dinâmica ou módulo: é gerado um módulo daquela funcionalidade
(arquivo com extensão .ko). Este módulo não é incluído na imagem final
do kernel. Ele é incluído no sistema de arquivos e pode ser carregado
dinamicamente (em tempo de execução), conforme a necessidade.
Embedded
Labworks
OPÇÕES DE CONFIGURAÇÃO
✗
Opções booleanas (verdadeiro/falso)
[ ] → Opção desabilitada
[*] → Opção habilitada
✗
Opções de 3 estados:
< > → Opção desabilitada
<*> → Opção habilitada (built­in)
<M> → Opção habilitada (módulo)
✗
Números inteiros. Ex: (17) Kernel log buffer size
✗
Strings. Ex: (iso8859­1) Default iocharset for FAT
Embedded
Labworks
DEPENDÊNCIAS
✗
Na configuração do kernel, podem existir dependências entre
funcionalidades:
✗
✗
Exemplo 1: o driver de um dispositivo I2C depende da habilitação do
barramento I2C para ser habilitado.
Exemplo 2: o driver do barramento USB é habilitado automaticamente
quando o driver de um dispositivo USB é habilitado.
Embedded
Labworks
CONFIGURAÇÃO POR ARQUITETURA
✗
✗
✗
✗
Toda a configuração do kernel é dependente da arquitetura.
Por padrão, o kernel considera um build nativo, então irá usar a arquitetura
da máquina de desenvolvimento (normalmente x86) no comando abaixo:
$ make menuconfig
Portanto, para configurar para ARM por exemplo, você precisa especificar a
arquitetura:
$ make ARCH=arm menuconfig
Ao invés de passar a variável ARCH na chamada do make, você pode
também defini-la como variável de ambiente ou alterar o arquivo Makefile
no diretório principal do kernel.
Embedded
Labworks
CONFIGURAÇÕES PRÉ-DEFINIDAS
✗
✗
Arquivos de configuração pré-definidos para diversas plataformas
estão disponíveis em arch/<arch>/configs/.
O uso de arquivos pré-configurados é a forma padrão de configurar
um kernel para uma plataforma específica. Por exemplo, para
carregar a configuração padrão do kit de desenvolvimento i.MX53
Quick Start Board:
$ make ARCH=arm imx_v6_v7_defconfig
✗
Se você alterou a configuração padrão e deseja salvá-la, pode criar
uma cópia conforme exemplo abaixo:
$ cp .config arch/<arch>/configs/myconfig_defconfig
Embedded
Labworks
COMPILANDO O KERNEL
✗
✗
✗
✗
Depois de configurado, para compilar nativamente basta executar:
$ make
Não precisa de previlégios de root!
Para cross-compilar, você precisa indicar a arquitetura e o prefixo do
cross-compiler. Exemplo:
$ make ARCH=arm CROSS_COMPILE=arm­linux­
O comando acima irá gerar uma imagem genérica para ARM. Se você quiser gerar
uma imagem específica para determinado bootloader, deve adicionar ao fim do
comando o nome da imagem. Exemplo para o U-Boot:
$ make ARCH=arm CROSS_COMPILE=arm­linux­ uImage
Embedded
Labworks
COMPILANDO OS MÓDULOS
✗
Para compilar apenas os módulos, basta executar:
$ make modules
✗
Para cross-compilar os módulos, não esqueça de indicar a
arquitetura e o prefixo do cross-compiler. Exemplo:
$ make ARCH=arm CROSS_COMPILE=arm­linux­ modules
Embedded
Labworks
COMPILANDO O KERNEL (cont.)
✗
Ao fim do processo de compilação, serão geradas as seguintes
imagens:
✗
vmlinux: imagem do kernel no formato ELF, que não é inicializável,
mas pode ser usada para debugging.
✗
*.ko: módulos do kernel, dentro de seus respectivos diretórios.
✗
Em arch/<arch>/boot/:
✗
✗
✗
Image: imagem final do kernel, inicializável e descomprimida.
*Image: imagem inicializável e comprimida do kernel (bzImage
para x86, zImage para ARM, etc).
uImage: imagem do kernel para o U-Boot (opcional).
Embedded
Labworks
INSTALANDO O KERNEL
✗
✗
✗
✗
Para instalar o kernel, basta executar o comando abaixo:
$ make install
Este comando irá instalar os seguintes arquivos no diretório /boot:
✗
vmlinuz­<version> (imagem do kernel comprimida)
✗
System.map­<version> (endereços dos símbolos do kernel)
✗
config­<version> (arquivo de configuração do kernel)
Normalmente não é usado em sistemas embarcados!
Em sistemas embarcados, normalmente gravamos o kernel em um
dispositivo de armazenamento (cartão SD, memória flash, etc).
Embedded
Labworks
INSTALANDO OS MÓDULOS
✗
Para instalar os módulos, basta executar o comando abaixo:
$ make modules_install
✗
✗
No caso de um ambiente de compilação cruzada, os módulos
devem ser instalados no rootfs do target.
Para isso, devemos passar o parâmetro INSTALL_MOD_PATH no
comando de instalação:
$ make ARCH=<arch> INSTALL_MOD_PATH=<dir> modules_install
Embedded
Labworks
FAZENDO A LIMPEZA
✗
✗
✗
Remove todos os arquivos gerados (imagens, arquivos-objeto, etc).
$ make clean
Remove todos os arquivos de gerados e arquivos de configuração
(usado quando pretende-se mudar de plataforma).
$ make mrproper
Além dos arquivos gerados e arquivos de configuração, remove
também arquivos de backup (bom para gerar patches).
$ make distclean
Embedded
Labworks
LINHA DE COMANDOS DO KERNEL
✗
✗
✗
Ao ser carregado, o kernel pode receber um conjunto de
parâmetros. Chamamos esses parâmetros de linha de comandos do
kernel.
Esta linha de comandos pode ser passada ao kernel de duas
formas diferentes:
✗
Pelo bootloader.
✗
Hardcoded
na
configuração
CONFIG_CMDLINE.
do
kernel,
através
da
opção
Esta linha de comandos é uma string com diversas opções no
formato key=value.
Embedded
Labworks
LINHA DE COMANDOS DO KERNEL (cont.)
console=ttySAC0 root=/dev/mtdblock3 rootfstype=jffs2
✗
Exemplo de linha de comandos do kernel, onde:
✗
console = dispositivo que será usado como console
✗
root = dispositivo onde se encontra o sistema de arquivos
✗
rootfstype = tipo do sistema de arquivos (JFFS2)
✗
Existem dezenas de outras opções!
✗
Documentação disponível em:
Documentation/kernel­parameters.txt
Embedded
Labworks
LABORATÓRIO
Compilando o kernel e testando o sistema
Embedded
Labworks
Linux Device Drivers
Módulos do kernel
Embedded
Labworks
MÓDULOS
✗
✗
✗
✗
Internamente, o Linux é bem modular. Cada funcionalidade é
abstraída em um módulo, com uma interface de comunicação bem
definida. Por isso, permite um sistema de configuração onde você
pode adicionar/remover determinada funcionalidade.
E o Linux permite que você adicione dinamicamente “pedaços de
código do kernel” em tempo de execução!
Chamamos esses “pedaços de código” de módulos do kernel.
Um device driver pode ser compilado de forma integrada ao kernel
(built-in) ou como um módulo do kernel.
Embedded
Labworks
VANTAGENS DOS MÓDULOS
✗
✗
✗
✗
Ajuda a manter a imagem do kernel bem pequena.
Módulos tornam fácil o desenvolvimento do kernel, como por
exemplo device drivers, sem precisar reiniciar o equipamento.
O tempo de boot fica menor: menos código para ser executado na
inicialização do kernel.
Cuidado: módulos rodam em kernel space. Uma vez carregados,
eles tem total controle do sistema! Por isso só podem ser
carregados como root.
Embedded
Labworks
DEPENDÊNCIAS DOS MÓDULOS
✗
✗
✗
✗
Alguns módulos dependem de outros módulos, que precisam ser
carregados primeiro.
Exemplo: o módulo usb_storage depende do módulo usbcore.
As dependências entre os módulos estão descritas no arquivo
/lib/modules/<kernel­version>/modules.dep .
Este arquivo é gerado automaticamente quando você instala os
módulos.
Embedded
Labworks
CARREGANDO UM MÓDULO
✗
✗
$ insmod <module_path>.ko
Carrega apenas um módulo. É necessário passar o caminho
completo do módulo.
$ modprobe <module_name>
Carrega um módulo e todas as suas dependências. Deve-se passar
apenas o nome do módulo, sem a extensão .ko e sem seu caminho
completo.
Embedded
Labworks
DESCARREGANDO UM MÓDULO
✗
✗
$ rmmod <module_name>
Descarrega apenas um módulo. Possível apenas se o módulo não
estiver mais em uso. Deve-se passar apenas o nome do módulo,
sem a extensão .ko e sem seu caminho completo.
$ modprobe ­r <module_name>
Descarrega um módulo e todas as suas dependências (que não
estão sendo usadas). Deve-se passar apenas o nome do módulo,
sem a extensão .ko e sem seu caminho completo.
Embedded
Labworks
LISTANDO INFORMAÇÕES DOS MÓDULOS
✗
✗
$ modinfo <module_name>
Lê informações de um módulo, como sua descrição, parâmetros,
licença e dependências. Deve-se passar apenas o nome do módulo,
sem a extensão .ko e sem seu caminho completo.
$ lsmod
Lista todos os módulos carregados.
Embedded
Labworks
PASSANDO PARÂMETROS
✗
✗
✗
✗
Descobrindo os parâmetros disponíveis do módulo:
$ modinfo <module>
Passando um parâmetro via insmod:
$ insmod <module> param=value
Para passar um parâmetro via modprobe, configurar o parâmetro
em /etc/modprobe.conf ou em um arquivo em /etc/modprobe.d/:
options <module> param=value
Para passar um parâmetro via linha de comandos do kernel:
<module>.param=value
Embedded
Labworks
LOGS DO KERNEL
✗
✗
✗
✗
Quando um novo módulo é carregado, informações relevantes são
exibidas no log do kernel.
O kernel mantém um log de mensagens na memória em um buffer
circular.
Este log de mensagens esta disponível através do comando dmesg
(diagnostic message).
Mensagens de log também são exibidas na console. Você pode
filtrar estas mensagens através do parâmetro loglevel ou
desabilitar completamente através do parâmetro do kernel quiet.
Embedded
Labworks
Linux Device Drivers
Desenvolvendo um módulo do kernel
Embedded
Labworks
#include <linux/module.h>
#include <linux/kernel.h>
/* module initialization */
static int __init mymodule_init(void)
{
printk("My module initialized.\n");
return 0;
}
/* module exit */
static void __exit mymodule_exit(void)
{
printk("Exiting my module.\n");
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");
__init é removido
após a inicialização
(built-in ou módulo)
__exit é descartado
caso seja compilado
estaticamente no kernel
(built-in).
Embedded
Labworks
O PRIMEIRO MÓDULO
✗
✗
✗
A macro module_init() declara a função de inicialização que
será chamada ao carregar o módulo para a memória.
A função de inicialização é nomeada de acordo com o padrão
<modulename>_init(), é responsável por inicializar o módulo e
retornar 0 para OK ou um valor negativo em caso de erro. É
removida da memória assim que executada.
A macro module_exit() declara a função de limpeza, que será
chamada assim que o módulo for descarregado da memória. Se o
módulo for compilado estaticamente, esta função não é utilizada, e
por este motivo, nem é carregada para a memória.
Embedded
Labworks
METADADOS DO MÓDULO
✗
Você pode declarar informações (metadados) dos módulos usando
as seguintes macros:
✗
MODULE_LICENSE(): Declara a licença do módulo.
✗
MODULE_DESCRIPTION():
Mensagem
descritiva
do
módulo
(apenas informativa).
✗
MODULE_AUTHOR(): Autor do módulo (também apenas informativa).
✗
MODULE_VERSION(): Versão do módulo.
Embedded
Labworks
EXPORTANDO SÍMBOLOS
✗
✗
De dentro de um módulo, apenas um número limitado de funções e
variáveis globais do kernel podem ser acessadas.
As funções e variáveis precisam ser exportadas explicitamente
para serem usadas externamente, e isso pode ser feito através de
duas macros:
✗
EXPORT_SYMBOL(symbolname), que exporta uma função ou
variável para todos os módulos.
✗
EXPORT_SYMBOL_GPL(symbolname) , que exporta uma função ou
variável apenas para módulos GPL.
Embedded
Labworks
COMPILANDO UM MÓDULO
✗
Existem duas opções para compilar um módulo:
✗
Fora da àrvore do kernel:
✗
✗
✗
Quando o código do módulo esta fora do diretório dos fontes do
kernel.
Fácil de gerenciar, mas não permite compilar um módulo
estaticamente (built-in).
Dentro da árvore do kernel:
✗
✗
Quando o código do módulo esta dentro do diretório dos fontes do
kernel.
Permite compilar um módulo estaticamente ou dinamicamente.
Embedded
Labworks
COMPILANDO FORA DA ÀRVORE DO KERNEL
KDIR := /linux/source/code/directory/
obj­m += mymodule.o
module:
$(MAKE) ­C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) ­C $(KDIR) M=$(PWD) clean
Obs: em caso de compilação cruzada, não esqueça de definir
os parâmetros ARCH e CROSS_COMPILE.
Embedded
Labworks
VERSÃO DO KERNEL
✗
✗
Para ser compilado, um módulo do kernel precisa acessar os
arquivos de cabeçalho (headers) do kernel. Para isso, temos duas
opções:
✗
Usar os fontes completos do kernel.
✗
Usar apenas os arquivos de cabeçalho do kernel.
Um módulo compilado com uma versão X dos headers do kernel
não é carregado em um kernel com versão Y.
✗
As ferramentas insmod ou modprobe irão reportar o erro “Invalid
module format”.
Embedded
Labworks
INTEGRANDO NOS FONTES DO KERNEL
✗
Para integrar um driver nos fontes do kernel, primeiro adicione o
arquivo-fonte em um diretório apropriado. Exemplo:
drivers/serial/8250.c
✗
Descreva a configuração do driver no arquivo Kconfig disponível
no diretório onde o fonte do driver foi adicionado:
config SERIAL_8250
tristate "8250/16550 and compatible serial support"
select SERIAL_CORE
help
This selects whether you want to include the driver for the standard
serial ports. The standard answer is Y. People who might say N
here are those that are setting up dedicated Ethernet WWW/FTP
servers, or users that have one of the various bus mice instead of a
...
Embedded
Labworks
INTEGRANDO NOS FONTES DO KERNEL (cont.)
✗
Adicione uma linha no Makefile baseado na configuração criada no
Kconfig:
obj­$(CONFIG_SERIAL_8250) += 8250.o
✗
Execute make menuconfig para habilitar a nova opção.
✗
Execute make ou make modules para compilar.
✗
Mais informações em Documentation/kbuild/.
Embedded
Labworks
RECEBENDO PARÂMETROS
#include <linux/moduleparam.h>
/* module parameter macro */
module_param(
name, /* name of an already defined variable */
type, /* either byte, short, ushort, int, uint,
long, ulong, charp, or bool.
(checked at compile time!) */
perm /* visibility in sysfs at
/sys/module/<module_name>/parameters/<param>
see linux/stat.h */
);
/* example */
int qtd = 5;
module_param(qtd, int, S_IRUGO);
Embedded
Labworks
LABORATÓRIO
O primeiro módulo
Embedded
Labworks
Linux Device Drivers
Dispositivos de hardware
Embedded
Labworks
DISPOSITIVOS
✗
✗
Um papel importante do kernel é prover um mecanismo de acesso
ao hardware para as bibliotecas e aplicações.
No Linux, o acesso ao hardware é exportado para as aplicações
através de 3 principais classes de dispositivos:
✗
Character device (dispositivo de caractere).
✗
Block Device (dispositivo de bloco).
✗
Network device (dispositivo de rede).
Embedded
Labworks
CLASSES DE DISPOSITIVOS
✗
✗
✗
Char device: pode ser acessado como um stream contínuo de dados
(acesso sequencial), sem começo, meio e fim. É acessado através de
um arquivo em /dev. Ex: porta serial, impressora, placa de som, etc.
Block device: trabalha com blocos de dados, pode ser endereçável,
tem começo, meio e fim. É acessado através de um arquivo em /dev.
Ex: HD, CDROM, DVD, pendrive, etc.
Network device: dispositivo totalmente diferente, que pode ser
representado por uma interface de rede física ou por software
(loopback), responsável por enviar e receber pacotes de dados
através da camada de rede do kernel. Não possui um arquivo em
/dev. A comunicação é feita através de uma API específica.
Embedded
Labworks
ARQUIVOS DE DISPOSITIVO
✗
✗
Os dispositivos de caractere e bloco são representados para
as aplicações através de arquivos chamados arquivos de
dispositivos e armazenados no diretório /dev por convenção.
Cada arquivo de dispositivo possui 3 informações básicas, que
identificam internamente o dispositivo ao qual o arquivo
pertence:
✗
✗
✗
Tipo (caractere ou bloco).
Major number (categoria do dispositivo).
Minor number (identificador do dispositivo).
Embedded
Labworks
ARQUIVOS DE DISPOSITIVO (cont.)
✗
Exemplos de arquivos de dispositivo:
brw­r­­­­­ 1 root root 31, 0 Feb 7 2012 /dev/mtdblock0
brw­r­­­­­ 1 root root 8, 1 Feb 7 2012 /dev/sda1
crw­rw­rw­ 1 root root 4, 64 Feb 7 2012 /dev/ttyS0
crw­rw­rw­ 1 root root 4, 65 Feb 7 2012 /dev/ttyS1
crw­rw­rw­ 1 root root 29, 0 Feb 7 2012 /dev/fb0
crw­rw­rw­ 1 root root 1, 1 Feb 7 2012 /dev/mem
crw­rw­rw­ 1 root root 1, 3 Feb 7 2012 /dev/null
Embedded
Labworks
CONVERSANDO COM O HARDWARE
✗
✗
Como os dispositivos de hardware são exportados através de
arquivos de dispositivo para as aplicações, o acesso ao hardware é
abstraído através de uma API comum de acesso à arquivos (open,
read, write, close).
Exemplo de escrita na porta serial:
int fd;
fd = open("/dev/ttyS0", O_RDWR);
write(fd, "Hello world!", 12);
close(fd);
Embedded
Labworks
ACESSANDO UM ARQUIVO DE DISPOSITIVO
Aplicação
Leitura
Escrita
User space
/dev/ttyS0
major/minor
Kernel space
Trata leitura
Trata escrita
Device driver
Embedded
Labworks
CRIANDO ARQUIVOS DE DISPOSITIVO
✗
✗
✗
Os arquivos de dispositivo podem ser criados manualmente:
$ mknod /dev/<device> [c|b] major minor
Neste caso, é preciso conhecer o major/minor number definido no
driver do dispositivo, além de ter previlégios de root.
Porém, o mais comum é a utilização de mecanismos automáticos de
criação dos arquivos de dispositivo:
✗
devtmpfs: virtual filesystem do Linux, disponível deste a versão
2.6.32.
✗
udev: daemon, solução usada em desktop e servidores.
✗
mdev: programa disponível no Busybox, versão mais leve do udev.
Embedded
Labworks
DISPOSITIVOS DE CARACTERE
✗
✗
Com exceção dos drivers para dispositivos de armazenamento, a
maioria dos drivers são implementados como um driver de
dispositivo de caractere.
Portanto, a maioria dos drivers que você irá desenvolver será do
tipo caractere.
Embedded
Labworks
IMPLEMENTANDO UM CHAR DEVICE
✗
Para implementar um driver de dispositivo do tipo caractere,
existem três passos principais:
1. Reservar o major number e o(s) minor number(s) do seu driver.
2. Implementar as operações que serão disponibilizadas aos usuários
do seu driver (open, read, write, close, etc) e associar estas
operações à uma estrutura do tipo file_operations.
3. Associar o major e o minor number alocados à sua estrutura de
operações de arquivo e registrar o dispositivo de caractere.
Embedded
Labworks
DEVICE NUMBER
✗
✗
✗
O primeiro passo para desenvolver um driver de dispositivo de
caractere ou bloco é registrar um device number para o driver do
dispositivo. O device number é composto por dois números
chamados de major number e minor number.
O kernel armazena as informações de major e minor number no tipo
de dados dev_t.
O tipo de dados dev_t esta definido em <linux/types.h> e
atualmente é representado com 32 bits, onde os 12 bits mais
significativos representam o major e os 20 bits restantes
representam o minor.
Embedded
Labworks
TIPO DE DADOS dev_t
✗
Algumas macros são disponibilizadas para gerenciar variáveis do
tipo dev_t:
/* creating a device number */
dev_t mydev = MKDEV(major, minor);
/* extracting major number */
MAJOR(mydev);
/* extracting minor number */
MINOR(mydev);
Embedded
Labworks
REGISTRANDO ESTATICAMENTE
✗
✗
O major number e o minor number podem ser registrados
estaticamente ou dinamicamente.
Você
pode
registrar
estaticamente
register_chrdev_region().
através
da
função
Embedded
Labworks
FUNÇÃO register_chrdev_region()
#include <linux/fs.h>
/* allocate device number statically */
int register_chrdev_region(dev_t from,
unsigned count,
const char *name);
/* example */
static dev_t mydriver_dev = MKDEV(202, 128);
if (register_chrdev_region(mydriver_dev, 4, “mydriver”)) {
pr_err("Failed to allocate device number\n");
[...]
}
Embedded
Labworks
REGISTRANDO DEVICE NUMBER DINÂMICO
✗
✗
Como você pode não saber quais dispositivos estão presentes no
sistema, pode haver conflito ao tentar alocar estaticamente um
major ou minor number já usado por outro dispositivo.
Portanto, o melhor é alocar dinamicamente com a função
alloc_chrdev_region().
Embedded
Labworks
FUNÇÃO alloc_chrdev_region()
#include <linux/fs.h>
/* allocate device number dynamically */
int alloc_chrdev_region(dev_t *dev,
unsigned baseminor,
unsigned count,
const char *name);
/* example */
static dev_t mydriver_dev;
if (alloc_chrdev_region(&mydriver_dev, 0, 1, "mydriver")) {
pr_err("Failed to allocate device number\n");
[...]
}
Embedded
Labworks
REGISTRANDO MAJOR/MINOR (cont.)
✗
Dispositivos registrados são visíveis em /proc/devices:
# cat /proc/devices
Character devices:
1 mem
5 /dev/tty
13 input
14 sound
[...]
Block devices:
1 ramdisk
7 loop
8 sd
9 md
11 sr
[...]
Embedded
Labworks
IMPLEMENTANDO UM CHAR DEVICE (2)
✗
Para implementar um driver de dispositivo do tipo caractere,
existem três passos principais:
1. Reservar o major number e o(s) minor number(s) do seu driver.
2. Implementar as operações que serão disponibilizadas aos usuários
do seu driver (open, read, write, close, etc) e associar estas
operações à uma estrutura do tipo file_operations.
3. Associar o major e o minor number alocados à sua estrutura de
operações de arquivo e registrar o dispositivo de caractere.
Embedded
Labworks
FILE OPERATIONS
✗
✗
A estrutura file_operations (também chamada de fops)
contém todas as operações implementadas pelo device driver.
Como ela é genérica para todos os arquivos gerenciados pelo
kernel, nem todas as operações definidas nesta estrutura são
necessárias para um driver de dispositivo de caractere.
Embedded
Labworks
ESTRUTURA file_operations
#include <linux/fs.h>
/* most important file operations for a char device */
struct file_operations {
[...]
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
[...]
};
Embedded
Labworks
FUNÇÃO open()
int foo_open(struct inode *i, struct file *f);
✗
✗
✗
Chamada quando o arquivo de dispositivo é aberto.
A estrutura inode é uma representação única do arquivo no sistema (seja ele um
arquivo comum, diretório, link, dispositivo de caractere ou bloco, etc).
Uma estrutura do tipo file é criada toda vez que um arquivo é aberto.
✗
✗
Armazena informações como a posição corrente do arquivo, modo de abertura, etc.
Tem um ponteiro chamado private_data que pode ser usado livremente pelo driver, já
que todas as outras operações recebem este ponteiro.
Embedded
Labworks
FUNÇÃO release()
int foo_release(struct inode *i, struct file *f);
✗
Chamada quando o arquivo de dispositivo é fechado.
✗
As estruturas inode e file são as mesmas da função open().
Embedded
Labworks
FUNÇÃO read()
ssize_t foo_read(struct file *f, __user char *buf,
size_t sz, loff_t *off);
✗
✗
✗
Chamada quando é realizada uma operação de leitura no arquivo de
dispositivo.
O driver deverá:
✗
Ler até sz bytes do dispositivo.
✗
Salvar os dados lidos no buffer buf.
✗
Atualizar a posição atual do arquivo na variável off.
✗
Retornar a quantidade de bytes lidos.
Em sistemas UNIX, a operação de leitura normalmente bloqueia enquanto
não houver bytes suficientes para serem lidos do dispositivo.
Embedded
Labworks
FUNÇÃO write()
ssize_t foo_write(struct file *f, __user const char *buf,
size_t sz, loff_t *off);
✗
✗
Chamada quando é realizada uma operação de escrita no arquivo
de dispositivo.
O driver deverá:
✗
Ler sz bytes do buffer buf.
✗
Escrever os dados lidos no dispositivo.
✗
Atualizar a posição atual do arquivo na variável off.
✗
Retornar a quantidade de bytes escritos.
Embedded
Labworks
TROCANDO DADOS COM USERSPACE
✗
Um código do kernel não pode acessar diretamente uma região de
memória de espaço de usuário, seja desreferenciando um ponteiro
ou usando funções do tipo memcpy().
✗
✗
✗
O endereço de memória virtual pode não estar mapeado.
Se o endereço passado é inválido, acontecerá erro de segmentação
de memória (segfault) e o kernel matará o processo.
Portanto, para manter o código do driver portável e seguro, o driver
precisa usar algumas funções específicas para trocar dados com o
espaço de usuário.
Embedded
Labworks
FUNÇÕES get_user() e put_user()
#include <asm/uaccess.h>
/* Read a single userspace variable pointed by p
and save to kernel variable v. Return 0 on
success or a negative number on error.
*/
if (get_user(v, p)) {
pr_err("Error getting variable...\n");
[...]
}
/* Save a single kernel variable v to userspace
variable pointed by p. Return 0 on success
or a negative number on error.
*/
if (put_user(v, p)) {
pr_err("Error putting variable...\n");
[...]
}
Embedded
Labworks
FUNÇÃO copy_to_user()
#include <asm/uaccess.h>
/* Copy n bytes from kernel buffer to user buffer.
Return 0 on success or a negative number on error.
*/
unsigned long copy_to_user(void __user *to,
const void *from,
unsigned long n);
/* example */
if (copy_to_user(user_buf, kernel_buf, qtd)) {
pr_err("Error copying to user buffer...\n");
[...]
}
Embedded
Labworks
FUNÇÃO copy_from_user()
#include <asm/uaccess.h>
/* Copy n bytes from user buffer to kernel buffer. Return 0 on success or a negative number on error.
*/
unsigned long copy_from_user(void *to, const void __user *from,
unsigned long n);
/* example */
if (copy_from_user(kernel_buf, user_buf, qtd)) {
pr_err("Error copying from user buffer...\n");
[...]
}
Embedded
Labworks
OUTROS MÉTODOS
✗
✗
Dependendo da quantidade de dados e do fluxo de comunicação, o
uso das funções de troca de dados entre kernel e espaço de usuário
pode impactar a performance do sistema.
Para estes casos, existem soluções alternativas onde não é
necessário realizar a cópia dos buffers (zero copy):
✗
✗
Implementar a operação mmap() para permitir que um código
rodando em espaço de usuário tenha acesso direto à memória do
dispositivo.
Usar a função get_user_pages() para mapear direto uma página
de memória do usuário ao invés de copiá-la.
Embedded
Labworks
EXEMPLO read()
static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int remaining_size, transfer_size;
remaining_size = mydriver_bufsize ­ (int)(*ppos);
/* check if EOF */
if (remaining_size == 0) {
return 0;
}
/* Size of this transfer */
transfer_size = min_t(int, remaining_size, count);
if (copy_to_user(buf, mydriver_buf + *ppos, transfer_size)) {
return ­EFAULT;
} else {
*ppos += transfer_size;
return transfer_size;
}
}
Embedded
Labworks
EXEMPLO write()
static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int remaining_bytes;
remaining_bytes = mydriver_bufsize ­ (*ppos);
if (count > remaining_bytes) {
return ­EIO;
}
if (copy_from_user(mydriver_buf + *ppos, buf, count)) {
return ­EFAULT;
} else {
*ppos += count;
return count;
}
}
Embedded
Labworks
FUNÇÃO unlocked_ioctl()
long unlocked_ioctl(struct file *f, unsigned int cmd, unsigned long arg);
✗
✗
✗
✗
✗
Esta operação esta associada à chamada de sistema ioctl().
Permite estender as capacidades do driver além da API de leitura e escrita
em arquivos.
Exemplos de utilização em drivers: configurar o baud rate de uma porta
serial, configurar a resolução de uma placa de vídeo, etc.
O comando a ser executado é passado no parâmetro cmd, e pode-se usar o
parâmetro arg para passar alguma informação adicional.
A semântica dos argumentos cmd e arg é específica de cada driver.
Embedded
Labworks
EXEMPLO unlocked_ioctl()
static long phantom_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct phm_reg r;
void __user *argp = (void __user *)arg;
switch (cmd) {
case PHN_SET_REG:
if (copy_from_user(&r, argp, sizeof(r)))
return ­EFAULT;
/* do something */
break;
case PHN_GET_REG:
if (copy_to_user(argp, &r, sizeof(r)))
return ­EFAULT;
/* do something */
break;
default:
return ­ENOTTY;
}
return 0;
}
Embedded
Labworks
EXEMPLO APLICAÇÃO COM ioctl()
int main(void)
{
int fd, ret;
struct phm_reg reg;
fd = open("/dev/phantom");
assert(fd > 0);
reg.field1 = 42;
reg.field2 = 67;
ret = ioctl(fd, PHN_SET_REG, &reg);
assert(ret == 0);
return 0;
}
Embedded
Labworks
DEFININDO FILE OPERATIONS
✗
Para definir a estrutura de operações em arquivo, basta declará-la
e inicializá-la com os ponteiros para as operações que você
implementou. Exemplo:
#include <linux/fs.h>
static struct file_operations acme_fops =
{
.owner = THIS_MODULE,
.read = acme_read,
.write = acme_write,
};
Embedded
Labworks
IMPLEMENTANDO UM CHAR DEVICE (3)
✗
Para implementar um driver de dispositivo do tipo caractere,
existem três passos principais:
1. Reservar o major number e o(s) minor number(s) do seu driver.
2. Implementar as operações que serão disponibilizadas aos usuários
do seu driver (open, read, write, close, etc) e associar estas
operações à uma estrutura do tipo file_operations.
3. Associar o major e o minor number alocados à sua estrutura de
operações de arquivo e registrar o dispositivo de caractere.
Embedded
Labworks
REGISTRANDO CHAR DEVICE
✗
✗
✗
✗
O kernel representa um dispositivo de caractere com a estrutura
cdev.
Para registrar um dispositivo de caractere, primeiro declare
globalmente uma estrutura do tipo cdev e inicialize-a chamando a
função cdev_init().
Depois é só adicionar o dispositivo de caractere ao sistema com a
função cdev_add().
Depois desta chamada, o kernel associará o major/minor number
registrado com as operações em arquivo definidas. Seu dispositivo
esta pronto para ser usado!
Embedded
Labworks
EXEMPLO CRIANDO CHAR DEVICE
#include <linux/cdev.h>
static struct cdev mydriver_cdev;
static int __init mydriver_init(void)
{
[...]
cdev_init(&mydriver_cdev, &mydriver_fops);
if (cdev_add(&mydriver_cdev, mydriver_dev, 1)) {
pr_err("Char driver registration failed\n");
[...]
}
[...]
}
Embedded
Labworks
DESREGISTRANDO
✗
✗
✗
Na função de limpeza do driver é necessário desregistrar o
dispositivo de caractere.
Para isso, primeiro remova o dispositivo de caractere com a função
cdev_del().
E depois libere o major/minor number alocado com a função
unregister_chrdev_region().
Embedded
Labworks
EXEMPLO DESREGISTRANDO CHAR DEVICE
#include <linux/cdev.h>
static void __exit mydriver_exit(void)
{
[...]
cdev_del(&mydriver_cdev);
unregister_chrdev_region(mydriver_dev, 1);
[...]
}
Embedded
Labworks
CÓDIGOS DE ERRO DO LINUX
✗
✗
✗
A convenção do Linux para tratamento de erros é a seguinte:
✗
Retornar 0 no caso de sucesso.
✗
Retornar um número negativo no caso de erro.
Regra geral: sempre trate o retorno das funções e retorne um
código de erro compatível com o problema apresentado.
Os códigos de erro padrão estão disponíveis em:
asm­generic/errno­base.h
asm­generic/errno.h
Embedded
Labworks
SUMÁRIO
✗
✗
✗
✗
Defina e implemente as operações de arquivo do driver (open, read, write,
close, ioctl, etc).
Declare a estrutura file_operations e inicialize-a com os ponteiros das
operações de arquivo implementadas.
Na função de inicialização do módulo, reserve o major/minor number com a
função register_chrdev_region(), inicialize a estrutura cdev com a
função cdev_init() e adicione no sistema com cdev_add().
Na função de limpeza do módulo, desregistre o dispositivo de caractere
com dev_del() e depois desregistre o major/minor number com a função
unregister_chrdev_region().
Embedded
Labworks
LABORATÓRIO
O primeiro driver
Embedded
Labworks
DISPOSITIVOS NO KERNEL
✗
✗
Durante os testes do driver desenvolvido, foi necessário criar o
arquivo de dispositivo manualmente. Como tornar a criação de um
arquivo de dispositivo automática?
Temos duas opções:
✗
✗
Delegar para o kernel a criação dos arquivos de dispositivo. Isso é
possível através do devtmpfs.
Fazer com que o kernel gere um evento toda vez que um dispositivo
de hardware for registrado no sistema. Uma aplicação como o udev
ou o mdev pode capturar estes eventos e cuidar da criação dos
arquivos de dispositivo.
Embedded
Labworks
CRIANDO ARQUIVOS DE DISPOSITIVO
Kernel space
devtmpfs
1
Dispositivo
identificado
2
User space
/dev
User space
daemon
Embedded
Labworks
DEVICE MODEL
✗
✗
✗
✗
Para ambos os casos funcionarem, o dispositivo precisa fazer parte
do modelo de dispositivos (device model) do kernel.
O device model provê um mecanismo único para representar os
dispositivos de hardware e sua topologia no sistema.
O device model nada mais é do que um conjunto de objetos do
kernel, chamados internamente de kobjects.
Estes objetos são conectados entre si, e formam uma hierarquia de
dispositivos e barramentos conectados ao sistema.
Embedded
Labworks
SYSFS
✗
✗
✗
O sysfs é basicamente um sistema de arquivo virtual que exporta
esta hierarquia de objetos para user space.
Por este motivo, no sysfs podemos colher informações sobre os
dispositivos de hardware conectados ao sistema.
É montado normalmente normalmente no diretório /sys:
$ mount ­t sysfs none /sys
Embedded
Labworks
SYSFS (cont.)
✗
Estes são alguns de seus principais diretórios:
✗
devices: Hierarquia completa dos dispositivos conectados ao
sistema.
✗
✗
bus: Dispositivos classificados por barramento (spi, pci, usb, etc).
✗
class: Dispositivos classificados por tipo (net, input, block, etc).
✗
dev: Dispositivos classificados por major e minor number.
Mais detalhes em Documentation/filesystems/sysfs.txt .
Embedded
Labworks
REGISTRANDO O DISPOSITIVO
✗
✗
Para registrar um dispositivo no device model do kernel,
precisamos primeiro criar uma classe com a função
class_create() e depois registrar o dispositivo com a função
device_create().
Da mesma forma, para desregistrar o dispositivo precisamos
removê-lo com a função device_destroy() e depois destruir a
classe com a função class_destroy().
Embedded
Labworks
DEVICE API
#include <linux/device.h>
/**
* class_create ­ create a struct class structure
*/
struct class *class_create(struct module *owner, const char *name);
/**
* device_create ­ creates a device and registers it * with sysfs
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...);
Embedded
Labworks
DEVICE API (cont.)
#include <linux/device.h>
/**
* class_destroy ­ destroys a struct class structure
*/
void class_destroy(struct class *cls)
/**
* device_destroy ­ removes a device that was created with device_create()
*/
void device_destroy(struct class *class, dev_t devt);
Embedded
Labworks
DEVICE API (EXEMPLO)
#include <linux/device.h>
static struct class *cosa_class;
static int __init cosa_init(void)
{
struct device *dev;
cosa_class = class_create(THIS_MODULE, "cosa");
dev = device_create(cosa_class, NULL, MKDEV(cosa_major, 0), NULL, "cosa%d", 0);
if (IS_ERR(dev)) {
// handle error
}
[...]
}
Embedded
Labworks
DEVICE API
#include <linux/device.h>
static struct class *cosa_class;
static void __exit cosa_exit(void)
{
[...]
device_destroy(cosa_class, MKDEV(cosa_major, 0));
class_destroy(cosa_class);
[...]
}
Embedded
Labworks
UEVENT
✗
✗
✗
✗
Na chamada à device_create() ou à qualquer outra função que
altere a hierarquia de dispositivos conectados ao sistema, o kernel
irá gerar um evento, chamado de uevent.
Este evento é comunicado para user space através de um netlink
socket (socket multicast específico para comunicação entre kernel
e user space).
Um daemon rodando em user space pode escutar estes eventos e
tratá-los.
É isso que faz o udevd (daemon do udev).
Embedded
Labworks
UDEV
✗
✗
✗
O udev é o gerenciador padrão de dispositivos do Linux, presente
na maioria das distribuições.
É composto por um conjunto de ferramentas e daemons como o
udevd e o udevinfo.
Seu comportamento pode ser configurável através de um conjunto
de regras armazenadas em /etc/udev/rules.d/.
Embedded
Labworks
HOTPLUG
Kernel space
device_create()
Device Driver
Registra
dispositivo
uevent
User space
/dev
udevd
Conexão via
netlink socket
Consulta
regras
Embedded
Labworks
COLDPLUG
✗
✗
✗
✗
Mas o que acontece com os dispositivos que foram identificados e
registrados no boot do sistema, quando o daemon do udev ainda
não estava rodando?
É aí que o sysfs tem um papel importante na solução.
Durante o boot, o kernel cria um arquivo chamado uevent para
cada dispositivo criado no sistema.
O udev varre o sysfs procurando por estes arquivos e gera os
eventos correspondentes, como se eles estivessem acontecendo
naquele momento.
Embedded
Labworks
OUTRAS FUNCIONALIDADES DO UDEV
✗
Além da capacidade de criar os arquivos de dispositivo no /dev, o
udev tem ainda outras funcionalidades:
✗
✗
✗
Diferenciar dispositivos e executar qualquer comando, script ou
aplicação para determinado dispositivo.
Carregar firmware.
Carregar módulos do kernel (para dispositivos conectados em
barramentos que suportam hotplug).
Embedded
Labworks
MDEV
✗
✗
✗
O mdev é uma implementação de gerenciador de dispositivos mais
leve presente no Busybox.
Tem os mesmos objetivos do udev, porém menos flexível. Ele é
capaz de:
✗
Criar dinamicamente arquivos de dispositivo.
✗
Executar comandos específicos para cada dispositivo conectado.
✗
Realizar a carga de firmware.
Seu arquivo de configuração fica em /etc/mdev.conf.
Embedded
Labworks
DEVTMPFS
✗
✗
✗
O devtmpfs é um sistema de arquivo virtual que permite delegar ao
kernel o gerenciamento dos arquivos de dispositivo.
Esta funcionalidade é habilitada na opção CONFIG_DEVTMPFS.
Com esta opção habilitada, é só montar no /dev o sistema de
arquivo devtmpfs:
$ mount ­t devtmpfs none /dev
✗
Para o kernel montar este sistema de arquivo automaticamente no
boot, habilite a opção CONFIG_DEVTMPFS_MOUNT.
Embedded
Labworks
MISC DRIVERS
✗
✗
✗
✗
Misc (miscellaneous) drivers são drivers de dispositivo de caractere
que compartilham algumas características em comum.
O kernel abstrai estas características comuns em uma API
(implementação em drivers/char/misc.c).
Todos os misc drivers possuem o major number 10, e cada um tem
seu minor number.
Quando usar? Drivers de dispositivo de caractere bem simples, que
precisam de apenas um minor number. Por exemplo, muitos drivers de
watchdog (em drivers/watchdog) são implementados através de
misc drivers.
Embedded
Labworks
MISC DRIVER (cont.)
✗
Vimos que, em um dispositivo de caractere, é comum realizarmos
as seguintes etapas:
✗
✗
✗
✗
Alocar o major/minor number, usando por exemplo a função
alloc_chrdev_region().
Criar o dispositivo de caractere com as funções cdev_init() e
cdev_add().
Registrar o dispositivo de caractere no device model com as funções
class_create() e device_create().
Um misc driver substitui todo este processo apenas pela chamada
à função misc_register().
Embedded
Labworks
MISC DRIVER API
#include <linux/miscdevice.h>
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
[...]
};
/**
* misc_register ­ register a miscellaneous device
*/
int misc_register(struct miscdevice * misc);
Embedded
Labworks
CHAR DRIVER
static int __init btn_init(void)
{
[...]
result = alloc_chrdev_region(&btn_dev, 0, 1, DEVICE_NAME);
[...]
cdev_init(&btn_cdev, &btn_fops);
result = cdev_add(&btn_cdev, btn_dev, 1);
[...]
btn_class = class_create(THIS_MODULE, DEVICE_NAME);
device_create(btn_class, NULL, MKDEV(MAJOR(btn_dev),0),
"btn");
[...]
}
Embedded
Labworks
MISC DRIVER
#include <linux/miscdevice.h>
static struct miscdevice btn_dev = {
MISC_DYNAMIC_MINOR,
"btn",
&btn_fops
};
static int __init btn_init(void)
{
[...]
result = misc_register(&btn_dev);
[...]
}
Embedded
Labworks
LABORATÓRIO
Registrando o dispositivo
Embedded
Labworks
Linux Device Drivers
Gerenciamento de memória
Embedded
Labworks
MEMÓRIA
✗
✗
✗
O desenvolvimento de alguns tipos de drivers, principalmente
aqueles que precisam de performance, requerem um conhecimento
maior de como o sub-sistema de memória virtual do Linux
funciona.
O sub-sistema de memória virtual no Linux é fortemente apoiado
em componente de hardware chamado MMU.
A MMU (Memory Management Unit) é o hardware que implementa o
mecanismo de memória virtual, gerenciando a memória do sistema
e fazendo a conversão entre endereços de memória físicos e
virtuais.
Embedded
Labworks
MEMORY MANAGEMENT UNIT
CPU
Endereço
virtual
MMU
Endereço
físico
Memória
Embedded
Labworks
VANTAGENS DA MMU
✗
Um sistema com MMU é capaz de prover:
✗
✗
✗
✗
✗
Maior endereçamento de memória para os processos: em uma
arquitetura de 32 bits, os processos podem ter acesso à um
endereçamento linear de até 3G de memória virtual.
Proteção: cada processo só enxerga seu espaço de endereçamento,
onde um acesso inválido gera uma exceção.
Memory mapping: possibilidade de mapear um arquivo físico em
memória.
Compartilhamento: os processos podem compartilhar memória (código,
dados, etc), usado por exemplo em mecanismos de IPC.
SWAP: se faltar memória física, possibilita salvar e recuperar páginas
de memória do disco.
Embedded
Labworks
COMO FUNCIONA NO LINUX?
✗
✗
✗
O kernel divide o espaço de endereçamento de memória virtual em
duas partes:
✗
Endereçamento de memória virtual do kernel.
✗
Endereçamento de memória virtual dos processos.
Esta divisão é configurada em tempo de compilação no kernel em
Memory Split, com três opções 1G/3G, 2G/2G e 3G/1G.
Por exemplo, um sistema de 32 bits consegue endereçar até 4G de
memória virtual. Se configurado com 3G/1G, temos 1G de memória
endereçável pelo kernel e 3G de memória endereçável pelos
processos.
Embedded
Labworks
ORGANIZAÇÃO DA MEMÓRIA 3G/1G (x86)
0xFFFFFFFF
✗
Kernel
1GB do endereçamento de memória virtual
é reservado para o kernel, contendo o
código do kernel e suas estruturas de
dados principais.
0xC0000000
✗
Processo 1
✗
0x00000000
3GB do endereçamento de memória virtual
é reservado para cada processo, para
armazenar código, dados (stack, heap, etc),
arquivos mapeados em memória, etc.
Não necessariamente um endereço virtual
reservado para um processo pode estar
mapeado para um endereço físico!
Embedded
Labworks
ORGANIZAÇÃO DA MEMÓRIA 2G/2G (i.MX53)
0xFFFFFFFF
✗
Kernel
✗
0x80000000
2GB do endereçamento de memória virtual
é reservado para o kernel, contendo o
código do kernel e suas estruturas de
dados principais.
2GB do endereçamento de memória virtual
é reservado para cada processo, para
armazenar código, dados (stack, heap, etc),
arquivos mapeados em memória, etc.
Processo 1
✗
0x00000000
Não necessariamente um endereço virtual
reservado para um processo pode estar
mapeado para um endereço físico!
Embedded
Labworks
Endereçamento Virtual de Memória
Endereçamento Físico de Memória
0xFFFFFFFF
Kernel
0xC0000000
RAM
Processo 1
0x00000000
CPU
MMU
I/O
0xFFFFFFFF
Kernel
0xC0000000
Processo 2
0x00000000
Flash
Embedded
Labworks
MEMÓRIA FÍSICA
✗
✗
✗
✗
Apesar de a menor unidade de memória endereçável pela CPU seja um byte ou
uma palavra (word), o kernel divide a memória física em páginas de memória.
Isso porque, para gerenciar a memória e realizar a conversão de endereços
virtuais para endereços físicos, a MMU divide a memória em páginas e mantém
uma tabela de páginas de memória do sistema.
O tamanho de uma página de memória pode variar dependendo da arquitetura
(tipicamente 4K em arquiteturas de 32 bits e 8K em arquiteturas de 64 bits).
Isso significa que, em um sistema de 32 bits, com páginas de 4K e 1G de
memória física, teremos 262.144 páginas físicas de memória.
Embedded
Labworks
ZONAS DE MEMÓRIA
✗
O kernel divide as páginas de memória física em diferentes zonas:
✗
ZONE_DMA e ZONE_DMA32: páginas de memória para operações de DMA.
✗
ZONE_NORMAL: páginas de memória mapeadas normalmente no espaço do kernel.
✗
✗
✗
ZONE_HIGHMEM: páginas de memória que não podem ser mapeadas no espaço de
endereçamento do kernel. São alocadas dinamicamente, conforme a necessidade.
Desta forma, quando por exemplo um driver precisa alocar memória para realizar
uma operação de DMA, ele irá alocar de ZONE_DMA.
O uso e o layout das zonas de memória são totalmente dependentes de arquitetura.
Embedded
Labworks
EXEMPLO DE MAPEAMENTO EM 32 BITS
Espaço de endereçamento virtual (4G)
Espaço de endereçamento físico (2G)
0xFFFFFFFF
Kernel
0xC0000000
0x80000000
ZONE_HIGHMEM
Processo 1
ZONE_NORMAL
ZONE_DMA
0x00000000
0x00000000
Embedded
Labworks
MEMÓRIA PARA OS PROCESSOS
✗
✗
✗
A memória para os processos é alocada das zonas ZONE_HIGHMEM
(se existente) ou ZONE_NORMAL.
Durante uma alocação de memória para um processo, o kernel não
necessariamente aloca fisicamente esta página para o processo.
Neste caso, o kernel pode usar uma funcionalidade chamada
demand fault paging para alocar dinamicamente uma página de
memória, através da exceção page fault gerada pela MMU quando
o processo tentar acessar esta região de memória.
Embedded
Labworks
MEMÓRIA PARA OS PROCESSOS (cont.)
✗
✗
É permitido à uma aplicação alocar mais memória do que a
disponível fisicamente no sistema (mas pode levar à falta de
memória).
Neste caso, o OOM Killer (Out of Memory Killer) entra em ação e
seleciona um processo para matar e ganhar um pouco mais de
memória (melhor do que travar!)
Embedded
Labworks
Linux Device Drivers
Alocação de memória
Embedded
Labworks
ALOCANDO MEMÓRIA
✗
Existem basicamente quatro mecanismos de alocação de memória
no kernel:
✗
Page allocator: funções de baixo nível que permitem alocações na
granularidade de páginas de memória (normalmente 4KB em 32 bits).
✗
SLAB Allocator: usa as funções do page alocator para criar caches
de alocação de memória, permitindo alocar objetos menores que uma
página de memória.
✗
vmalloc(): também usa as funções do page allocator, mas permite
alocar regiões não-contínuas de memória física.
✗
kmallok(): mecanismo mais comum que usa as funções do SLAB
allocator para alocar memória.
Embedded
Labworks
Código do kernel
kmalloc()
vmalloc()
SLAB Allocator
Page allocator
Embedded
Labworks
PAGE ALLOCATOR
✗
✗
✗
✗
Apropriado para alocações de grandes regiões de memória (maiores
que 128K).
Trabalha com alocação de páginas de memória (normalmente 4K em
32 bits) e trabalha apenas em potência de 2 (1 página, 2 páginas, 4
páginas, 8 páginas, etc).
Aloca no máximo 8MB (é possível mudar este valor na configuração
do kernel).
Aloca uma região de memória virtual contínua, e também fisicamente
contínua (o que pode ser um problema se a memória estiver muito
fragmentada).
Embedded
Labworks
PAGE ALLOCATOR API
#include <linux/gfp.h>
/* allocate physical pages */
unsigned long __get_free_page(int flags);
unsigned long get_zeroed_page(int flags);
unsigned long __get_free_pages(int flags, unsigned int order);
/* free allocated pages */
void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned int order);
Embedded
Labworks
FLAGS
✗
Estas são as mais comuns flags em alocação de memória:
✗
GFP_KERNEL: Flag padrão para alocação de memória, usado na
maioria das situações.
✗
GFP_ATOMIC: Usada em situações onde não se pode dormir (seções
críticas ou rotinas de tratamento de interrupção).
✗
GFP_DMA: Usada quando é necessário alocar memória da zona de
DMA.
✗
As outras flags estão definidas em <linux/gfp.h>.
Embedded
Labworks
SLAB ALLOCATOR
✗
✗
Camada que usa a API da page allocator para criar um cache de alocação
de memória, permitindo alocar objetos menores que o tamanho de uma
página de memória.
Existem três implementações da camada SLAB (todas implementam a
mesma API):
✗
SLAB: versão original.
✗
SLOB: mais simples, economiza espaço, mas não escala bem.
✗
✗
SLUB: mecanismo padrão a partir da versão 2.6.23. Simples e escala melhor
que o SLAB, gerando menos fragmentação de memória.
Sua API é usada internamente no kernel (vide <linux/slab.h>), mas
dificilmente será usada em um driver comum.
Embedded
Labworks
KMALLOC
✗
✗
✗
✗
Em um driver comum, o mecanismo padrão de alocação é através
da função kmalloc().
Permite
alocar
objetos de 8 bytes a 128KB
/proc/slabinfo, necessário habilitar SLUB_DEBUG).
(vide
A área alocada será fisicamente contínua e arredondada à potência
de 2.
Usa os mesmos flags do page allocator (GFP_KERNEL,
GFP_ATOMIC, GFP_DMA, etc), e com a mesma semântica.
Embedded
Labworks
KMALLOC API
#include <linux/slab.h>
/* allocate size bytes and return a pointer to the area (virtual address) */
void *kmalloc(size_t size, int flags);
/* free an allocated area */
void kfree(const void *objp);
/* example */
struct ib_update_work *work;
work = kmalloc(sizeof *work, GFP_ATOMIC);
[...]
kfree(work);
Embedded
Labworks
KMALLOC API (cont.)
#include <linux/slab.h>
/* allocates a zero­initialized buffer */
void *kzalloc(size_t size, gfp_t flags);
/* allocates memory for an array of n elements of size
size, and zeroes its contents */
void *kcalloc(size_t n, size_t size, gfp_t flags);
/* changes the size of the buffer pointed by p to new_size,
by reallocating a new buffer and copying the data,
unless the new_size fits within the alignment of the
existing buffer. */
void *krealloc(const void *p, size_t new_size, gfp_t flags);
Embedded
Labworks
VMALLOC ALLOCATOR
✗
✗
Permite alocar um endereço de memória virtual contínuo, mas não
fisicamente contínuo.
Permite alocações de grandes áreas de memória já que não sofre
de problemas de fragmentação, mas não pode ser usado para
regiões de memória de DMA, que requer uma região de memória
fisicamente contínua.
Embedded
Labworks
VMALLOC ALLOCATOR API
#include <linux/vmalloc.h>
/* allocate size bytes and returns a virtual address */
void *vmalloc(unsigned long size);
/* free allocated memory */
void vfree(void *addr);
Embedded
Labworks
KERNEL MEMORY DEBUGGING
✗
KMEMCHECK:
verifica dinamicamente por memória não inicializada,
apenas disponível no x86 (32/64 bits). Documentação em:
Documentation/kmemcheck.txt
✗
DEBUG_KMEMLEAK:
verifica dinamicamente por memory leaks,
disponível em todas as arquiteturas. Documentação em:
Documentation/kmemleak.txt
✗
Estas funcionalidades adicionam um overhead na execução do
kernel. Use apenas em desenvolvimento!
Embedded
Labworks
Linux Device Drivers
Hardware I/O
Embedded
Labworks
ACESSANDO I/O
✗
Existem basicamente dois mecanismos de acesso à I/O, de acordo
com a arquitetura da CPU:
✗
✗
Memory-mapped I/O: quando a CPU utiliza o mesmo barramento de
endereços para endereçar memória e I/O. Neste caso, o acesso é
realizado normalmente através de instruções de acesso à memória. É
o mecanismo mais usado por diferentes arquiteturas suportadas pelo
Linux.
Port I/O: quando a CPU utiliza barramentos de endereços diferentes
para acessar memória e I/O. Neste caso, o acesso à I/O é realizado
através do uso de instruções especiais da CPU (ex: instruções IN e
OUT na arquitetura x86).
Embedded
Labworks
PORT I/O
✗
✗
✗
✗
Antes de usar um port I/O, o driver deve requisitar ao kernel a
região de I/O que deseja usar com a função request_region().
Você pode listar todos os I/Os registrados pelos drivers no
arquivo /proc/ioports.
Isso evita que outros drivers usem a mesma região de I/O (mas é
um procedimento puramente voluntário).
Depois de usada, a região de I/O deve ser liberada com a função
release_region().
Embedded
Labworks
PORT I/O API
#include <linux/ioport.h>
/* request I/O region */
struct resource *request_region(unsigned long start,
unsigned long len, char *name);
/* release I/O region */
void release_region(unsigned long start, unsigned long len);
Embedded
Labworks
PORT I/O API
#include <linux/ioport.h>
/* example: requesting I/O port */
if (request_region(0x03f8, 8, "serial") == NULL) {
pr_err("Failed requesting I/O region!\n");
[...]
}
/* example: releasing I/O port */
release_region(0x0170, 8);
Embedded
Labworks
PORT I/O API (cont.)
#include <asm/io.h>
/* read/write bytes (8 bits) */
unsigned inb(unsigned long *addr);
void outb(unsigned port, unsigned long *addr);
/* read/write words (16 bits) */
unsigned inw(unsigned long *addr);
void outw(unsigned port, unsigned long *addr);
/* read/write longs (32 bits) */
unsigned inl(unsigned long *addr);
void outl(unsigned port, unsigned long *addr);
Embedded
Labworks
PORT I/O API (cont.)
#include <asm/io.h>
/* read/write multiple bytes (8 bits) */
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
/* read/write multiple words (16 bits) */
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
/* read/write multiple longs (32 bits) */
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
Embedded
Labworks
PORT I/O API (cont.)
#include <asm/io.h>
/* example: read 8 bits */
oldlcr = inb(baseio + UART_LCR);
/* example: write 8 bits */
outb(MOXA_MUST_ENTER_ENCHANCE, baseio + UART_LCR);
Embedded
Labworks
MEMORY-MAPPED I/O
✗
✗
✗
✗
O primeiro passo para o uso de memory-mapped I/O é requisitar a região
de I/O para o kernel através da função request_mem_region(),
normalmente chamada na inicialização do device driver.
Esta função irá registrar o uso de determinada região de I/O mapeada em
memória.
Em /proc/iomem temos todas as regiões de I/O mapeadas em memória já
reservadas no kernel.
Ao
descarregar
o
driver,
devemos
chamar
a
função
release_mem_region() para notificar o kernel de que esta região de I/O
mapeado em memória esta livre para uso.
Embedded
Labworks
MEMORY-MAPPED I/O API
#include <linux/ioport.h>
/* request memory­mapped I/O */
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
/* release memory­mapped I/O */
void release_mem_region(unsigned long start, unsigned long len);
Embedded
Labworks
MEMORY-MAPPED I/O API (cont.)
#include <linux/ioport.h>
/* example: requesting memory­mapped I/O */
if (request_mem_region(TB0219_START, TB0219_SIZE,
"TB0219") == NULL) {
pr_err("Failed requesting memory­mapped I/O region!\n");
[...]
}
/* example: releasing memory­mapped I/O */
release_mem_region(TB0219_START, TB0219_SIZE);
Embedded
Labworks
MEMORY-MAPPED I/O NA MEMÓRIA VIRTUAL
✗
✗
✗
O kernel só trabalha com memória virtual!
Portanto, para acessar um endereço de memória correspondente à
determinado I/O, precisamos converter o endereço físico desta
porta de I/O em um endereço virtual antes de usá-lo.
Isso pode ser feito com a função ioremap().
Embedded
Labworks
MEMORY-MAPPED I/O API
#include <asm/io.h>
/* get memory­mapped I/O virtual address */
void *ioremap(unsigned long phys_addr, unsigned long size);
/* unmap memory­mapped I/O virtual address */
void iounmap(void *address);
Embedded
Labworks
MEMORY-MAPPED I/O API (cont.)
#include <asm/io.h>
/* example: mapping memory­mapped I/O address */
static void __iomem *tb0219_base;
if ((tb0219_base = ioremap(TB0219_START, TB0219_SIZE)) == NULL) {
pr_err("Failed remapping memory­mapped I/O!\n");
[...]
}
/* example: unmapping memory­mapped I/O address */
iounmap(tb0219_base);
Embedded
Labworks
MEMORY-MAPPED I/O API (cont.)
#include <asm/io.h>
/* read/write bytes (little­endian access) */
unsigned readb(void *addr);
void writeb(unsigned val, void *addr);
/* read/write words (little­endian access) */
unsigned readw(void *addr);
void writew(unsigned val, void *addr);
/* read/write longs (little­endian access) */
unsigned readl(void *addr);
void writel(unsigned val, void *addr);
Embedded
Labworks
MEMORY-MAPPED I/O API (cont.)
#include <asm/io.h>
/* read/write bytes (raw access) */
unsigned __raw_readb(void *addr);
void __raw_writeb(unsigned val, void *addr);
/* read/write words (raw access) */
unsigned __raw_readw(void *addr);
void __raw_writew(unsigned val, void *addr);
/* read/write longs (raw access) */
unsigned __raw_readl(void *addr);
void __raw_writel(unsigned val, void *addr);
Embedded
Labworks
MEMORY-MAPPED I/O API (cont.)
#include <asm/io.h>
/* example: little­endian byte read */
tmp8 = readb(mmio + CARM_INITC);
/* example: little­endian long write */
writel(data, lynx­>registers + offset);
/* example: raw byte read */
char sense = __raw_readb(GDROM_ERROR_REG);
/* example: raw long write */
__raw_writel(1 << KS8695_IRQ_UART_TX, membase + KS8695_INTST);
Embedded
Labworks
NOVA API DE ACESSO À I/O
✗
✗
Existe uma nova API para acessar dispositivos de I/O de forma
transparente, seja port I/O ou memory-mapped I/O.
Alguns drivers usam esta funcionalidade, mas parece não existir
ainda um consenso se todos os drivers devem ser
desenvolvidos/migrados para este novo mecanismo.
Embedded
Labworks
NEW I/O API
#include <asm/io.h>
/* mapping port I/O */
void *ioport_map(unsigned long port,
unsigned int count);
void ioport_unmap(void *addr);
/* mapping memory­mapped I/O */
void *ioremap(unsigned long phys_addr,
unsigned long size);
void iounmap(void * addr);
Embedded
Labworks
NEW I/O API (cont.)
#include <asm/io.h>
/* read/write 8 bits */
unsigned int ioread8(void *addr);
void iowrite8(u8 value, void *addr);
/* read/write 16 bits */
unsigned int ioread16(void *addr);
void iowrite16(u16 value, void *addr);
/* read/write 32 bits */
unsigned int ioread32(void *addr);
void iowrite32(u32 value, void *addr);
Embedded
Labworks
NEW I/O API (cont.)
#include <asm/io.h>
/* set value starting on addr count times */
void memset_io(void *addr, u8 value, unsigned int count);
/* copy from I/O to memory count bytes */
void memcpy_fromio(void *dest, void *source, unsigned int count);
/* copy from memory to I/O count bytes */
void memcpy_toio(void *dest, void *source, unsigned int count);
Embedded
Labworks
MEMORY BARRIERS
✗
Imagine que, para acessar um byte de uma E2PROM, você precise
configurar o endereço que deseja acessar no registrador A e depois
fazer a leitura no registrador B. Para ler o byte do endereço 8
ficaria assim:
A = 8; // set address
x = B; // read byte
✗
Neste exemplo, a ordem de execução das instruções é
extremamente importante, mas para a CPU ou para o compilador,
elas parecem ser totalmente independentes e podem ser
executadas em qualquer ordem!
Embedded
Labworks
MEMORY BARRIERS (cont.)
✗
✗
Portanto, o compilador ou a CPU podem reordenar a execução das
instruções ou o acesso à memória (desde que não influenciem a
operação aparente do programa em execução).
Existe uma técnica chamada Memory Barriers para previnir este
tipo de problema.
A = 5; // set address
insert_barrier(); // insert memory barrier
x = B; // read byte
Embedded
Labworks
MEMORY BARRIERS (cont.)
✗
A API do kernel provê algumas funções que funcionam como
memory barriers:
✗
barrier(): previne otimizações do compilador, mas não influencia
no funcionamento do hardware.
✗
rmb(): memory barrier para leitura.
✗
wmb(): memory barrier para escrita.
✗
mb(): memory barrier para leitura e escrita.
Embedded
Labworks
MEMORY BARRIERS (cont.)
#include <asm/system.h>
#include <asm/io.h>
/* example */
__raw_writel(0x01, DIRECTION_ADDR);
wmb();
status = __raw_readl(STATUS_ADDR);
Embedded
Labworks
MEMORY BARRIERS (cont.)
✗
✗
Mecanismos de sincronização do kernel como spinlocks também
server como memory barriers (veremos spinlocks mais adiante).
Mais detalhes na documentação do kernel:
Documentation/memory­barriers.txt
Embedded
Labworks
LABORATÓRIO
Acessando o hardware
Embedded
Labworks
Linux Device Drivers
Gerenciamento de processos
Embedded
Labworks
PROCESSOS E THREADS
✗
✗
✗
Um processo é um programa em execução.
Em sistemas Unix, um processo é criado através da chamada de
sistema fork().
Um processo é composto por:
✗
✗
Um espaço de endereçamento virtual, que contém código, dados,
stack, bibliotecas carregadas dinamicamente, etc.
Uma thread, cujo ponto de entrada é a função main().
Embedded
Labworks
PROCESSOS E THREADS (cont.)
✗
Dentro de um processo, threads adicionais podem ser criadas com
a função pthread_create():
✗
✗
As threads compartilham o mesmo espaço de endereçamento virtual
do processo.
O ponto de entrada da thread é passado como argumento em
pthread_create().
Embedded
Labworks
PROCESSOS E THREADS NO KERNEL
✗
✗
✗
✗
Internamente, o kernel não diferencia processos e threads.
Uma thread nada mais é do que um processo que compartilha
dados com outros processos.
Por este motivo, o Linux escalona threads e não processos!
A partir de agora, pelo fato do kernel tratar tudo como tarefa
(task), vamos nos referir à thread ou processo apenas como tarefa.
Embedded
Labworks
Processo 1
Thread 1
Thread 2
Processo 2
Thread 3
Thread 1
Processo 3
Thread 1
Thread 2
User space
task_struct
Tarefa 1
Tarefa 2
Tarefa 3
Tarefa 4
Tarefa 5
Tarefa 6
Kernel space
Embedded
Labworks
OS ESTADOS DE UMA TAREFA
✗
✗
✗
✗
✗
✗
TASK_RUNNING: O processo esta em execução ou pronto para ser executado.
TASK_INTERRUPTIBLE: O processo esta bloqueado esperando alguma condição,
mas pode ser interrompido se receber um sinal.
TASK_UNINTERRUPTIBLE: O processo esta bloqueado esperando alguma
condição, mas ignora qualquer sinal.
EXIT_ZOMBIE: O processo terminou mas o processo-pai ainda não fez a limpeza.
__TASK_TRACED: O processo esta sendo restreado por outro processo (tracing)
via chamada de sistema ptrace().
__TASK_STOPPED: O processo parou sua execução porque recebeu algum sinal
(SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU) via debugger ou controle de job.
Embedded
Labworks
EXIT_ZOMBIE
TAREFA CRIADA
Finalizada, aguardando
ACK do processo-pai
fork() ou pthread_create()
Tarefa selecionada pelo escalonador
TASK_RUNNING
TASK_RUNNING
Pronta aguardando execução
Em execução
Tarefa é interrompida pelo escalonador
Para executar outra tarefa
Evento acontece e tarefa
fica pronta para execução
TASK_INTERRUPTIBLE ou
TASK_UNINTERRUPTIBLE
Bloqueada
Tarefa é bloqueada
esperando algum evento
Embedded
Labworks
O ESCALONADOR
✗
✗
✗
✗
Como e quando escalonar uma tarefa? Trade-off entre capacidade
de processamento e tempo de resposta (latência).
O Linux é um sistema multitasking preemptivo.
Possui o conceito de classes de escalonador, onde cada classe
possui um algoritmo de escalonamento, que decide qual processo
deve ser executado, quando e por quanto tempo.
O escalonador padrão do Linux é o CFS (Completely Fair
Scheduler), onde cada processo recebe uma "porcentagem justa"
da CPU (foco em performance).
Embedded
Labworks
ESCALONANDO TAREFAS
✗
✗
O kernel pode interromper uma tarefa em execução tanto em
espaço de usuário (padrão) quanto em espaço de kernel
(configurável via opção CONFIG_PREEMPT).
Existem diversos pontos onde o kernel pode interromper uma
tarefa em execução para executar outra tarefa, incluindo:
✗
Ao fim do time slice (quantum) da tarefa.
✗
No retorno do tratamento de uma interrupção.
✗
No retorno de uma chamada de sistema.
Embedded
Labworks
TAREFAS DE TEMPO REAL
✗
✗
✗
✗
O Linux possui também um escalonador para processos de tempo
real, que tem prioridade sobre o CFS.
Mas mesmo assim, o Linux não pode ser considerado um sistema
operacional determinístico (em alguns trechos do código a latência
para atender um pedido de interrupção pode ser muito grande).
Existe um conjunto de patches (PREEMPT_RT) que pode ser aplicado
ao kernel e melhorar este cenário de alta latência.
Uma opção para o uso do Linux em aplicações hard real-time é a
utilização de um kernel de tempo real em conjunto com o Linux
(RTLinux, RTAI, Xenomai).
Embedded
Labworks
CONTEXTO DE EXECUÇÃO
✗
✗
✗
✗
Uma tarefa pode rodar tanto em espaço de usuário (user space)
quanto em espaço de kernel (kernel space).
Quando uma tarefa rodando em espaço de usuário (user space)
executa uma chamada de sistema, ela entra em espaço de kernel
(kernel space).
Porém, apesar de estar executando em kernel space, a chamada de
sistema é executada no contexto da tarefa que a requisitou.
Após finalizar a execução da chamada de sistema, o kernel pode
retornar para a execução da tarefa em espaço de usuário ou
escalonar uma outra tarefa para execução.
Embedded
Labworks
KERNEL THREADS
✗
✗
✗
✗
Kernel threads são tarefas comuns, mas que são executadas em
kernel space.
Muito útil para o kernel ou algum device driver executar operações
em background.
As threads do kernel aparecem na listagem do programa ps entre
colchetes.
A API para a criação de threads do kernel esta disponível em
<linux/kthread.h>.
Embedded
Labworks
KERNEL THREADS (cont.)
✗
✗
✗
Você pode usar a função kthread_create() para criar uma
thread do kernel. Neste caso a thread é criada de forma bloqueada,
e você precisa acordá-la com a função wake_up_process().
Você pode também usar a função kthread_run() para criar e
executar uma thread do kernel. Neste caso a thread é criada e
colocada no estado TASK_RUNNING, pronta para execução.
Para parar uma thread do kernel, você pode usar a função
kthread_stop().
Embedded
Labworks
KERNEL THREADS API
#include <linux/kthread.h>
/* create a kernel thread */
struct task_struct *kthread_create(
int (*threadfn)(void *data), void *data, const char namefmt[], ...);
/* create and run a kernel thread */
struct task_struct *kthread_run(int (*threadfn)(void *data), void *data, const char namefmt[], ...);
/* stop a kernel thread */
int kthread_stop(struct task_struct *k);
Embedded
Labworks
KERNEL THREADS API (EXEMPLO)
#include <linux/kthread.h>
struct task_struct *mytask;
/* kernel thread */
static int mythread(void *unused)
{
while (!kthread_should_stop()) {
/* do something */
}
return(0);
}
/* create and start task */
mytask = kthread_run(mythread, NULL, "%s", "mythread");
/* stop task */
kthread_stop(mytask);
Embedded
Labworks
EVENTOS
✗
✗
Boa parte das tarefas que desenvolvemos são baseadas em
eventos, ou seja, elas devem esperar um evento para continuar seu
processamento. Exemplos:
✗
Eventos temporais ou delay (ex: esperar 100ms).
✗
Operação de I/O (ex: esperar a leitura de um arquivo).
✗
Evento de hardware (ex: esperar uma tecla ser pressionada).
✗
Mecanismos de sincronização (ex: esperar a liberação de um mutex).
Uma forma de implementar tarefas que esperam eventos é através
de polling. Mas ao usar polling, a tarefa irá monopolizar e
disperdiçar ciclos preciosos de CPU.
Embedded
Labworks
SLEEPING
✗
✗
✗
O ideal nestes casos é trabalhar com interrupção. Ou seja, colocar
a tarefa para dormir e pedir para o kernel acordá-la assim que o
evento acontecer.
Quando uma tarefa dorme, o kernel coloca ela em um dos estados
TASK_INTERRUPTIBLE ou TASK_UNINTERRUPTIBLE, e seleciona
outra tarefa para execução.
Assim que o evento acontecer, o kernel coloca a tarefa novamente
no estado TASK_RUNNING, pronta para ser executada.
Embedded
Labworks
SLEEPING (cont.)
Processo de
comunicação
via RS232
Processo de
comunicação
via RS232
Outros processos
são escalonados
Lê um byte da porta serial
Retorna byte lido
… chamada
de sistema
Chamada de
sistema ...
Dorme
Requisita um byte da interface RS232
Acorda o
processo
ISR porta
serial
Embedded
Labworks
WAIT QUEUES
✗
✗
✗
Para dormir, você deve declarar uma wait queue, que mantém uma
lista de tarefas esperando um evento acontecer.
Uma
wait
queue é definida através da variável
wait_queue_head_t, que pode ser inicializada de suas formas:
✗
Estaticamente com a macro DECLARE_WAIT_QUEUE_HEAD().
✗
Dinamicamente com a função init_waitqueue_head().
Os processos devem dormir usando uma das funções do tipo
wait_event*() e devem ser acordados através das funções do
tipo wake_up*().
Embedded
Labworks
SLEEPING API
#include <linux/wait.h>
#include <linux/sched.h>
/* declare wait queue statically */
DECLARE_WAIT_QUEUE_HEAD(my_queue);
/* declare wait queue dynamically */
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
Embedded
Labworks
SLEEPING API (cont.)
#include <linux/wait.h>
/* Sleeps until the task is woken up and the given C expression is true. Caution: can't be interrupted (can't kill the user­ space process!) */
ret = wait_event(queue, condition);
/* Can be interrupted, but only by a “fatal” signal (SIGKILL). Returns ­ERESTARSYS if interrupted. */
ret = wait_event_killable(queue, condition);
/* Can be interrupted by any signal. Returns ­ERESTARTSYS if interrupted. */
ret = wait_event_interruptible(queue, condition);
Embedded
Labworks
SLEEPING API (cont.)
#include <linux/wait.h>
/* Also stops sleeping when the task is woken up and the timeout expired. Returns 0 if the timeout elapsed, non­zero if the condition was met. */
ret = wait_event_timeout(queue, condition, timeout);
/* Same as above, interruptible. Returns 0 if the timeout elapsed, ­ERESTARTSYS if interrupted, positive value if the condition was met. */
ret = wait_event_interruptible_timeout(queue, condition,
timeout);
Embedded
Labworks
SLEEPING API (cont.)
#include <linux/wait.h>
/* wakes up all processes in the wait queue */
wake_up(&queue);
/* wakes up all processes waiting in an interruptible sleep on the given queue */
wake_up_interruptible(&queue);
Embedded
Labworks
SLEEPING API (EXEMPLO)
#include <linux/wait.h>
#include <linux/sched.h>
/* declare wait queue */
wait_queue_head_t wait;
/* initialize wait queue */
init_waitqueue_head(&wait);
/* wait on a wait queue (called on a task) */
ret = wait_event_interruptible(wait, ready != 0);
/* wake up all process waiting on the wait queue (called inside an interrupt handler) */
wake_up_interruptible(&wait);
Embedded
Labworks
IMPLEMENTAÇÃO WAIT_EVENT()
#define __wait_event(wq, condition)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq, &__wait,
TASK_UNINTERRUPTIBLE);
if (condition)
break;
schedule();
}
finish_wait(&wq, &__wait);
} while (0);
Embedded
Labworks
EXCLUSIVE E NON-EXCLUSIVE
✗
✗
✗
Todas as funções que vimos até aqui (wait_event*()) colocam a
tarefa em uma espera não exclusiva.
Isso significa que todas as tarefas esperando no queue podem ser
acordadas
com
as
funções
wake_up()
e
wake_up_interruptible().
Mas você pode esperar um evento de forma exclusiva com a função
wait_event_interruptible_exclusive() .
Embedded
Labworks
EXCLUSIVE E NON-EXCLUSIVE (cont.)
✗
✗
Desta forma, se você tiver várias tarefas esperando de forma
esclusiva, as funções wake_up() e wake_up_interruptible()
acordarão apenas uma delas. Portanto, o modo exclusivo faz
sentido quando apenas uma tarefa irá "consumir" o evento.
Mas se mesmo assim você quiser acordar todas as tarefas que
estão esperando de forma exclusiva, basta usar as funções
wake_up_all() e wake_up_interruptible_all().
Embedded
Labworks
LABORATÓRIO
Kernel threads e wait queues
Embedded
Labworks
Linux Device Drivers
Gerenciamento de interrupções
Embedded
Labworks
INTERRUPÇÃO
✗
✗
✗
✗
Um dos principais trabalhos do sistema operacional é gerenciar e se
comunicar com os dispositivos de hardware conectados ao sistema.
Para isso, o kernel pode checar o status do hardware periodicamente
(polling) e atuar de acordo.
Mas como os dispositivo de hardware são muito mais lentos que a
CPU, este mecanimos de acesso ao hardware disperdiça preciosos
ciclos de CPU, e não é normalmente usado.
Por este motivo, para conversar com o hardware, a CPU, e
consequentemente o kernel, usam o mecanismo de interrupção.
Embedded
Labworks
INTERRUPÇÃO (cont.)
✗
✗
✗
O mecanismo de interrupção possibilita ao hardware a capacidade
de sinalizar a CPU quando acontecer algum evento (o botão do
mouse foi pressionado, uma tecla foi digitada, um pacote foi
recebido da interface Ethernet, etc).
Diferentes dispositivos estão associados à diferentes números de
interrupção, também chamadas de linhas de IRQ (Interrupt Request).
Quando a CPU recebe o sinal de interrupção, interrompe a execução
atual, e de acordo com a linha de IRQ, executa uma rotina de
tratamento de interrupção registrada no sistema (também chamada
de interrupt handler ou ISR - Interrupt Service Routine).
Embedded
Labworks
INTERRUPÇÃO NO KERNEL
✗
✗
Uma rotina de tratamento de interrupção nada mais é do que uma
função definida pelo device driver (com um protótipo bem definido
que veremos mais adiante).
Para um device driver usar uma linha de interrupção, ele deve:
✗
✗
✗
Requisitar o uso da linha de IRQ quando for iniciar o uso do
dispositivo de hardware através da função request_irq().
Implementar a ISR (veremos as regras de implementação da ISR
mais adiante).
Liberar o uso da linha de IRQ quando não for mais usar o dispositivo
através da função free_irq().
Embedded
Labworks
IRQ API
#include <linux/interrupt.h>
/* request IRQ, returns 0 on success */
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irq_flags, const char *devname, void *dev_id);
/* free IRQ */
void free_irq(unsigned int irq, void *dev_id);
Embedded
Labworks
IRQ FLAGS
✗
Estas são as principais flags que podem ser passadas para
registrar uma IRQ:
✗
IRQF_SHARED: O canal de IRQ pode ser compartilhado por várias
ISRs. Neste caso, o hardware precisa prover um mecanismo para que
a ISR possa verificar a origem da interrupção.
✗
IRQF_SAMPLE_RANDOM: usar a interrupção para alimentar a rotina
de geração de números aleatórios do kernel.
Embedded
Labworks
IRQ API (EXEMPLO)
#include <linux/interrupt.h>
/* request IRQ */
if (request_irq(irqn, mydriver_isr, IRQF_SHARED,
"mydriver", mydriver.dev)) {
pr_err("Error requesting IRQ!\n");
[...]
}
/* free IRQ */
free_irq(irqn, mydriver.dev);
Embedded
Labworks
IRQS REGISTRADAS NO SISTEMA
✗
Para verificar as IRqs registradas no sistema, basta listar o
arquivo /proc/interrupts:
# cat /proc/interrupts CPU0 1: 45 tzic mmc0
3: 0 tzic mmc1
6: 0 tzic sdma
31: 44 tzic IMX­uart
39: 5638 tzic i.MX Timer Tick
62: 0 tzic imx­i2c
87: 6515 tzic imx25­fec
235: 0 gpio­mxc mmc1
237: 0 gpio­mxc mmc0
[...]
Embedded
Labworks
PROTÓTIPO DE UMA ISR
/**
* ISR prototype.
* @irq: the IRQ number
* @dev_id: the opaque pointer passed at request_irq()
*
* Should return IRQ_HANDLED if interrupt was recognized * and handled or IRQ_NONE if interrupt wasn't recognized * or managed by module.
*/
irqreturn_t foo_interrupt(int irq, void *dev_id);
Embedded
Labworks
RESTRIÇÕES DE UMA ISR
✗
✗
✗
✗
Como não existe uma garantia de qual espaço de endereçamento o
sistema estará quando uma interrupção acontecer, não é possível
trocar dados com user space.
A execução da rotina de tratamento de interrupção é gerenciada pela
CPU, e não pelo escalonador. Ou seja, uma ISR roda em contexto de
interrupção, e não em contexto de processo!
Por este motivo, uma ISR não pode dormir, já que ela não roda em
contexto de processo, e não pode ser escalonada.
Em específico, alocações de memória devem ser feitas passando a
flag GFP_ATOMIC.
Embedded
Labworks
RESTRIÇÕES DE UMA ISR (cont.)
✗
Desde o kernel 2.6.36, quando uma ISR é executada:
✗
✗
A linha de IRQ da interrupção é desabilitada em todas as CPUs (por
isso uma ISR não precisa ser reentrante).
Todas as interrupções locais (da mesma CPU) são desabilitadas. Por
este motivo, uma ISR deve executar seu trabalho rapidamente e
retornar.
Embedded
Labworks
IMPLEMENTANDO UMA ISR
✗
✗
✗
Reconhecer a interrupção, por exemplo setando um bit em um
registrador do controlador de interrupções.
Ler ou escrever dados do dispositivo.
Sinalizar o processo esperando um evento do dispositivo,
normalmente usando wait queues:
wake_up_interruptible(&mydriver_queue);
Embedded
Labworks
ISR EXEMPLO 1
irqreturn_t mydriver_interrupt(int irq, void *dev_id)
{
/* ack interrupt */
ack();
/* read device data */
dev_id­>data = getdata();
/* wake up process waiting on wait queue */
wake_up(&mydriver_queue);
/* return IRQ handled*/
return IRQ_HANDLED;
}
Embedded
Labworks
TOP HALF E BOTTOM HALF
✗
✗
Em alguns casos, uma interrupção precisa realizar uma quantidade
grande de trabalho, ao mesmo tempo em que sua execução precisa
ser bem rápida. São necessidades conflitantes!
Para estes casos, o processamento pode ser dividido em duas
partes:
✗
Top Half.
✗
Bottom Half.
Embedded
Labworks
TOP HALF
✗
✗
✗
Este é próprio código da ISR, que é executado assim que acontece a
interrupção.
Deve retornar o mais rápido possível, já que as interrupções estão
desabilitadas.
Por esse motivo, ele deve delegar o tratamento dos dados ou
evento recebido para um mecanismo de bottom half.
Embedded
Labworks
BOTTOM HALF
✗
✗
✗
Será executado em algum momento no futuro, com todas as
interrupções habilitadas.
Desta forma, o impacto é menor se levar mais tempo para realizar
o processamento delegado pela interrupção.
No Linux, pode ser implementado através de três mecanismos
diferentes: softirqs, tasklets ou workqueues.
Embedded
Labworks
ISR EXAMPLE 2
/* driver ISR */
irqreturn_t mydriver_interrupt(int irq, void *dev_id)
{
ack();
dev_id­>data = getdata();
enable_bottom_half();
return IRQ_HANDLED;
}
/* bottom half ­ execute with interrupts enabled */
void bottom_half_function()
{
/* do the heavy work here! */
[...]
wake_up(&mydriver_queue);
}
Embedded
Labworks
SOFTIRQS
✗
✗
✗
✗
✗
Softirqs são uma forma de processamento em bottom half.
São executadas depois que todas as interrupções forem processadas,
normalmente no fim do tratamento das interrupções.
Rodam portanto em contexto de interrupção, por isso não podem
bloquear (dormir).
Mas como são executadas com todas as interrupções habilitadas, o
impacto no tempo de resposta do sistema é bem menor.
Dependendo da carga do sistema, podem ser tratadas também em
threads do kernel (vide ksoftirqd/X, onde X é o número da CPU).
Embedded
Labworks
SOFTIRQS (cont.)
✗
✗
✗
✗
A mesma softirq pode rodar em múltiplas CPUs ao mesmo tempo, por esse
motivo é preciso tomar cuidado extra com sua implementação (em
específico, ela precisa ser thread-safe).
A quantidade de softirqs disponíveis no sistema é limitada (por esse
motivo ela não é usada normalmente por drivers, e sim por sub-sistemas
do kernel, como o sub-sistema de rede).
A lista de softirqs esta definida em <linux/interrupt.h>: HI, TIMER,
NET_TX, NET_RX, BLOCK, BLOCK_IOPOLL, TASKLET, SCHED, HRTIMER, RCU.
Não é muito comum o uso direto de softirqs por drivers. O mais comum é
usar as softirqs através da implementação de tasklets.
Embedded
Labworks
TASKLETS
✗
✗
✗
As tasklets são executadas nas softirqs HI e TASKLET.
Como é um tipo de softirq, tem as mesmas características desta,
rodando em contexto de interrupção com todas as interrupções
habilitadas.
A única diferença com relação às softirqs é que é garantido que só
uma instância da tasklet estará rodando ao mesmo tempo, mesmo
em sistemas com múltiplas CPUs (SMP).
Embedded
Labworks
TASKLETS (cont.)
✗
✗
Uma tasklet pode ser declarada estaticamente com a macro
DECLARE_TASKLET() ou criada dinamicamente com a função
tasklet_init().
Uma ISR pode delegar trabalho para uma tasklet usando uma das
duas funções abaixo:
✗
tasklet_schedule(): executa a tasklet na softirq TASKLET.
✗
tasklet_hi_schedule(): executa a tasklet na softirq HI, que
possui maior prioridade.
Embedded
Labworks
TASKLET API
#include <linux/interrupt.h>
/* init tasklet */
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
/* kill tasklet */
void tasklet_kill(struct tasklet_struct *t);
/* schedule tasklet */
void tasklet_schedule(struct tasklet_struct *t)
/* tasklet function */
static void tasklet_func(unsigned long data);
Embedded
Labworks
TASKLET API (EXEMPLO)
#include <linux/interrupt.h>
/* The tasklet function */
static void atmel_tasklet_func(unsigned long data) {
struct uart_port *port = (struct uart_port *)data;
[...]
}
/* Registering the tasklet (ex: on an init function) */
tasklet_init(&atmel_port­>tasklet, atmel_tasklet_func,
(unsigned long)port);
/* Removing the tasklet (ex: on a cleanup function) */
tasklet_kill(&atmel_port­>tasklet);
/* Triggering execution of the tasklet (ex: on an ISR) */
tasklet_schedule(&atmel_port­>tasklet);
Embedded
Labworks
WORK QUEUES
✗
✗
✗
✗
Work queue é um mecanismo genérico de deferir trabalho, não só
limitado ao tratamento de interrupções.
A função definida como work queue é executada em uma thread do
kernel (kworker) com todas as interrupções habilitadas.
Como uma work queue roda em uma thread do kernel, ela é
executada em contexto de processo, e por este motivo pode
bloquear (dormir).
Portanto, se o processamento deferido pela CPU precisa bloquear
(dormir), em vez de usar tasklets, você deverá usar work queues.
Embedded
Labworks
WORK QUEUES (cont.)
✗
✗
Em sua forma mais comum, a work queue é uma interface para
deferir trabalho para uma thread genérica do kernel (events/n ou
kworker/n:id, dependendo da versão do kernel, onde n é o
número da CPU e id é o id da work queue).
Um trabalho a ser delegado (work) pode ser criado com
DECLARE_WORK() ou INIT_WORK(), e é normalmente acionado
com schedule_work().
Embedded
Labworks
WORK QUEUE API
#include <linux/workqueue.h>
/* create work statically */
DECLARE_WORK(name, void (*func)(void *));
/* create work dinamically */
INIT_WORK(struct work_struct *work, void (*func)(void *));
/* schedule work on default work queue thread */
int schedule_work(struct work_struct *work);
/* schedule delayed work on default work queue thread */
int schedule_delayed_work(struct work_struct *work,
unsigned long delay);
Embedded
Labworks
CRIANDO NOVAS WORK QUEUES
✗
✗
✗
As funções schedule_work() e schedule_delayed_work() irão
delegar o trabalho para uma thread de tratamento de work queues
genérica do sistema.
Isso significa que seu processamento poderá competir com o
trabalho delegado por outros drivers ou sub-sistemas do kernel.
Se você precisa de mais performance ou exclusividade no
tratamento dos trabalhos, você pode criar uma nova work queue
com a função alloc_workqueue().
Embedded
Labworks
WORK QUEUE API (cont.)
#include <linux/workqueue.h>
/* create new work queue */
struct workqueue_struct *alloc_workqueue(char *name,
unsigned int flags, int max_active);
/* schedule work on user defined work queue */
int queue_work(struct workqueue_struct *wq,
struct work_struct *work);
/* schedule delayed work on user defined work queue */
int queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *work,
unsigned long delay);
Embedded
Labworks
WORKQUEUE API (EXEMPLO)
void init_function()
{
INIT_WORK(&gpio­>work, pcf857x_irq_demux_work);
[...]
}
static irqreturn_t isr_function(int irq, void *data) {
[...]
schedule_work(&gpio­>work);
return IRQ_HANDLED;
}
static void pcf857x_irq_demux_work(struct work_struct *work)
{
/* do the work here! */
}
Embedded
Labworks
COMPARANDO
Bottom Half
Contexto
Serializado
Softirq
Interrupção
Não
Tasklet
Interrupção
Sim (na mesma tasklet)
Work queues
Processo
Não
Embedded
Labworks
QUAL BOTTOM HALF USAR?
✗
✗
✗
Work queues envolvem um overhead maior pelo uso de threads do
kernel e pelas trocas de contexto envolvidas. Você deverá usar
work queues quando existir a possibilidade do trabalho ser
escalonado, ou seja, se o trabalho precisar dormir.
Por outro lado, tasklets envolvem menos overhead e devem ser o
método preferido se seu trabalho não precisar dormir e puder ser
executado em contexto de interrupção.
Se você precisar de muita performance, avalie a possibilidade de
usar softirqs no lugar de tasklets.
Embedded
Labworks
OUTROS MECANISMOS
✗
Existem ainda outros mecanismos que podem ser usados no
tratamento de interrupções no Linux, dentre eles:
✗
Threaded Interrupts.
✗
Kernel Timers.
Embedded
Labworks
THREADED INTERRUPTS
✗
✗
O suporte à threaded interrupts tem suas origens na árvore de realtime do
kernel e foi adicionado ao Linux a partir da versão 2.6.30.
Seu principal objetivo é diminuir a latência do sistema como um todo,
fazendo com que uma interrupção seja tratada dentro de uma thread do
kernel, rodando em contexto de processo.
✗
Por este motivo, é permitido bloquear (dormir) em uma threaded interrupt.
✗
Para
✗
Mais informações em <linux/interrupt.h>.
registrar
uma
threaded
request_threaded_irq().
interrupt,
basta
usar
a
função
Embedded
Labworks
KERNEL TIMERS
✗
✗
✗
✗
Kernel timers é um mecanismo usado para programar a execução
de um trabalho em determinado momento no futuro.
Para usá-lo, você precisa declarar uma estrutura do tipo
timer_list e inicializar esta estrutura basicamente com o valor
do timer e a função de callback.
Os kernel timers rodam em uma softirq (TIMER_SOFTIRQ), ou seja,
em contexto de interrupção, e portanto não podem bloquear!
Mais informações em <linux/timer.h>.
Embedded
Labworks
LABORATÓRIO
Trabalhando com interrupções
Embedded
Labworks
Linux Device Drivers
Mecanismos de sincronização
Embedded
Labworks
MULTITHREAD E CONCORRÊNCIA
✗
✗
✗
Em um ambiente multithread, os recursos compartilhados do
sistema precisam ser protegidos de acesso concorrente.
Um trecho de código que acessa recursos compartilhados é
chamado de região crítica (critical session).
Para previnir o acesso concorrente à uma região crítica, o acesso
deve ser atômico, ou seja, a operação em cima do recurso
compartilhado deve ser executada o começo ao fim sem
interrupção.
Embedded
Labworks
CONCORRÊNCIA NO KERNEL
✗
✗
Em termos de concorrência, o kernel tem as mesmas restrições de um
programa multithread: o seu estado é global e visível à todos os
contextos de execução (interrupção e processos).
E os problemas de concorrência no kernel podem acontecer porque:
✗
✗
✗
Uma interrupção pode interromper a execução de um processo, e ambos
podem estar usando recursos compartilhados.
A preempção do kernel pode interromper a execução de uma chamada de
sistema para executar outra chamada de sistema, e ambas podem estar
usando recursos compartilhados.
Em sistemas SMP (com múltiplas CPUs) podemos ter processos rodando
realmente em paralelo em diferentes processadores, que podem estar
usando recursos compartilhados.
Embedded
Labworks
SOLUÇÃO
✗
✗
Sempre que possível, mantenha um estado local dos processos.
Caso você precise de recursos compartilhados, identifique quais
são estes recursos:
✗
✗
Se o seu recurso compartilhado for um número inteiro, você pode
usar as funções atômicas disponibilizadas pelo kernel.
Em outros casos, como estruturas de dados mais complexas ou
recursos de hardware, você deverá usar um mecanismo de locking.
Embedded
Labworks
ATOMIC OPERATIONS
✗
✗
✗
✗
O recurso de operações atômicas do kernel é útil quando o recurso
compartilhado esta armazenado em uma variável do tipo inteiro.
Mesmo uma operação simples em inteiros do tipo x++ ou x|=1 não é
garantida que seja atômica em todas as arquiteturas!
O uso das funções de operações atômicas garantem acesso atômico
às variáveis do tipo inteiro.
O Linux fornece duas APIs de operações atômicas:
✗
Operações em número inteiros, definida em <asm/atomic.h>.
✗
Operações em bits, definida em <asm/bitops.h>.
Embedded
Labworks
ATOMIC OPERATIONS API
#include <asm/atomic.h>
/* set or read the the variable */
void atomic_set(atomic_t *v, int i);
int atomic_read(atomic_t *v);
/* change variable */
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
void atomic_add(int i, atomic_t *v);
void atomic_sub(int i, atomic_t *v);
Embedded
Labworks
ATOMIC OPERATIONS API (cont.)
#include <asm/atomic.h>
/* change and return true if result is zero */
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_add_and_test(int i, atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
/* change and return result */
int atomic_inc_and_return(atomic_t *v);
int atomic_dec_and_return(atomic_t *v);
int atomic_add_and_return(int i, atomic_t *v);
int atomic_sub_and_return(int i, atomic_t *v);
Embedded
Labworks
ATOMIC BIT OPERATIONS API
#include <asm/bitops.h>
/* set, clear or toggle a given bit */
void set_bit(int nr, unsigned long * addr);
void clear_bit(int nr, unsigned long * addr);
void change_bit(int nr, unsigned long * addr);
/* test bit and return its value */
int test_bit(int nr, unsigned long *addr);
/* set a bit and return its old value */
int test_and_set_bit(int nr, unsigned long *addr);
int test_and_clear_bit(int nr, unsigned long *addr);
int test_and_change_bit(int nr, unsigned long *addr);
Embedded
Labworks
LOCKING
✗
✗
✗
Nem sempre um recurso compartilhado pode ser representado por
uma variável do tipo inteiro.
Na maioria das vezes, a região crítica que precisamos proteger
atua em cima de estruturas de dados mais complexas.
Para estes casos, devemos proteger o acesso ao recurso
compartilhado através de mecanismos de locking.
Embedded
Labworks
LOCKING (cont.)
Processo 1
Processo 2
lock()
Seção crítica
unlock()
wait_lock()
Embedded
Labworks
SEMÁFORO
✗
✗
✗
✗
O semáforo é um mecanismo de comunicação entre tarefas.
Quando uma tarefa tenta adquirir um semáforo que esta
indisponível, a tarefa é colocada em uma fila de espera e bloqueia
(dorme).
Quando o semáforo ficar disponível, a tarefa é acordada, adquire o
semáforo e pode realizar seu trabalho.
Após realizar o processamento, a tarefa deverá liberar o semáforo
para outra tarefa utilizá-lo se necessário.
Embedded
Labworks
SEMÁFORO (cont.)
✗
✗
✗
Um semáforo pode ser usado como mecanismo de comunicação de
eventos entre tarefas (sincronização) ou como um mecanismo de
locking.
Durante bastante tempo, o Linux implementava o mecanismo de
locking através de semáforos. Mais informações sobre a API de
semáforos em <asm/semaphore.h>.
A partir da versão 2.6.16, a funcionalidade de mutex (abreviação
de mutual exclusion) foi implementada e adotada como mecanismo
padrão de locking para gerenciar o acesso à recursos
compartilhados no kernel.
Embedded
Labworks
MUTEX
✗
✗
✗
✗
✗
Antes de entrar em uma região crítica, um lock deve ser adquirido.
Se o lock não estiver disponível, a tarefa deverá bloquear (dormir)
aguardando o lock ficar disponível.
Assim que o lock ficar disponível, a tarefa é acordada, adquire o lock
e realiza seu processamento.
Após o processamento, a tarefa deve liberar o lock.
Como o processo que requisita o lock pode bloquear (dormir) se este
lock estiver indisponível, um mutex só pode ser usado em contexto de
processo.
Embedded
Labworks
MUTEX API
#include <linux/mutex.h>
/* initializing a mutex statically */
DEFINE_MUTEX(mutex_name);
/* initializing a mutex dynamically */
void mutex_init(struct mutex *lock);
Embedded
Labworks
MUTEX API (cont.)
#include <linux/mutex.h>
/* tries to lock the mutex, sleeps otherwise. Can't be
interrupted, resulting in processes you cannot kill! */
void mutex_lock(struct mutex *lock);
/* same, but can be interrupted by a fatal (SIGKILL) signal.
If interrupted, returns a non zero value and doesn't hold
the lock. Test the return value! */
int mutex_lock_killable(struct mutex *lock);
/* same, but can be interrupted by any signal */
int mutex_lock_interruptible(struct mutex *lock);
Embedded
Labworks
MUTEX API (cont.)
#include <linux/mutex.h>
/* never waits, returns a non zero value if the mutex is
not available */
int mutex_trylock(struct mutex *lock);
/* just tells whether the mutex is locked or not */
int mutex_is_locked(struct mutex *lock);
/* releases the lock, do it as soon as you leave the
critical section */
void mutex_unlock(struct mutex *lock);
Embedded
Labworks
MUTEX API (EXEMPLO)
#include <linux/mutex.h>
static int __devinit ads7846_probe(struct spi_device *spi)
{
[...]
mutex_init(&ts­>lock);
[...]
}
static void ads7846_enable(struct ads7846 *ts)
{
mutex_lock(&ts­>lock);
if (ts­>disabled) {
ts­>disabled = false;
[...]
}
mutex_unlock(&ts­>lock);
}
Embedded
Labworks
DEFICIÊNCIAS DOS MUTEXES
✗
Os mutexes devem ser o mecanismo preferido para proteger o
acesso concorrente à regiões críticas, mas eles possuem duas
principais deficiências:
✗
✗
✗
Existe um overhead de pelo menos duas trocas de contexto na sua
execução (colocar a tarefa para dormir quando o mutex estiver sendo
usado e acordar a tarefa quando o mutex for liberado).
O fato de bloquear não permite seu uso em interrupções. Por
exemplo, um sistema multicore onde temos uma CPU executando
uma ISR e outra CPU executando um processo, ambos acessando um
recurso compartilhado.
É para estes casos específicos que existem os spinlocks.
Embedded
Labworks
SPINLOCKS (cont.)
✗
✗
✗
O spinlock é um mecanismo de locking criado para ser usado em
trechos de código que não podem (ou não querem) bloquear, como
por exemplo ISRs ou bottom halves, e são importantes em
sistemas multicore onde temos processos e interrupções
acessando um recurso compartilhado ao mesmo tempo.
Ele é implementado como um loop que fica verificando (spinning)
em um loop até que o lock esteja disponível.
Por este motivo, a seção crítica protegida pelo spinlock deve ser
executada rapidamente e não pode bloquear (dormir).
Embedded
Labworks
SPINLOCKS (cont.)
✗
✗
✗
Além de verificar o lock, um spinlock desabilita a preempção do
kernel na CPU em que esta executando.
O lock do spinlock só faz sentido em sistemas multicore. Por este
motivo, em sistemas com um único core o tratamento do lock é
removido, e só o código que habilita/desabilita a preempção é
compilado.
Em sistemas com um único core e com a preempção do kernel
desabilitada, o código relacionado ao spinklock é removido
completamente!
Embedded
Labworks
SPINLOCK API
#include <linux/spinlock.h>
/* initializing a spinlock statically */
DEFINE_SPINLOCK(my_lock);
/* initializing a spinlock dynamically */
void spin_lock_init(spinlock_t *lock);
Embedded
Labworks
SPINLOCK API (cont.)
#include <linux/spinlock.h>
/* doesn't disable interrupts. Used for locking in process
context (critical sections in which you do not want to
sleep) */
void spin_lock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
/* disables/restores IRQs on the local CPU. Typically used
when the lock can be accessed in both process and
interrupt context, to prevent preemption by interrupts */
void spin_lock_irqsave(spinlock_t *lock,
unsigned long flags);
void spin_unlock_irqrestore(spinlock_t *lock,
unsigned long flags);
Embedded
Labworks
SPINLOCK API (cont.)
#include <linux/spinlock.h>
/* disables software interrupts, but not hardware ones.
Useful to protect shared data accessed in process context
and in a soft interrupt (“bottom half”). No need to
disable hardware interrupts in this case */
void spin_lock_bh(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
Embedded
Labworks
SPINLOCK API (EXEMPLO)
#include <linux/spinlock.h>
static void serio_init_port(struct serio *serio)
{
[...]
spin_lock_init(&serio­>lock);
[...]
}
irqreturn_t serio_interrupt(struct serio *serio,
unsigned char data, unsigned int dfl)
{
unsigned long flags;
spin_lock_irqsave(&serio­>lock, flags);
[...]
spin_unlock_irqrestore(&serio­>lock, flags);
}
Embedded
Labworks
DESABILITANDO PREEMPÇÃO
✗
✗
✗
✗
Em sistemas com apenas uma CPU, onde o recurso compartilhado é
acessado apenas por processos, não precisamos de um spinlock, já
que este recurso não será acessado por outra CPU.
Neste caso, temos duas opções: usar um mutex ou desabilitar a
preempção do kernel.
Se você quiser evitar o overhead do uso de um mutex, pode usar a
família de funções para habilitar e desabilitar a preempção, como
as funções preempt_enable() e preempt_disable().
Estas funções estão definidas em <linux/preempt.h>.
Embedded
Labworks
DESABILITANDO INTERRUPÇÕES
✗
✗
✗
✗
Em sistemas com uma única CPU, ao compartilhar dados entre um
processo e uma interrupção, você pode simplesmente desabilitar as
interrupções.
Para isso, você pode usar a família de funções de habilitação e
desabilitação de interrupções, como por exemplo as funções
local_irq_disable(), local_irq_enable(), local_irq_save()
e local_irq_restore().
Mais informações sobre estas funções em <linux/irqflags.h>.
Lembre-se de que ao desabilitar as interrupções, você estará
aumentando o tempo de latência do sistema.
Embedded
Labworks
RESUMO
✗
Compartilhando dados entre processos:
✗
✗
✗
✗
Como padrão, use mutex.
Caso a performance seja importante, avalie a possibilidade do uso de
spinlocks, mas lembre-se de que spinlocks aumentam o tempo de latência do
sistema.
Compartilhando dados entre processos e interrupções:
✗
Evite, se possível!
✗
Use spinlocks.
Lembre-se: desenvolva o driver sempre levando em consideração que ele
pode rodar em um sistema SMP e com a preempção do kernel habilitada!
Embedded
Labworks
DEADLOCKS
SITUAÇÃO 1
SITUAÇÃO 2
lock(1)
lock(1)
lock(2)
lock(1)
lock(2)
lock(1)
deadlock
deadlock
Embedded
Labworks
READER-WRITE SPINLOCKS
✗
✗
✗
✗
Quando um processo pretende escrever em um recurso
compartilhado, é importante que nenhum outro processo tenha
acesso ao recurso compartilhado.
Porém, quando um processo pretende ler de um recurso
compartilhado, não existe problema se outro processo também
tentar ler deste mesmo recurso compartilhado.
Neste caso podemos ter um lock exclusivo para escritas e um lock
compartilhado para leituras.
O Linux implementa esta funcionalidade através de spinlocks do tipo
Reader-Writer.
Embedded
Labworks
READER-WRITE SPINLOCKS API
#include <linux/rwlock.h>
/* initialize */
DEFINE_LOCK(mydriver_rwlock);
void rwlock_init(rwlock_t *lock);
/* lock on read */
void read_lock(rwlock_t *lock);
void read_unlock(rwlock_t *lock);
/* lock on write */
void write_lock(rwlock_t *lock);
void write_unlock(rwlock_t *lock);
Embedded
Labworks
OUTROS MECANISMOS
✗
✗
Sequencial locks: também chamado de seqlock, é um mecanismo
parecido com o Reader-Write lock, mas prioriza escritas ao invés
de leituras. Mais informações em <linux/seqlock.h>.
RCU (Read Copy Update): é um outro mecanismo parecido com o
Reader-Write lock, com as rotinas de leitura muito mais rápidas,
porém com a rotina de escrita mais lenta. Mais informações em
<linux/rcupdate.h>.
Embedded
Labworks
COMPLETION VARIABLE
✗
✗
✗
✗
A variável completion é um exemplo de uso de semáforos como
mecanismo de comunicação de eventos (notificação) entre tarefas.
Uma tarefa espera (dorme) na variável completion enquanto outra
tarefa realiza algum processamento.
Quando a outra tarefa finalizar o processamento, ela utiliza a
variável completion para acordar qualquer outra tarefa que esteja
esperando.
Diferentemente de semáforos ou wait queues, usando completion
você consegue acordar várias tarefas ao mesmo tempo.
Embedded
Labworks
COMPLETION VARIABLE API
#include <linux/completion.h>
/* initialize completion variable */
DECLARE_COMPLETION(x);
void init_completion(struct completion *x);
/* wait for completion variable */
void wait_for_completion(struct completion *x);
/* wait for completion variable ­ return on signal */
int wait_for_completion_interruptible(struct completion *x);
/* sinalize completion variable */
void complete(struct completion *x);
/* sinalize completion variable on all threads */
void complete_all(struct completion *x);
Embedded
Labworks
LABORATÓRIO
Usando mecanismos de sincronização
Embedded
Labworks
Linux Device Drivers
Kernel Debugging
Embedded
Labworks
KERNEL DEBUGGING
✗
✗
✗
Debugging do kernel é bem mais difícil quando comparado ao
debugging de aplicações.
Um bug em uma aplicação derruba apenas a aplicação, já um bug
no kernel pode derrubar todo o sistema!
Debugging do kernel exige conhecimentos do sistema operacional e
das diversas técnicas de análise, tracing e profiling que
estudaremos nesta seção do treinamento.
Embedded
Labworks
DEBUGGING COM MENSAGENS
✗
✗
A função printk(), definida em <linux/printk.h>, é a responsável por
imprimir mensagens no kernel, podendo ser chamada tanto em contexto de
processo quanto em contexto de interrupção.
Ela tem o mesmo protótipo da função printf() usada em user space:
int printk(const char *s, ...);
✗
✗
Todas as mensagens do kernel são armazenadas em um buffer circular (ring
buffer), cujo tamanho pode ser definido em tempo de compilação na opção
CONFIG_LOG_BUF_SHIFT ou em tempo de execução no parâmetro de boot
log_buf_len.
As mensagens são normalmente exibidas na console e podem ser emitidas a
qualquer momento com a ferramenta dmesg.
Embedded
Labworks
NÍVEIS DE LOG
✗
Por padrão, passa-se uma macro indicando o nível (ou prioridade) da mensagem
ao imprimí-la:
printk(KERN_WARNING "warning: skipping physical page 0\n");
✗
✗
Todas as mensagens são armazenadas, mas apenas as mensagens com nível de
log menor ou igual ao configurado serão exibidas na console.
O kernel define os seguintes níveis de mensagens de log:
0 (KERN_EMERG) system is unusable
1 (KERN_ALERT) action must be taken immediately
2 (KERN_CRIT) critical conditions
3 (KERN_ERR) error conditions
4 (KERN_WARNING) warning conditions
5 (KERN_NOTICE) normal but significant condition
6 (KERN_INFO) informational
7 (KERN_DEBUG) debug­level messages
Embedded
Labworks
NÍVEIS DE LOG (cont.)
✗
✗
O nível de log padrão pode ser definido em tempo de compilação na
opção CONFIG_DEFAULT_MESSAGE_LOGLEVEL, no boot passando o
parâmetro loglevel ou em tempo de execução no arquivo
/proc/sys/kernel/printk.
Outros parâmetros de boot que podem alterar o comportamento das
mensagens de log do kernel:
✗
✗
✗
debug: Habilita o nível 7 (KERN_DEBUG) de mensagens de log.
ignore_loglevel: Habilita todos os níveis de mensagens (também
pode ser habilitado em /sys/module/printk/parameters/ ignore_loglevel).
quiet: Configura o nível de log como 4 (KERN_WARNING).
Embedded
Labworks
NOVAS FUNÇÕES
✗
✗
Hoje temos outras opções para exibir mensagens de log no kernel, e não é
mais comum o uso da função printk() para debugging.
Pode-se usar a família de funções pr_*(), definida em <linux/printk.h>:
pr_emerg(), pr_alert(), pr_crit(), pr_err(), pr_warning(), pr_notice(), pr_info(), pr_cont(), pr_debug().
✗
Ou a família de funções dev_*(), definida em <linux/device.h>. Estas
funções recebem como argumento um ponteiro para uma estrutura do tipo
device (estudaremos esta estrutura mais adiante).
dev_emerg(), dev_alert(), dev_crit(), dev_err(), dev_warning(), dev_notice(), dev_info(), dev_dbg().
Embedded
Labworks
MENSAGENS DE DEBUG
✗
✗
✗
✗
As funções pr_debug() e dev_dbg() só são compiladas se você habilitá-las no
kernel.
Quando o kernel é compilado com a opção CONFIG_DEBUG, estas mensagens de debug
são compiladas e exibidas com a função printk() no nível de debug do kernel.
Quando o kernel é compilado com a opção CONFIG_DYNAMIC_DEBUG, as mensagens
de debug podem ser habilitadas por arquivo, por módulo ou até por mensagem! Veja a
documentação do kernel em Documentation/dynamic­debug­howto.txt.
Quando nenhuma destas duas opções do kernel estiverem habilitadas, as mensagens
de debug impressas com pr_debug() e dev_dbg() não são compiladas.
Embedded
Labworks
DEBUGFS
✗
O debugfs é um sistema de arquivos virtual que exporta informações de debug
para user space.
✗
Habilite a opção CONFIG_DEBUG_FS em Kernel hacking -> Debug Filesystem.
✗
Depois é só montar o debugfs:
# mount ­t debugfs none /sys/kernel/debug
# ls /sys/kernel/debug/
asoc bluetooth hid mmc1
bdi gpio mmc0 usb
✗
A API esta documentada do DocBook do kernel:
http://free-electrons.com/kerneldoc/latest/DocBook/filesystems/index.html
Embedded
Labworks
DEBUGFS API
#include <linux/debugfs.h>
/* create a sub­directory for your driver */
struct dentry *debugfs_create_dir(const char *name,
struct dentry *parent);
/* expose an integer as a file in debugfs, where u is for
decimal representation and x for hexadecimal
representation */
struct dentry *debugfs_create_{u,x}{8,16,32}(
const char *name, mode_t mode,
struct dentry *parent, u8 *value);
/* expose a binary blob as a file in debugfs */
struct dentry *debugfs_create_blob(const char *name,
mode_t mode, struct dentry *parent,
struct debugfs_blob_wrapper *blob);
Embedded
Labworks
OUTROS MECANISMOS
✗
Alguns mecanismos de debugging foram bastante usados, mas
agora seu uso não é mais comum:
✗
Usar comandos especiais de ioctl() para debugging (use debugfs).
✗
Usar entradas especiais no sistema de arquivos proc (use debugfs).
✗
Usar entradas especiais no sysfs (use debugfs).
✗
Usar printk() (use a família de funções pr_*() ou dev_*()).
Embedded
Labworks
MAGIC SYSRQ KEY
✗
A Magic Sysrq Key é uma combinação de teclas manipulada pelo
kernel, que pode ser usada como ferramenta de análise, debug e
recuperação do kernel:
✗
✗
✗
✗
No PC: [Alt] + [SysRq] + <caractere>. Em alguns teclados, a tecla SysRq
é a tecla Print Screen.
Pela console serial: <break> + <caractere>.
Em todas plataformas: escrever o caractere diretamente no arquivo
/proc/sysrq­trigger.
Deve
ser
habilitada no kernel através da opção
CONFIG_MAGIC_SYSRQ, e pode ser habilitada ou desabilitada em
tempo de execução através do arquivo /proc/sys/kernel/sysrq.
Embedded
Labworks
MAGIC SYSRQ KEY (cont.)
✗
✗
Exemplos de comandos:
✗
b: Reinicia o sistema automaticamente.
✗
e: Envia o sinal SIGTERM para todos os processos (menos o init).
✗
t: Mostra o stack (dump) de todos os processos em execução.
✗
w: Mostra o stack (dump) dos processos bloqueados (dormindo).
✗
c: Força um crash do kernel desreferenciando um ponteiro nulo.
Mais informações e comandos disponíveis na documentação do
kernel em Documentation/sysrq.txt.
Embedded
Labworks
KERNEL OOPS
✗
✗
✗
✗
✗
Um oops é um mecanismo de comunicação do kernel para notificar o
usuário que um erro aconteceu.
Este erro pode acontecer por diversos motivos, como por exemplo acesso
ilegal à regiões de memória ou execução de instruções inválidas.
Quando acontece um oops o kernel emite uma mensagem na console,
exibindo o status atual do sistema no momento em que aconteceu o
problema, incluindo um dump dos registradores e o back trace do stack.
Após o oops, o kernel irá tentar se recuperar e resumir a execução, mas
dependendo do erro, nem sempre isso é possível.
Portanto, o kernel pode travar após o oops (kernel panic!).
Embedded
Labworks
KERNEL OOPS (EXEMPLO)
Internal error: Oops: 817 [#1] PREEMPT
pc : [<80231bb8>] lr : [<80231fe8>] psr: 60000093
sp : df4f5f18 ip : df4f5e88 fp : 00000002
r10: 00000000 r9 : 00000000 r8 : 00000007
r7 : 60000013 r6 : 808214c8 r5 : 00000000 r4 : 00000063
r3 : 00000001 r2 : 00000000 r1 : 00000000 r0 : 00000063
Stack: (0xdf4f5f18 to 0xdf4f6000)
5f00: 00000002 802320b4
5f20: df4d9400 00000002 000a8d48 00000000 df4f5f80 802320e4 df1f7e00 800fea88
5f40: 00000002 df4d9400 000a8d48 df4f5f80 00000000 00000000 7ec2271c 800bfff0
[<80231bb8>] (sysrq_handle_crash+0x14/0x20) from [<80231fe8>] (__handle_sysrq+0xdc/0x1a8)
[<80231fe8>] (__handle_sysrq+0xdc/0x1a8) from [<802320e4>] (write_sysrq_trigger+0x30/0x38)
[<802320e4>] (write_sysrq_trigger+0x30/0x38) from [<800fea88>] (proc_reg_write+0xb4/0xc8)
[<800fea88>] (proc_reg_write+0xb4/0xc8) from [<800bfff0>] (vfs_write+0xac/0x154)
[<800bfff0>] (vfs_write+0xac/0x154) from [<800c0144>] (sys_write+0x3c/0x68)
[<800c0144>] (sys_write+0x3c/0x68) from [<80030f80>] (ret_fast_syscall+0x0/0x30)
Kernel panic ­ not syncing: Fatal exception
Embedded
Labworks
CONFIGURANDO O OOPS
✗
✗
No exemplo anterior, os endereços estavam convertidos para os
nomes das respectivas funções, facilitando o processo de
debugging.
Para que esta funcionalidade esteja disponível, habilite a opção
CONFIG_KALLSYMS na configuração do kernel (antes o kernel só
exibia os endereços e era necessário usar um programa chamado
ksymoops para fazer a análise).
Embedded
Labworks
NOTIFICANDO PROBLEMAS
✗
Se necessário, é possível gerar um kernel oops através das macros
BUG() ou BUG_ON(). Exemplos:
if (erro) BUG();
BUG_ON(erro);
✗
Se quiser gerar direto um kernel panic, use a função panic():
if (erro_geral)
panic("Alguma coisa terrível aconteceu!");
✗
Já se o que você quer é só imprimir o back trace do stack, use a
função dump_stack().
Embedded
Labworks
OUTRAS OPÇÕES DE DEBUG
✗
✗
O Linux possui diversas funcionalidades de debugging integradas ao kernel.
Para usá-las basta habilitar a opção CONFIG_DEBUG_KERNEL no menu de
configuração, e depois habilitar a funcionalidade de debug desejada no menu
"Kernel hacking". Alguns exemplos:
✗
CONFIG_DEBUG_MUTEXES: habilita checagem do uso de mutexes.
✗
CONFIG_DEBUG_SPINLOCK: habilita checagem de spinlocks.
✗
CONFIG_SLUB_DEBUG_ON: checagem na camada de alocação de memória.
✗
CONFIG_DEBUG_KMEMLEAK: habilita checagem de memory leak.
✗
CONFIG_DEBUG_LL (arm e unicore32): habilita o debugging de baixo nível, útil
se o kernel não inicia ou trava no boot.
Embedded
Labworks
GDB
✗
✗
✗
O GDB (GNU Debugger) é o debugger padrão do projeto GNU, disponível
para diversas arquiteturas.
http://www.gnu.org/software/gdb/
Interface via console, mas com diversos frontends disponíveis
(Eclipse, DDD, GDB/Insight, etc).
Permite iniciar o processo de debugging de um kernel em execução:
$ gdb vmlinux /proc/kcore
✗
O acesso é somente de leitura. Você consegue listar o conteúdo de uma
função, exibir o valor de variáveis, etc; mas não consegue fazer
alterações ou colocar breakpoints por exemplo.
Embedded
Labworks
KDB
✗
✗
✗
✗
✗
O kdb é um debugger que permite examinar a memória e as estruturas de
dados do kernel enquanto o sistema esta em execução.
Durante um bom tempo era disponibilizado através de um conjunto de
patches, mas foi integrado ao mainline na versão 2.6.35.
Fornece uma interface de linha de comandos, permitindo realizar
operações típicas de um debugger como step, stop, run, colocar
breakpoints, disassembly de instruções, etc.
A tecla Pause pode ser usada para entrar no modo de debug.
Não trabalha no nível do código-fonte, apenas no nível de instruções
assembly!
Embedded
Labworks
KGDB
✗
✗
✗
O KGDB permite que a execução do kernel seja controlada remotamente via
conexão serial (ou rede) através do gdb rodando em uma outra máquina.
Esta funcionalidade foi incluída no kernel deste a versão 2.6.26 (x86 e sparc) e
2.6.27 (arm, mips e ppc).
É possível controlar quase tudo, ler e escrever em variáveis, executar
passo-a-passo, e até colocar breakpoints em rotinas de tratamento de
interrupção!
Embedded
Labworks
CONFIGURANDO O KERNEL
✗
Para usar o KGDB, você precisa habilitar as seguintes opções:
✗
CONFIG_KGDB: Habilita o KGDB.
✗
CONFIG_KGDB_SERIAL_CONSOLE: Habilita o driver de I/O para a
comunicação entre o host e o target via console serial.
✗
Além destas configurações, algumas outras opções podem ajudar se
forem habilitadas:
✗
CONFIG_DEBUG_INFO: Para compilar o kernel com símbolos de
debugging.
✗
CONFIG_FRAME_POINTER: Ajuda a produzir back traces de stack mais
confiáveis.
✗
CONFIG_KGDB_KDB: Frontend para o KGDB.
Embedded
Labworks
USANDO O KGDB
✗
Para habilitar o kgdb, basta passar os seguintes parâmetros de
boot para o kernel:
kgdboc=<tty­device>,[baud] kgdbwait
✗
Exemplo:
kgdboc=ttyS0,115200 kgdbwait
✗
Onde:
✗
kgdboc: indica a conexão física com o KGDB.
✗
kgdbwait: faz o kgdb esperar por uma conexão de debug.
Embedded
Labworks
USANDO O KGDB (cont.)
✗
Na sua máquina de desenvolvimento, inicie o gdb conforme abaixo:
$ gdb ./vmlinux
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0
✗
Lembre-se de usar o gdb do seu (cross-compiling) toolchain.
✗
No target, interrompa o kernel com [Alt] + [SyrRq] + [g].
✗
Assim que estabelecer a conexão, você poderá debugar o kernel
como se ele fosse uma aplicação!
Embedded
Labworks
USANDO O KGDB (cont.)
✗
✗
Você também pode debugar via KGDB pela rede (UDP/IP), passando
o parâmetro de boot kgdboe para o kernel (precisa aplicar um
patch, já que esta funcionalidade ainda não esta no mainline).
Mais informações sobre o KGDB em:
http://free-electrons.com/kerneldoc/latest/DocBook/kgdb/
Embedded
Labworks
DEBUGGING COM JTAG
✗
✗
JTAG é uma interface física que permite acesso à modulos de
debug integrados ao core da CPU, onde podemos colocá-la em halt,
inspecionar registradores, memória, colocar breakpoints, etc.
É uma interface de debugging necessária quando trabalhamos no
porte do bootloader ou do kernel para determinada plataforma,
trabalho também chamado de board bring-up.
Embedded
Labworks
DEBUGGING COM JTAG (cont.)
✗
Precisamos basicamente de 4 componentes para trabalhar com
debugging via JTAG:
✗
Hardware (kit de desenvolvimento) com suporte à JTAG.
✗
Adaptador JTAG (Ex: Flyswatter).
✗
Debugger (Ex: GDB).
✗
✗
Software de interface entre o adaptador JTAG e o debugger (Ex:
OpenOCD).
Mais informações em:
http://sergioprado.org/linux-kernel-debugging-com-jtag/
Embedded
Labworks
ANÁLISE COM KEXEC/KDUMP
✗
✗
✗
Kexec é uma chamada de sistema que torna possível executar um
novo kernel sem reiniciar ou passar pelo bootloader/BIOS.
É útil no processo de debugging quando utilizado em conjunto com
o kdump, um mecanismo para analisar um dump do kernel.
Para mais detalhes consulte a documentação do kdump em
Documentation/kdump/kdump.txt.
Embedded
Labworks
QUANDO NADA RESOLVER
✗
✗
✗
Se você esgotar todas as possibilidades de debugging, e mesmo
assim não encontrar o problema... não se desespere! Você ainda
tem a comunidade!
Se o problema esta em uma versão do kernel fornecida pelo
fabricante do chip, estes fabricantes mantém um fórum com
engenheiros especializados para te ajudar.
Se o problema esta na versão atual do kernel, você pode mandar
um e-mail para a lista de discussão do kernel (LKML) descrevendo
o problema e as suas descobertas.
Embedded
Labworks
QUANDO NADA RESOLVER (cont.)
✗
✗
Se o problema esta em um driver que você fez, existem listas de
discussão ou canais de IRC que podem te ajudar, como o Kernel
Newbies.
http://kernelnewbies.org/
E lembre-se, você sempre terá o e-mail do Sergio Prado!
Embedded
Labworks
Linux Device Drivers
Tracing
Embedded
Labworks
KERNEL PROBES
✗
✗
✗
✗
Kernel probes fornecem um mecanismo de instrumentação no kernel.
Com esta funcionalidade é possível extrair informações do kernel e
alterar seu comportamento (aplicar patches) em tempo de execução, sem
precisar reiniciar o sistema!
A implementação genérica de kernel probes é chamada de kprobes, e
pode ser usada para instrumentar qualquer instrução do kernel.
Os kprobes possuem ainda duas variantes:
✗
jprobes: usados para instrumentar chamadas de função.
✗
return probes: usados para instrumentar retornos de função.
Embedded
Labworks
KERNEL PROBES (cont.)
✗
✗
✗
✗
O principio básico do kprobe é baseado no uso de interrupções de
software.
Para usar a infraestrutura de kprobes, você precisa desenvolver um
módulo do kernel e gerenciar os pontos de instrumentação através
de uma estrutura do tipo kprobe.
A limitação do uso de kprobes esta na dificuldade de associar o
código-fonte com o binário gerado (otimizações do compilador,
recursos da linguagem C como macros e funções inline, etc).
Mais informações sobre kprobes em Documentation/kprobes.txt.
Embedded
Labworks
SYSTEMTAP
✗
✗
✗
✗
O SystemTap é uma infraestrutura de tracing que usa kprobes para
facilitar o trabalho de instrumentação no kernel.
http://sourceware.org/systemtap/
Assim como os kprobes, ele também elimina a necessidade de modificar e
recompilar o kernel para investigar um problema funcional ou de
performance.
Utiliza uma linguagem simples de script (diversos exemplos disponíveis
na Internet).
Tutorial e exemplos do SystemTap disponíveis no ambiente de laboratório
do treinamento em /opt/labs/docs/guides/systemtap.pdf.
Embedded
Labworks
SYSTEMTAP (EXEMPLO)
# strace­open.stp
probe syscall.open
{
printf ("%s(%d) open (%s)\n", execname(), pid(), argstr)
}
probe timer.ms(4000) # after 4 seconds
{
exit ()
}
Embedded
Labworks
SYSTEMTAP (TESTANDO EXEMPLO)
# stap strace­open.stp
vmware­guestd(2206) open ("/etc/redhat­release", O_RDONLY)
hald(2360) open ("/dev/hdc", O_RDONLY|O_EXCL|O_NONBLOCK)
hald(2360) open ("/dev/hdc", O_RDONLY|O_EXCL|O_NONBLOCK)
hald(2360) open ("/dev/hdc", O_RDONLY|O_EXCL|O_NONBLOCK)
df(3433) open ("/etc/ld.so.cache", O_RDONLY)
df(3433) open ("/lib/tls/libc.so.6", O_RDONLY)
df(3433) open ("/etc/mtab", O_RDONLY)
hald(2360) open ("/dev/hdc", O_RDONLY|O_EXCL|O_NONBLOCK)
...
Embedded
Labworks
KERNEL TRACEPOINTS
✗
✗
✗
✗
Kprobes possibilitam instrumentar qualquer instrução do kernel,
mas adicionam um overhead ao processamento pelo fato de
trabalharem com interrupções de software.
Uma solução mais leve são os tracepoints, que adicionam ponto de
trace estáticos no kernel, definidos em tempo de compilação.
A implementação é bem simples: se uma função de probe estiver
associada à determinado tracepoint, esta função será chamada.
Mais informações sobre tracepoints nos fontes do kernel em
Documentation/trace/tracepoints.txt .
Embedded
Labworks
LTTng
✗
✗
✗
✗
Toolkit que permite coletar e analisar informações de tracing do
kernel, baseado em kernel tracepoints.
Até então (Linux 3.6), é necessário aplicar patches no kernel para
utilizar esta ferramenta de tracing.
Capacidade de gerar timestamps extremamente precisos e com
muito pouco overhead.
Mais informações na documentação do projeto:
http://lttng.org/documentation
Embedded
Labworks
LTTV VIEWER
Embedded
Labworks
Linux Device Drivers
Profiling
Embedded
Labworks
OPROFILE
✗
✗
✗
O principal objetivo de uma ferramenta de profiling é analisar a
performance do sistema (consumo de ciclos de CPU).
A ferramenta OProfile usa eventos de timer ou contadores de
performance providos pelo processador para capturar dados em
intervalos regulares.
Mais informações sobre o projeto em:
http://oprofile.sourceforge.net/
Embedded
Labworks
OPROFILE (EXEMPLO)
$ opreport ­­exclude­dependent
CPU: PIII, speed 863.195 MHz (estimated)
Counted CPU_CLK_UNHALTED events (clocks processor is not halted) with a unit mask of 0x00 (No unit mask) count 50000
450385 75.6634 cc1plus
60213 10.1156 lyx
29313 4.9245 XFree86
11633 1.9543 as
10204 1.7142 oprofiled
7289 1.2245 vmlinux
7066 1.1871 bash
6417 1.0780 oprofile
6397 1.0747 vim
3027 0.5085 wineserver
1165 0.1957 kdeinit
832 0.1398 wine
...
Embedded
Labworks
LABORATÓRIO
Usando ferramentas de debugging
Embedded
Labworks
Linux Device Drivers
Unified Device Model
Embedded
Labworks
UNIFIED DEVICE MODEL
✗
✗
Antes da versão 2.6, o kernel não possuia um padrão unificado de
desenvolvimento de drivers que descrevesse os dispositivos
conectados ao sistema e sua topologia.
A partir do kernel 2.6 foi implementado o modelo de dispositivos
unificado (unified device model), que é basicamente um framework
para o desenvolvimento de drivers para os diferentes tipos de
dispositivos suportados pelo Linux.
Embedded
Labworks
ALGUMAS VANTAGENS
✗
✗
✗
✗
✗
Padrão para o desenvolvimento de drivers, evitando duplicação de código.
Capacidade de identificar todos os dispositivos disponíveis no sistema, e
em qual barramento eles estão conectados.
Capacidade de ligar dispositivos e drivers.
Capacidade de dividir os dispositivos por classes, sem se importar com
sua topologia física.
Facilita o gerenciamento de energia (conhecendo o topologia do sistema,
fica simples identificar qual dispositivo desligar primeiro).
Embedded
Labworks
ARQUITETURA
✗
A implementação do device model esta baseada em dois
componentes:
✗
✗
Framework de drivers: como o dispositivo se apresenta para a
camada de usuário.
Infraestrutura de barramento: como o driver conversa com o
dispositivo de hardware.
Embedded
Labworks
ARQUITETURA (cont.)
User space
Bibliotecas/aplicações
Interface de chamada de sistema
Kernel space
Framework
Driver
Infraestrutura de barramento
Hardware
Embedded
Labworks
FRAMEWORK DE DRIVERS
✗
✗
✗
Muitos drivers não são implementados diretamente como um driver
de dispositivo de caractere.
Eles são implementados sob um framework específico de um tipo
de dispositivo (exemplos: framebuffer, tty, input, etc).
Vantagens do uso de um framework:
✗
✗
Padrão (API comum) para o desenvolvimento de um tipo de driver.
A mesma interface é provida para as aplicações, independente do
driver.
Embedded
Labworks
FRAMEWORKS
Aplicação
Aplicação
Aplicação
Interface de chamada de sistema
Char
driver
Framebuffer
core
Input
core
Framebuffer
driver
Input
driver
TTY
core
TTY
driver
Block
core
Serial
core
IDE
core
SCSI
core
Serial
driver
IDE
driver
SCSI
driver
Embedded
Labworks
EXEMPLO: FRAMEBUFFER
✗
✗
✗
✗
É o framework padrão para dispositivos de vídeo (monitor, display
LCD, etc).
É habilitado na opção do kernel CONFIG_FB.
Fontes disponíveis em drivers/video/ e definição da API em
<linux/fb.h>.
Implementa um dispositivo de caractere (/dev/fbX), onde as
aplicações podem ler, escrever e enviar comandos ioctl para o
dispositivo.
Embedded
Labworks
EXEMPLO: FRAMEBUFFER (cont.)
✗
Tudo o que um driver de framebuffer precisa fazer é:
✗
✗
✗
✗
Definir e inicializar uma estrutura do tipo fb_info.
Prover um conjunto de operações que podem ser realizadas no
dispositivo através da implementação da estrutura fb_ops.
Registrar
o
dispositivo
de
register_framebuffer().
framebuffer
com
a
função
Existe um modelo de driver de framebuffer disponível em
drivers/video/skeletonfb.c.
Embedded
Labworks
STRUCT FB_OPS
#include <linux/fb.h>
static struct fb_info *info;
static struct fb_ops xxxfb_ops = {
.owner = THIS_MODULE,
.fb_open = xxxfb_open,
.fb_read = xxxfb_read,
.fb_write = xxxfb_write,
.fb_release = xxxfb_release,
.fb_blank = xxxfb_blank,
.fb_fillrect = xxxfb_fillrect,
.fb_copyarea = xxxfb_copyarea,
.fb_sync = xxxfb_sync,
.fb_ioctl = xxxfb_ioctl,
.fb_mmap = xxxfb_mmap,
[...]
};
Embedded
Labworks
REGISTRANDO O FRAMEBUFFER
static int __devinit xxxfb_probe(struct pci_dev *dev,
const struct pci_device_id *ent)
{
[...]
info = framebuffer_alloc(sizeof(struct xxx_par),
device);
info­>fbops = &xxxfb_ops;
if (register_framebuffer(info) < 0)
return ­EINVAL;
[...]
}
Embedded
Labworks
A CAMADA TTY
✗
✗
✗
É o framework padrão para dispositivos de comunicação serial
(porta serial, conversor USB/serial, etc).
Fontes disponíveis em drivers/tty/ e definição da API em
<linux/tty.h>.
Implementa
um dispositivo de caractere (normalmente
/dev/ttyXX), onde as aplicações podem ler, escrever e enviar
comandos ioctl para o dispositivo.
Embedded
Labworks
A CAMADA TTY (cont.)
✗
A camada TTY no Linux possui quatro componentes principais:
✗
✗
✗
✗
Driver de baixo nível: conversa diretamente com o dispositivo de
hardware.
Driver TTY: faz interface do driver de baixo nível com o core TTY,
provendo uma interface comum de acesso ao dispositivo serial.
Core TTY: camada de abstração entre user space (bibliotecas e
aplicações) e o driver TTY.
Line discipline: permite com que o core TTY manipule os dados
enviados e recebidos pelo usuário de diferentes formas (terminal,
conexão PPP, etc).
Embedded
Labworks
A CAMADA TTY (cont.)
User space
/dev/tty*
TTY Core
Kernel space
TTY driver
Low level driver
Hardware
Dispositivo
serial
Line
Discipline
Embedded
Labworks
VANTAGENS DA CAMADA TTY
✗
Esta arquitetura permite que possamos usar a camada TTY para:
✗
Executar uma sessão de terminal através de uma conexão RS232.
✗
Se conectar à Internet via modem dial-up.
✗
Se comunicar com dispositivos infravermelho.
✗
Emular uma porta serial através de um conversor USB/serial.
✗
Se comunicar através de uma porta RS485.
✗
Se conectar remotamente à uma máquina via Telnet ou SSH.
✗
Etc!
Embedded
Labworks
DISPOSITIVOS NA CAMADA TTY
/dev/ttyS*
User space
/dev/ttyUSB*
/dev/modem
/dev/tty0..63
Line
Disciplines
TTY Core
Kernel space
Serial TTY driver
Hardware
USB/Serial
TTY driver
Modem
TTY driver
Virtual
Terminal
Input
Driver
Driver
Driver
Driver
Driver
Driver
Porta
RS232
Porta
RS485
Conversor
USB/Serial
Modem
PCMCIA
Monitor
Teclado
Embedded
Labworks
UART DRIVER
✗
✗
Portanto, para ser integrado ao kernel, um driver de porta serial
precisa fazer parte da camada TTY.
Levando em consideração os quatro componentes da camada TTY,
isso significa que:
✗
✗
✗
✗
Driver de baixo nível: precisa ser desenvolvido.
Driver TTY: já existe uma implementação do driver TTY para portas
seriais chamado serial core. O driver de baixo nível deverá se registrar
neste driver TTY.
Core TTY: é padrão, não precisa mexer.
Line discipline: também é padrão, e o comum é usar N_TTY para que
a porta serial funcione como um terminal.
Embedded
Labworks
UART DRIVER (cont.)
User space
/dev/ttyS*
tty_io.c
Kernel space
serial_core.c
UART driver
Hardware
UART
n_tty.c
Embedded
Labworks
IMPLEMENTANDO DRIVER UART
✗
✗
✗
Definir uma estrutura do tipo uart_driver que representará o
driver.
Definir uma estrutura do tipo uart_port para cada porta serial
presente no sistema.
Definir uma estrutura do tipo uart_ops com as operações que
podem ser realizadas na porta serial, e criar o link com a estrutura
uart_port.
Embedded
Labworks
IMPLEMENTANDO DRIVER UART (cont.)
✗
✗
Registrar a estrutura uart_driver na inicialização do driver com
uart_register_driver() e desregistrar esta estrutura na rotina
de limpeza do driver com uart_unregister_driver().
Registrar a estrutura uart_port com uart_add_one_port() e
desregistrar com uart_remove_one_port().
Embedded
Labworks
EXEMPLO DRIVER UART
/* drivers/tty/serial21285.c */
#include <linux/serial_core.h>
static struct uart_driver serial21285_reg = {
.owner = THIS_MODULE,
.driver_name = "ttyFB",
.dev_name = "ttyFB",
.major = SERIAL_21285_MAJOR,
.minor = SERIAL_21285_MINOR,
.nr = 1,
.cons = SERIAL_21285_CONSOLE,
};
Embedded
Labworks
EXEMPLO DRIVER UART (cont.)
/* UART operations. See documentation in
Documentation/serial/driver */
static struct uart_ops serial21285_ops = {
.tx_empty = serial21285_tx_empty,
.get_mctrl = serial21285_get_mctrl,
.set_mctrl = serial21285_set_mctrl,
.stop_tx = serial21285_stop_tx,
.start_tx = serial21285_start_tx,
.stop_rx = serial21285_stop_rx,
.enable_ms = serial21285_enable_ms,
.break_ctl = serial21285_break_ctl,
.startup = serial21285_startup,
.shutdown = serial21285_shutdown,
.set_termios = serial21285_set_termios,
.type = serial21285_type,
.release_port = serial21285_release_port,
.request_port = serial21285_request_port,
.config_port = serial21285_config_port,
.verify_port = serial21285_verify_port,
};
Embedded
Labworks
EXEMPLO DRIVER UART (cont.)
static struct uart_port serial21285_port = {
.mapbase = 0x42000160,
.iotype = UPIO_MEM,
.irq = 0,
.fifosize = 16,
.ops = &serial21285_ops,
.flags = UPF_BOOT_AUTOCONF,
};
Embedded
Labworks
EXEMPLO DRIVER UART (cont.)
static int __init serial21285_init(void)
{
[...]
ret = uart_register_driver(&serial21285_reg);
if (ret == 0)
uart_add_one_port(&serial21285_reg, &serial21285_port);
[..]
}
static void __exit serial21285_exit(void)
{
uart_remove_one_port(&serial21285_reg, &serial21285_port);
uart_unregister_driver(&serial21285_reg);
}
Embedded
Labworks
LABORATÓRIO
Integrando driver com a camada TTY
Embedded
Labworks
INFRAESTRUTURA DE BARRAMENTO
✗
✗
Os drivers dependem de uma infraestrutura de barramento que
possa identificar, enumerar e se comunicar com os dispositivos
conectados ao barramento.
A infraestrutura de barramento é composta por:
✗
✗
Um driver de barramento (bus driver), que implementa a API para
um driver conversar com determinado barramento.
Um driver adaptador (adapter driver), capaz de conversar
fisicamente com o dispositivo através de determinado barramento
(USB controllers, I2C adapters).
Embedded
Labworks
INFRAESTRUTURA DE BARRAMENTO (cont.)
User
space
Bibliotecas/aplicações
Interface de chamada de sistema
Kernel
space
Framework
Bus driver
Driver
Bus adapter
Infraestrutura de barramento
Hardware
Embedded
Labworks
INFRAESTRUTURA DE BARRAMENTO (cont.)
✗
✗
✗
Existe um driver para cada tipo de barramento (USB, SPI, I2C, PCI,
MMC, etc).
Podem existir um ou mais drivers adaptadores, um para cada
controlador de barramento existente no hardware.
Para usar determinado barramento, os drivers adaptadores
precisam se registrar no driver do barramento.
Embedded
Labworks
DRIVER DE BARRAMENTO
✗
O driver de barramento é responsável por:
✗
✗
✗
✗
✗
Registrar o barramento, também chamado de core infrastructure,
através da estrutura struct bus_type.
Permitir o registro de drivers adaptadores (USB controllers, I2C
adapters, etc).
Permitir o registro de drivers de dispositivo (USB devices, I2C
devices, etc).
Prover uma API tanto para os drivers de dispositivo quanto para os
drivers adaptadores.
Implementar as estruturas para a definição do dispositivo e do
driver, tipicamente xxx_driver e xxx_device.
Embedded
Labworks
EXEMPLO: BARRAMENTO I2C
✗
Driver de barramento (I2C core):
drivers/i2c/i2c­core.c
✗
Driver adaptador (I2C adapter para a linha i.MX da Freescale):
drivers/i2c/busses/i2c­imx.c
Embedded
Labworks
NA INICIALIZAÇÃO
✗
Na inicialização do kernel, o adaptador I2C se registra no core I2C.
Core I2C
i2c_add_adapter()
i2c-imx
Embedded
Labworks
EXEMPLO DE DRIVER
✗
Para ilustrar como os drivers são implementados, vamos estudar o
driver do acelerômetro MMA8450.
✗
✗
✗
Ele é um dispositivo conectado ao barramento I2C, portanto é um
driver I2C.
Ele é um dispositivo que gera eventos, portanto usa o framework de
input do kernel.
Fontes disponíveis em:
drivers/input/misc/mma8450.c
Embedded
Labworks
IMPLEMENTANDO O DRIVER
✗
Como este dispositivo esta conectado ao barramento I2C, ele deve
implementar um driver I2C. Para isso, é necessário:
✗
✗
✗
Definir uma estrutura do tipo struct i2c_driver.
Registrar o driver i2c com a função i2c_add_driver() na
inicialização do driver.
Desregistrar o driver com a função i2c_del_driver() na
finalização do driver.
Embedded
Labworks
ESTRUTURA I2C
static struct i2c_driver mma8450_driver = {
.driver = {
.name = "mma8450",
.owner = THIS_MODULE,
},
.suspend = mma8450_suspend,
.resume = mma8450_resume,
.probe = mma8450_probe,
.remove = __devexit_p(mma8450_remove),
[...]
};
Embedded
Labworks
ADICIONANDO E REMOVENDO
static int __init mma8450_init(void)
{
[...]
res = i2c_add_driver(&mma8450_driver);
[...]
}
static void __exit mma8450_exit(void)
{
[...]
i2c_del_driver(&mma8450_driver);
[...]
}
module_init(mma8450_init);
module_exit(mma8450_exit);
Embedded
Labworks
NA INICIALIZAÇÃO
✗
Na inicialização do kernel, o driver I2C se registra no core I2C.
Core I2C
i2c_add_driver()
mma8450
Embedded
Labworks
INSTANCIANDO O DRIVER
✗
A partir deste momento, o barramento I2C sabe que existe um
driver para tratar dispositivos do tipo mma8450.
✗
Mas para ser usado, o driver precisa ser instanciado.
✗
O que é instanciar o driver? É chamar sua função probe().
✗
Quando o driver é instanciado? Quando o sistema identifica um
dispositivo de hardware que pode ser tratado por aquele driver.
Embedded
Labworks
INSTANCIANDO O DRIVER (cont.)
✗
Como o driver pode ser instanciado?
✗
✗
✗
Dinamicamente se o barramento possuir suporte à enumeração de
dispositivos conectados a ele (ex: USB).
Estaticamente através de uma função disponibilizada pela
infraestrutura do barramento.
Estaticamente usando uma funcionalidade chamada de device tree.
Embedded
Labworks
INSTANCIANDO O DRIVER ESTATICAMENTE
/* arch/arm/mach­imx/mach­mx53_loco.c */
static struct i2c_board_info mx53loco_i2c_devices[] = {
{
I2C_BOARD_INFO("mma8450", 0x1C),
},
};
static void __init mx53_loco_board_init(void)
{
[...]
i2c_register_board_info(0, mx53loco_i2c_devices,
ARRAY_SIZE(mx53loco_i2c_devices));
[...]
}
Embedded
Labworks
DEVICE PROBE
Driver
mma8450
Definição do
dispositivo I2C
i2c_add_driver()
i2c_register_board_info()
Core I2C
mma8450_probe()
Driver
mma8450
Embedded
Labworks
O MÉTODO PROBE
✗
✗
O método probe() recebe como argumento uma estrutura
descrevendo o dispositivo de acordo com o barramento
(i2c_client, pci_dev, usb_interface, etc).
Esta função é responsável por:
✗
✗
Inicializar o dispositivo, mapear I/O em memória e registrar ISRs. A
infraestrutura de barramento normalmente provê mecanismos para
ler endereçamento de I/O, número de interrupções e outras
informações específicas do dispositivo.
Registrar o dispositivo no framework correto do kernel.
Embedded
Labworks
MMA8450 PROBE
static int __devinit mma8450_probe(
struct i2c_client *client,
const struct i2c_device_id *id)
{
[...]
result = i2c_smbus_read_byte_data(client,
MMA8450_WHO_AM_I);
[...]
result = input_register_polled_device(idev);
[...]
}
Embedded
Labworks
O MODELO É RECURSIVO!
ALSA
Network stack
Input framework
Input driver
I2C device driver
I2C core
Network driver
USB device driver
I2C adapter driver
USB device driver
USB core
ALSA driver
PCI device driver
USB Adapter driver
PCI device driver
PCI core
PCI Adapter
Embedded
Labworks
PLATFORM DEVICES
✗
✗
✗
✗
Alguns dispositivos podem não estar conectados em um
barramento, como por exemplo uma UART ou algum dispositivo
conectado à um GPIO.
E como prover a mesma solução sem uma infraestrutura de
barramento?
Criando a sua própria infraestrutura de barramento!
Esta implementação é realizada através da infraestrutura de
platform device e platform driver.
Embedded
Labworks
IMPLEMENTANDO PLATFORM DRIVERS
✗
✗
Um dispositivo de hardware é representado por um platform device
e o driver para tratar este dispositivo é representado por um
platform driver.
Para implementar um platform driver, é necessário:
✗
Definir uma estrutura do tipo struct platform_driver.
✗
Registrar
✗
Desregistrar
o
platform
driver
com
a
função
platform_driver_register() na inicialização do driver.
o
driver
com
a
função
platform_driver_unregister() na rotina de limpeza do
driver.
Embedded
Labworks
EXEMPLO PORTA SERIAL
/* drivers/tty/serial/imx.c */
#include <linux/platform_device.h>
static struct platform_driver serial_imx_driver = {
.probe = serial_imx_probe,
.remove = serial_imx_remove,
.driver = {
.name = "imx­uart",
.owner = THIS_MODULE,
},
[...]
};
Embedded
Labworks
EXEMPLO PORTA SERIAL (cont.)
/* drivers/tty/serial/imx.c */
static int __init imx_serial_init(void)
{
[...]
ret = platform_driver_register(&serial_imx_driver);
[...]
}
static void __exit imx_serial_exit(void)
{
[...]
platform_driver_unregister(&serial_imx_driver);
[...]
}
Embedded
Labworks
INSTANCIANDO PLATFORM DRIVER
✗
Como a infraestrutura de platform devices não possui um
mecanismo de hotplug, um platform driver pode ser instanciado de
duas formas:
✗
✗
Estaticamente através da criação e inicialização de uma estrutura do
tipo platform_device.
Estaticamente através da funcionalidade de device tree.
Embedded
Labworks
DEFININDO UM PLATFORM DEVICE
static struct platform_device imx_uart1_device = {
.name = "imx­uart",
.id = 0,
.num_resources = ARRAY_SIZE(imx_uart1_resources),
.resource = imx_uart1_resources,
.dev = {
.platform_data = &mxc_ports[0],
}
};
Embedded
Labworks
INSTANCIANDO UM PLATFORM DEVICE
static struct platform_device *devices[] __initdata = {
&cs89x0_device, &imx_uart1_device,
&imx_uart2_device,
};
static void __init mx1ads_init(void)
{
[...]
platform_add_devices(devices, ARRAY_SIZE(devices));
[...]
}
Embedded
Labworks
PLATFORM DEVICE PROBE
Platform Driver
imx-uart
Platform Device
UART 1
platform_driver_register()
platform_add_devices()
Platform device infrastructure
serial_imx_probe()
Platform Driver
imx-uart
Embedded
Labworks
USANDO RECURSOS
✗
✗
✗
Todo driver normalmente usa um ou mais recursos de hardware,
como portas de I/O, linhas de interrupção ou canais de DMA.
Estas informações podem ser representadas em uma estrutura do
tipo struct resource, e um vetor de estruturas deste tipo estão
associadas à um platform device.
Este tipo de mecanismo permite que determinado driver possa ser
instanciado para gerenciar múltiplos dispositivos, que usam
recursos de hardware diferentes, sem que uma linha de código seja
alterada.
Embedded
Labworks
USANDO RECURSOS (cont.)
static struct platform_device imx_uart1_device = {
.name = "imx­uart",
.id = 0,
.num_resources = ARRAY_SIZE(imx_uart1_resources),
.resource = imx_uart1_resources,
.dev = {
.platform_data = &mxc_ports[0],
}
};
Definição dos recursos
usados pelo dispositivo
de hardware
Embedded
Labworks
USANDO RECURSOS (cont.)
static struct resource mxc_uart_resources1[] = {
{
.start = UART1_BASE_ADDR,
.end = UART1_BASE_ADDR + 0x0B8,
.flags = IORESOURCE_MEM,
},
{
.start = MXC_INT_UART1,
.flags = IORESOURCE_IRQ,
},
};
Embedded
Labworks
USANDO RECURSOS (cont.)
static int serial_imx_probe(struct platform_device *pdev)
{
struct resource *res;
[...]
/* get and remap memory mapped I/O */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = ioremap(res­>start, PAGE_SIZE);
[...]
/* get ISR number */
sport­>rxirq = platform_get_irq(pdev, 0);
[...]
}
Embedded
Labworks
PLATFORM DATA
✗
✗
✗
Além dos recursos de hardware comuns que podem ser alocados
para um determinado dispositivo, muitos dispositivos requerem
informações específicas que não estão disponíveis em outros
dispositivos de hardware (ex: modos de vídeo de um display).
Estas informações podem ser passadas para um driver em uma
outra estrutura chamada de platform_data.
Esta estrutura nada mais é do que um ponteiro para void dentro
da estrutura platform_device.
Embedded
Labworks
USANDO RECURSOS (cont.)
static struct platform_device imx_uart1_device = {
.name = "imx­uart",
.id = 0,
.num_resources = ARRAY_SIZE(imx_uart1_resources),
.resource = imx_uart1_resources,
.dev = {
.platform_data = &mxc_ports[0],
}
};
Definição de informações
específicas do dispositivo
de hardware
Embedded
Labworks
PLATFORM DATA (cont.)
static uart_mxc_port mxc_ports[] = {
[0] = {
[...]
.ints_muxed = 1,
.mode = MODE_DCE,
.ir_mode = NO_IRDA,
.enabled = 1,
.cts_threshold = UART1_UCR4_CTSTL,
.dma_enabled = UART1_DMA_ENABLE,
.dma_rxbuf_size = UART1_DMA_RXBUFSIZE,
.rx_threshold = UART1_UFCR_RXTL,
.tx_threshold = UART1_UFCR_TXTL,
[...]
}
Embedded
Labworks
PLATFORM DATA (cont.)
static int serial_imx_probe(struct platform_device *pdev)
{
[...]
mxc_ports[id] = pdev­>dev.platform_data;
[...]
if (mxc_ports[id]­>enabled == 1) {
[...]
}
}
Embedded
Labworks
LABORATÓRIO
Implementando um platform driver
Embedded
Labworks
Linux Device Drivers
Frameworks
Embedded
Labworks
RELEMBRANDO...
User space
Bibliotecas/aplicações
Interface de chamada de sistema
Kernel space
Framework
Driver
Infraestrutura de barramento
Hardware
Embedded
Labworks
TTY
✗
✗
O que é? Subsistema para dispositivos seriais.
Quem usa? Porta serial RS232, porta serial RS485, conversor
USB/Serial, etc.
✗
Interface: /dev/tty*.
✗
Fontes: drivers/tty/.
✗
Documentação: Documentation/serial/.
Embedded
Labworks
INPUT
✗
O que é? Subsistema para dispositivos de entrada.
✗
Quem usa? Mouse, teclado, touch screen, acelerômetro, botão, etc.
✗
Interface: /dev/input/event*.
✗
Fontes: drivers/input/.
✗
Documentação: Documentation/input/.
Embedded
Labworks
FRAMEBUFFER
✗
✗
O que é? Subsistema de vídeo.
Quem usa? Controladores de vídeo em geral para conexão com
monitores e displays.
✗
Interface: /dev/fb* e /sys/class/graphics/fb*/.
✗
Fontes: drivers/video/.
✗
Documentação: Documentation/fb/.
Embedded
Labworks
ALSA
✗
O que é? Subsistema de som (Advanced Linux Sound Architecture).
✗
Quem usa? Controladores de áudio, placas de som, etc.
✗
Interface: /dev/snd/*.
✗
Fontes: sound/.
✗
Documentação: Documentation/sound/.
Embedded
Labworks
V4L2
✗
✗
O que é? Interface para dispositivos de captura de áudio e vídeo.
Quem usa? Webcam, sintonizador de TV, receptor de rádio, receptor
de TV digital, etc.
✗
Interface: /dev/dvb/* e /dev/v4l/*.
✗
Fontes: drivers/media/.
✗
Documentação: Documentation/video4linux/.
Embedded
Labworks
BLOCK LAYER
✗
O que é? Subsistema para dispositivos de armazenamento com
capacidade de acesso randômico.
✗
Quem usa? Discos rígido, CD/DVD, pendrive, etc.
✗
Interface: /dev/sd*, /dev/sr*, etc.
✗
Fontes: drivers/block/.
✗
Documentação: Documentation/block/.
Embedded
Labworks
MTD
✗
O que é? Subsistema para memórias flash.
✗
Quem usa? Memórias flash NAND e NOR.
✗
Interface: /dev/mtd* e /dev/mtdblock*.
✗
Fontes: drivers/mtd/.
✗
Documentação: Documentation/mtd/.
Embedded
Labworks
NETWORK
✗
O que é? Subsistema de rede TCP/IP.
✗
Quem usa? Placas de rede em geral.
✗
Interface: Socket.
✗
Fontes: net/ e drivers/net/.
✗
Documentação: Documentation/networking/.
Embedded
Labworks
BLUETOOTH
✗
O que é? Subsistema para dispositivos bluetooth.
✗
Quem usa? Dispositivos bluetooth em geral.
✗
Interface: /dev/rfcomm* para emulação serial, interface de rede
ppp*, /dev/input/event* para dispositivo de entrada, etc.
✗
Fontes: drivers/bluetooth/.
✗
Documentação: -
Embedded
Labworks
NFC
✗
O que é? Subsistema para dispositivos NFC.
✗
Quem usa? Dispositivos NFC em geral.
✗
Interface: Socket.
✗
Fontes: net/nfc/ e drivers/nfc/.
✗
Documentação: Documentation/nfc/.
Embedded
Labworks
IrDA
✗
O que é? Subsistema para comunicação via infravermelho.
✗
Quem usa? Dispositivos de comunicação infravermelho.
✗
Interface: /dev/rfcomm* para emulação serial, interface de rede
ppp*, etc.
✗
Fontes: drivers/net/irda/.
✗
Documentação: Documentation/networking/irda.txt .
Embedded
Labworks
HWMON
✗
✗
O que é? Subsistema para dispositivos de monitoramento do
hardware.
Quem usa? Sensores em geral (temperatura, corrente, tensão, etc),
ventoinha, etc.
✗
Interface: /sys/class/hwmon/hwmon*/.
✗
Fontes: drivers/hwmon/.
✗
Documentação: Documentation/hwmon/.
Embedded
Labworks
IIO (INDUSTRIAL I/O)
✗
✗
O que é? Subsistema (recente) para dispositivos conversores
analógico/digital.
Quem usa? Conversor A/D e D/A, acelerômetro, sensor de luz,
sensor de proximidade, compasso, giroscópio, magnetrômetro, etc.
✗
Interface: /sys/bus/iio/devices/*.
✗
Fontes: drivers/iio/.
✗
Documentação: drivers/staging/iio/Documentation/ .
Embedded
Labworks
WATCHDOG
✗
O que é? Subsistema para dispositivos watchdog.
✗
Quem usa? Qualquer chip com funcionalidade de watchdog.
✗
Interface: /dev/watchdog.
✗
Fontes: drivers/watchdog/.
✗
Documentação: Documentation/watchdog/.
Embedded
Labworks
RTC
✗
O que é? Subsistema para relógios de tempo real.
✗
Quem usa? RTCs em geral.
✗
Interface: /dev/rtc* e /sys/class/rtc/rtc*/.
✗
Fontes: drivers/rtc/.
✗
Documentação: Documentation/rtc.txt.
Embedded
Labworks
PWM
✗
O que é? Subsistema (recente) para dispositivos PWM.
✗
Quem usa? Qualquer chip com funcionalidade de PWM.
✗
Interface: /sys/class/pwm/*.
✗
Fontes: drivers/pwm/.
✗
Documentação: Documentation/pwm.txt.
Embedded
Labworks
PINCTRL (PIN CONTROL)
✗
✗
O que é? Subsistema para gerenciar pinos de I/O.
Quem usa? Toda e qualquer CPU ou SoC que possuir pinos de I/O
para serem gerenciados, como o MUX presente nos SoCs atuais.
✗
Interface: -
✗
Fontes: drivers/pinctrl/.
✗
Documentação: Documentation/pinctrl.txt.
Embedded
Labworks
GPIO
✗
✗
O que é? Subsistema para gerenciar GPIOs.
Quem usa? Toda e qualquer CPU ou SoC com GPIOs disponíveis,
expansor de I/O, shift register, etc.
✗
Interface: /sys/class/gpio/*.
✗
Fontes: drivers/gpio/.
✗
Documentação: Documentation/gpio.txt.
Embedded
Labworks
LEDS
✗
O que é? Subsistema para gerenciar leds.
✗
Quem usa? Todo e qualquer led disponível no hardware.
✗
Interface: /sys/class/leds/*.
✗
Fontes: drivers/leds/.
✗
Documentação: Documentation/leds/.
Embedded
Labworks
PARPORT
✗
O que é? Subsistema para portas paralelas.
✗
Quem usa? Qualquer dispositivo de porta paralela.
✗
Interface: /dev/lp*.
✗
Fontes: drivers/parport/.
✗
Documentação: Documentation/parport.txt.
Embedded
Labworks
CLOCK
✗
✗
O que é? Subsistema para gerenciar as fontes de clock do sistema.
Quem usa? Todo e qualquer device driver que dependa de um clock
para seu funcionamento (Ex: UART).
✗
Interface: -
✗
Fontes: drivers/clk/.
✗
Documentação: Documentation/clk.txt.
Embedded
Labworks
CPUFREQ
✗
O que é? Subsistema para gerenciamento de energia.
✗
Quem usa? Sistema (CPU, SoC, etc).
✗
Interface: /sys/devices/system/cpu/cpu*/cpufreq/ .
✗
Fontes: drivers/cpufreq/ ou arch/<arch>/*.
✗
Documentação: Documentation/cpu­freq/.
Embedded
Labworks
POWER MANAGEMENT
✗
O que é? Subsistema para gerenciar o estado de gerenciamento de
energia do sistema (standby, suspend-to-RAM, suspend-to-disk).
✗
Quem usa? Toda plataforma que deseje este tipo de suporte.
✗
Interface: /sys/power/.
✗
Fontes: drivers/base/power/.
✗
Documentação: Documentation/power/.
Embedded
Labworks
CPU IDLE
✗
O que é? Subsistema para gerenciar o modo idle da CPU.
✗
Quem usa? Qualquer CPU que possua um ou mais modos idle.
✗
Interface: /sys/devices/system/cpu/cpu*/cpuidle/ .
✗
Fontes: drivers/cpuidle/.
✗
Documentação: Documentation/cpuidle/.
Embedded
Labworks
POWER SUPPLY
✗
✗
O que é? Subsistema para fontes de energia.
Quem usa? Dispositivos de controle de bateria, fontes de
alimentação, etc.
✗
Interface: /sys/class/power_supply/*.
✗
Fontes: drivers/power/.
✗
Documentação: Documentation/power/power_supply_class.txt.
Embedded
Labworks
REGULATOR
✗
O que é? Subsistema para reguladores de corrente e tensão.
✗
Quem usa? Qualquer chip regulador de corrente e tensão.
✗
Interface: /sys/class/regulator/*.
✗
Fontes: drivers/regulator/.
✗
Documentação: Documentation/power/regulator/.
Embedded
Labworks
MFD
✗
O que é? Subsistema para chips multifuncionais.
✗
Quem usa? Qualquer chip com múltiplas funções.
✗
Interface: -
✗
Fontes: drivers/mfd/.
✗
Documentação: -
Embedded
Labworks
SEM FRAMEWORK?
✗
✗
✗
São raros os casos onde um dispositivo não se encaixa em nenhum
destes frameworks.
Mas estes casos ainda existem. Exemplos:
✗
Display de 7 segmentos.
✗
Display LCD 2x20.
✗
EEPROM.
Para estes casos, temos duas soluções:
✗
Criar uma interface específica no /sys.
✗
Criar um misc driver com interface no /dev.
Embedded
Labworks
LABORATÓRIO
Analisando a implementação de drivers
Embedded
Labworks
Linux Device Drivers
E agora?
Embedded
Labworks
FILOSOFIA LINUX (1)
"Linux is evolution, not intelligent design."
Linus Torvalds
Embedded
Labworks
FILOSOFIA LINUX (2)
"Talk is cheap. Show me the code."
Linus Torvalds
Embedded
Labworks
LEIA A DOCUMENTAÇÃO
✗
✗
O
kernel
tem
uma extensa documentação
Documentation/. Consulte sempre!
no
diretório
O kernel também possui um conjunto de documentos em
Documentation/DocBook, que podem ser compilados com um dos
comandos abaixo:
$ make pdfdocs
$ make htmldocs
Embedded
Labworks
LEIA A DOCUMENTAÇÃO (cont.)
✗
Alguns documentos interessantes:
✗
Documentation/HOWTO: contém instruções gerais sobre como se
tornar um desenvolvedor do kernel.
✗
Documentation/ManagementStyle :
descreve
gerenciamento do Linux, e um pouco de sua cultura.
✗
Documentation/stable_api_nonsense.txt : explica porque o
Linux não tem uma interface (API) estável.
o
estilo
de
Embedded
Labworks
PROCESSO DE DESENVOLVIMENTO
-next
Linus Torvalds
Andrew Morton
Mantenedor do
subsistema
Mantenedor do
driver ou arquivo
Desenvolvedor
Desenvolvedor
Mantenedor do
subsistema
Mantenedor do
driver ou arquivo
Desenvolvedor
Mantenedor do
driver ou arquivo
Desenvolvedor
Desenvolvedor
Desenvolvedor
Embedded
Labworks
PROCESSO DE DESENVOLVIMENTO (cont.)
✗
✗
✗
Boa documentação sobre o processo de desenvolvimento em
Documentation/development­process/ .
A maioria das árvores git dos subsistemas do kernel encontram-se
no link abaixo:
http://git.kernel.org/
Cada subsistema possui uma lista de discussão e um ou mais
mantenedores.
Embedded
Labworks
PROCESSO DE DESENVOLVIMENTO (cont.)
✗
✗
O e-mail do mantenedor, bem como a lista de discussão do
subsistema, estão disponíveis no arquivo MAINTAINERS no
diretório principal do kernel.
O script scripts/get_maintainer.pl pode te ajudar a
identificar o e-mail da lista de discussão e o nome e e-mail dos
mantenedores de determinado arquivo ou patch!
Embedded
Labworks
GET MAINTAINER
$ ./scripts/get_maintainer.pl ­f init/main.c Rusty Russell <[email protected]> (commit_signer:4/20=20%)
Jim Cromie <[email protected]> (commit_signer:3/20=15%)
Al Viro <[email protected]> (commit_signer:3/20=15%)
"H. Peter Anvin" <[email protected]> (commit_signer:3/20=15%)
Andrew Morton <akpm@linux­foundation.org> (commit_signer:2/20=10%)
linux­[email protected] (open list)
Embedded
Labworks
CONTRIBUINDO
✗
✗
✗
Todo o processo de contribuição acontece por e-mail através das
listas de discussão.
A principal lista de discussão é a LKML (Linux Kernel Mailing List):
http://lkml.org/
Mas muitos subsistemas possuem listas específicas.
http://vger.kernel.org/vger-lists.html
Embedded
Labworks
CONTRIBUINDO (cont.)
✗
✗
Para colaborar, é só preparar o patch e enviar para a lista de
discussão e para todos os mantenedores responsáveis pelo
subsistema.
O gerenciamento dos patches enviados é feito com a ferramenta
patchwork.
https://patchwork.kernel.org/
Embedded
Labworks
CONTRIBUINDO (cont.)
✗
Documentação sobre como submeter um patch:
Documentation/SubmittingPatches
✗
Checklist adicional para submissão de patches:
Documentation/SubmitChecklist
✗
Documentação específica sobre como submeter novos drivers:
Documentation/SubmittingDrivers
Embedded
Labworks
CONTRIBUINDO (cont.)
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
$ cd linux/
$ git branch smeagol
$ git checkout smeagol
# change Linux, and change the world!
$ scripts/checkpatch.pl ­f <changed_files>
$ git commit ­a
$ git format­patch master..smeagol
$ scripts/get_maintainer.pl <patch_file>
$ git send­email ­­to [email protected] ­­cc [email protected] <patch_file>
Embedded
Labworks
AGUARDANDO APROVAÇÃO
✗
✗
✗
Ao enviar um patch, você pode esperar por críticas, comentários,
pedidos para alterar o código ou pedidos para explicar
determinado ponto da alteração.
Você deve ser capaz de aceitas as críticas, analisar tecnicamente o
problema, alterar o código ou explicar claramente os motivos pelos
quais o código não foi alterado, e reenviar o patch.
Se não receber resposta, espere alguns dias e reenvie o patch.
Embedded
Labworks
AGUARDANDO APROVAÇÃO (cont.)
✗
✗
“Publicly making fun of people is half the fun of open source
programming. In fact, the real reason to eschew programming in
closed environments is that you can't embarrass people in public.”
Linus Torvalds.
É um processo que exige muita paciência e perseverança!
Embedded
Labworks
PROCURANDO AJUDA
✗
✗
✗
O website Kernel Newbies é um ponto de referência para buscar
ajuda sobre o funcionamento interno do Kernel.
http://kernelnewbies.org
Possui uma lista de discussão e um canal IRC onde são discutidos
qualquer tipo de assunto relacionado ao kernel, do mais básico ao
mais avançado.
Lembre-se de procurar nos arquivos da lista antes de postar
alguma pergunta!
Embedded
Labworks
REPORTANDO PROBLEMAS
✗
✗
✗
Um documento descrevendo como reportar bugs chamado
REPORTING­BUGS encontra-se no diretório principal do kernel.
O kernel também tem um bug tracker no link abaixo:
https://bugzilla.kernel.org
Você pode contribuir reportando ou corrigindo problemas!
Embedded
Labworks
PARA COMEÇAR
✗
✗
Kernel Janitors é um projeto do site Kernel Newbies para quem
quiser aprender a desenvolver para o kernel revisando o código,
fazendo limpezas, convertendo o uso de APIs e dando manutenção
em código antigo.
http://kernelnewbies.org/KernelJanitors
Linux Driver Project é um projeto criado para quem quiser ajudar no
desenvolvimento de drivers.
http://www.linuxdriverproject.org/
Embedded
Labworks
LINKS
✗
✗
✗
✗
Site do kernel Linux:
http://www.kernel.org
Linux kernel mailing list:
http://www.tux.org/lkml
Acompanhar as mudanças nas novas versões do kernel:
http://wiki.kernelnewbies.org/LinuxChanges
Notícias e novidades sobre o desenvolvimento do kernel:
http://lwn.net
Embedded
Labworks
LIVROS LINUX KERNEL
Linux Kernel in a Nutshell
Greg Kroah-Hartman
Linux Kernel Development
Robert Love
Embedded
Labworks
LIVROS LINUX DEVICE DRIVERS
Essential Linux Device Drivers
Sreekrishnan Venkateswaran
Linux Device Drivers
Jonathan Corbet & others
Embedded
Labworks
BECOMING A MASTER
✗
Leia muito código!
✗
Desenvolva!
✗
Leia mais código!
✗
Desenvolva!
✗
Leia muito mais código!
✗
Contribua com a comunidade!
OBRIGADO!
E-mail
Website
[email protected]
http://e-labworks.com
Embedded Labworks
Por Sergio Prado. São Paulo, Novembro de 2012
® Copyright Embedded Labworks 2004-2013. All rights reserved.

Documentos relacionados

Programação no kernel Linux

Programação no kernel Linux Perm ite a carga/ descarga sob dem anda

Leia mais