USB-MIDI Gadget improvements on Linux

Here at ROLI we make heavy usage of Linux and its audio stack (ALSA). And one of our problems was the MIDI Gadget driver. The Linux USB Gadget MIDI driver has been neglected for few years now. It had several bugs, memory leaks and not very clean code-base. Why didn’t anyone care? Well, one of the reasons is that MIDI is a low speed and low throughput protocol, and the current driver was delivering just enough. But that is about to change because ROLI and others have been working on the Multidimensional Polyphonic Expression (MPE) MIDI specification, which increases dramatically the number of MIDI messages per second and per USB request. Thus problems started to arise.

Overall there were several patches that we upstreamed, including performance and memory-footprint improvements by pre-allocating IN USB requests, state machine refactor and fixing an annoying bug that caused the MIDI Streaming endpoint to freeze. There were also critical memory leaks fixes in ChipIdea’s UDC driver.

It is important to note that the Linux community was very helpful and open as always. Our threads created some helpful discussions and motivated others to contribute and improve the MIDI driver too.

So, update your kernel to v4.5 and enjoy a much better MIDI experience for your device on Linux.

PS: v4.6 and v4.7 will also have other improvements that were not merged yet.

BlueZ fork on github for MIDI over BLE

Since there are some people reaching me and showing interest in helping with the implementation of the MIDI over BLE profile on Linux, I created a fork repository of BlueZ on github so others can checkout the branch midi and help it out.

The idea is just to share the MIDI profile driver with others so we can concentrate the development efforts, then send patches to the mainline BlueZ. Once it gets integrated into mainline, I will drop this repository.

As mentioned on previous blog post, the work is still undergoing and incomplete.

So, please, checkout the repository and the profile driver.

MIDI over Bluetooth Low Energy on Linux

Two months ago I made a request for comments on the linux-bluetooth and linux-alsa lists to propose the implementation of the MIDI over Bluetooth Low Energy profile on Linux using BlueZ and ALSA.

The main goals of the project are:

  1. To be transparent to applications, i.e., use ALSA Sequencer interface (ideally also use ALSA RawMidi too[1]);
  2. Support Peripheral and Central Bluetooth GAP roles.

[1] The problem with this is that it requires a kernel module to create a rawmidi device.

MMA is almost done with the actual MIDI over BLE spec. But since it is very close to Apple’s one, we can actually start implementing it.

On the mailing list, BlueZ developers suggested to implement it using the GATT D-Bus API to read and write the MIDI I/O characteristic for the sake of simplicity of initial version. Although this recommendation is valid, it will probably not fit the low latency requirements for this type of application.

That is why I already started the implementation as a native BlueZ plug-in (using the new shared api’s) instead. I’ve also sent two patches to fix a bug on that new API, which still requires some more work.

What I’ve Got So Far

I have a simple BlueZ profile plug-in that setups up an ALSA Sequencer client and port, reads the MIDI I/O characteristics notifications and writes the MIDI messages to the Sequencer port. I have tested the Seaboard RISE with a Linux synthesizer that supports ALSA Sequencer and it seems to work pretty fine.

This initial version still lacks timestamp support, write support and central role support.

Considerations

I’ve noticed that the connection with the Seaboard RISE was very unstable. Every time the RISE would send high throughput messages, the connection would drop. Then I’ve noticed that the RISE doesn’t send the Connection Update Command to the host and then BlueZ would not update the connection state to be in a lower latency. But that doesn’t happen on a iOS and Mac OS system, but why?

To understand what was going on, I connected to an iThing device as a peripheral exporting the MIDI Service and dumped the Connection Update Command data. I noticed that the Mac OS forced the connection parameters (instead of listening to the peripheral to suggest it) as follows:

  • Minimum Connection Interval: 6
  • Maximum Connection Interval: 12
  • Connection Latency: 0
  • Supervision Timeout: 200

As BlueZ supports the setting up connection parameters, I just changed the info file and reconnected. That caused my connection to be much more stable, but still not perfect. Eventually the connection still drops. So, that is something I still need to figure out. Any suggestions on it will be helpful.

I want to finish the timestamp and write support before releasing it. The central role can be implemented after the release, since I think the most exciting feature right now is to support BLE MIDI devices.

If you are interested in contributing, send me an email, I can share what I have it so far.

Introdução a módulos no Linux Kernel

Este post é uma continuação do artigo anterior: Introdução ao Linux Kernel.

Em um sistema modular, módulo é um componente que tem uma funcionalidade e é desacoplado do sistema como um todo. Apesar de ser desacoplado, o módulos podem ser integrados ao sistema, interagirem um com os outros e criarem configurações específicas e distintas de acordo com quais módulos estão sendo usados.

Quem é programador entende de forma intuitiva o conceito de módulos, ou plugins, add-ons etc. E no Linux Kernel módulos são exatamente o que você espera de um módulo.

  • São desacoplados do Kernel em funcionalidade, porém são sempre específicos a um Kernel. Se a versão do Kernel muda, todos os módulos precisam ser recompilados para este Kernel.
  • Performam uma função específica;
  • Podem trabalhar em conjunto, criando assim dependência entre eles;
  • Podem ser inseridos/removidos no/do sistema em tempo de execução.

O Linux suporta duas formas de compilar seu módulo: embutido (built-in) ou módulo (module). A única diferença entre os dois para o usuário é que no modo built-in o módulo é compilado e embutido dentro da vmlinux (imagem do Kernel), enquanto no modo module o módulo será compilado como um Kernel Object (.ko) e poderá ser inserido em tempo de execução com a ferramenta insmod ou modprobe , que checa aliases (definidas no arquivo /etc/modprobe.conf e /etc/modprobe.d/*.conf ) do módulo e suas dependências (definidas no arquivo /lib/modules/`uname -r`/modules.dep  gerado pelo depmod ) e então executa o insmod  para cada módulo. Você pode confirmar esse processo com o comando modprobe -n -v module.

Existem outras importantes características do código no modo module:

  1. É necessário que a configuração CONFIG_MODVERSIONS do Kernel esteja habilitada (comumente habilitada) para que seja possível inserir módulos em tempo de execução;
  2. É possível compilar módulos como Kernel Objects fora da árvore de código fonte original do Kernel. Ou seja, módulos podem ter suas próprias arvores de código e ser compiladas para um Kernel específico apenas apontando para a árvore que geral esse Kernel. Isto é chamado de out-of-tree kernel module;
  3. Módulos como Kernel Objects precisam de um root filesystem para serem executados, já que o módulo é inserido no Kernel pela ferramenta modprobe à partir do caminho /lib/modules/`uname -r`/kernel/* . Isso é meio que óbvio, já que o Kernel por si só não serve de nada.

Mão na massa: escrevendo um módulo

O Linux tem uma filosofia de nunca quebrar o user-space, entretanto dentro do Kernel a API interna é quebrada com uma frequência até que grande. Porém a API para módulos é relativamente bem estável e simples, o que torna fácil de mantê-la.

Basicamente precisamos criar um arquivo de código fonte em C e modificar dois arquivos, Kconfig e Makefile, exigidos pelo KBuild.

OBS: Todo nosso código será escrito em inglês, já que é a língua franca no mundo do software.

Crie uma pasta dentro de drivers chamada hello (drivers/hello) e o arquivo de código fonte em drivers/hello/hello-world.c (lembrando que nossa raíz do Linux é em ~/devel/linux):

Este é o módulo clássico que todo mundo começando no Linux escreve. É bem simples de entender, certo? A função hello_init()  é chamada qual o módulo é carregado, e a função hello_exit()  é chamada quando o módulo é removido.

A função helo_init()  retorna um int, isso é uma pratica comum no Linux Kernel e em API’s POSIX no geral. O int representa um erro. O valor 0 significa que a função rodou sem dar nenhum erro, e um valor negativo representa um erro. Normalmente erros são definidos nos cabeçalhos errno-base.h e errno.h.

Isso tudo é simples, mas tem uns poréns.

  1. module_init()  e module_exit()  são macros helpers que facilitam a declaração das funções em diferentes modos de compilação, built-in ou module. Lembre-se que no Kernel, não existe um sistema operacional e uma biblioteca padrão para facilitar sua vida, tudo precisa ser feito à mão.
  2. A macro __init causa a função hello_init() ser descartada depois de der chamada pelo kernel no caso de ser built-in. Porém não tem efeito no caso de ser module. O mesmo serve para variáveis, que usam __initdata ou __initconst.
  3. A macro __exit  causa a omissão da função no Kernel quando ela é compilada como built-in. Fazendo com que a função hello_exit()  nunca seja chamada.

Essas macros são importantes porque causam o Kernel limpar memória quando termina o boot (já viram uma mensagem do tipo “Freeing unused kernel memory: 280K (80693000 – 806d9000)”?) Também simplificam o código possibilitando assim que o arquivo fonte seja compilado como built-in ou module de forma simples.

Perguntinha teste: Por que o __init  não faz sentido quando a função é compilado como module?

O MODULE_LICENSE("GPL v2")  é necessário se não o Kernel vai chiar quando for carregar o módulo via modprobe. O módulo será marcado como não GPL e não terá acesso a API internas exportadas como GPL. Além de ser o óbvio a se fazer. :)

Agora modifique os arquivos Kconfig e Makefile do diretório drivers (drivers/Kconfig e drivers/Makefile) adicionando nosso diretório de desenvolvimento.

drivers/Kconfig

Certifique-se de que essa modificação está foi inserida antes do endmenu .

drivers/Makefile

Crie um Kconfig no diretório de seu módulo (drivers/hello/Kconfig) com o seguinte:

Repare que o tipo da configuração é tristate (ou de três estados). Isso significa que ela pode ser selecionada para não ser configurada, para ser configurada como built-in ou ser configurada como module. Para saber por mais tipos suportados pelo KBuild, leia a documentação e procure por “Menu attributes”.

Também, repare na sintaxe. O KBuild exige esse tipo de sintaxe que usa tabulações e espaços para poder fazer o parse correto das configurações.

E por último, apenas crie um arquivo Makefile dentro do diretório do seu módulo (drivers/hello/Makefile) com o seguinte:

Bom, acredito que essas duas últimas modificações são auto-explanatórias, não?

Disponibilizei o 0001-blog-hello-world.c.patch para quem quiser aplicar na sua árvore do Linux. O commit foi em cima da tag v4.4-rc4. Use o comando git am  para aplicar.

Como executar um módulo

Agora na raíz do seu Linux, execute o comando (considerando que você já selecionou um defconfig):

Habilite o nosso Hello World módulo em:

Repare que o KBuild irá habilitar 3 possíveis configurações: excluded, built-in ou module. Bem simples.

Executando um módulo compilado como built-in

Habilite nosso módulo como built-in no menuconfig e recompile o Kernel.

Repare que nosso arquivo foi compilado junto com a imagem do Linux:

Agora rode nosso Kernel fresquinho no QEMU novamente com:

E repare como nosso módulo é carregado procurando pela linha “################# Hello, World!“. Bem tranqüilo.

Executando um módulo compilado como module

Habilite nosso módulo como module no menuconfig e depois recompile o Kernel e compile os módulos:

Pergunta para o atento: Por que foi preciso recompilar o Kernel?

Repare que nosso módulo agora foi compilado como module.

Após ser compilado, o binário passa por um passo chamado MODPOST  no qual é feita a verificação dos símbolos externos do Kernel, presentes no arquivo Module.symvers dentro do diretório de build. Além disso, se necessário, o arquivo Module.symvers é modificado por ser adicionado as funções exportadas pelo módulo compilado.

Construindo Uma Pequena Distribuição

É importante lembrar que o foco dos artigos é sobre o Linux Kernel. Mas como o Kernel em si não faz nada de interessante (o objetivo de um Kernel é servir o user-space), precisamos rodar uma distribuição (user-space) para tirar proveito do Kernel, por isso esta etapa é necessária e será usada nos futuros artigos.

Chegou a hora mais temida, pra ser sincero. 😯 Vamos precisar de um rootfs e para isso vamos construir uma distribuição bem pequena usando o BusyBox apenas! Também vamos usar o suporte a NFS no Linux para podermos montar o rootfs via NFS e assim ter uma distribuição para fácil desenvolvimento onde podemos atualizar nosso rootfs no host (seu computador) e automaticamente refletir o mesmo rootfs no target (máquina virtual no QEMU).

Para quem não sabe o que é BusyBox eu recomendo ler o site oficial. Mas por cima, é um único utilitário que implementa boa parte dos utilitários necessários para rodar um Sistema Operacional Unix em user-space.

Primeiro passo é baixar a última versão do código fonte do busybox no site oficial, compilar e instalar. Para isso vamos simplificar os comandos e se houver qualquer dúvida, poste nos comentários.

Selecione o modo de linkagem como estática. Isso garante que as dependências (i.e., glibc) do busybox não precisem estar instaladas no sistema target (alvo).

Agora selecione a seguinte opção para instalarmos o BusyBox no diretório /rootfs-bb do seu sistema. Na realidade você pode instalar onde quiser, só que eu optei por usar esse diretório.

Saia com ESQ  e não se esqueça de salvar.

Agora compile e instale (no meu caso precisa de sudo, já que estou instalando no /rootfs-bb).

Repare que boa parte do rootfs está pronta no /rootfs-bb, porém ainda faltam alguns toques finais. Eu não vou explicar tudo, fica a critério do leitor se informar ou perguntar.

É isso aí, nossa distribuição Linux minimalista está prontinha! 😀

O último detalhe é configurar seu servidor NFS local. Isso eu não vou explicar pois existem milhões de tutoriais por aí na internet em como o fazer. Mas é necessário que você configure seu /etc/exportfs da seguinte forma:

Agora é o momento de rufarem os tambores pois vamos executar nossa máquina virtual com um monte de parâmetros que antes não tínhamos usado. E isso significa que algo sempre dá errado. 👿  Mas não se desespere, tente se certificar de todos passos e qualquer coisa estamos aí nos comentários!

ANTES DE EXECUTAR: Leia os comentários abaixo!

Eu recomendo vocês executarem o QEMU num outro terminal já que vamos continuar usando o terminal na árvore do Linux.

Bom, vamos considerar alguns parâmetros antes de carregar o módulo em nosso Kernel. O -m 128M  é a quantidade de memória RAM alocada para a máquina virtual. O -append  são parâmetros passados para o Linux, e para isso leia a documentação. O importante lá é entender que vamos usar o rootfs to tipo NFS pelo parâmetro root=/dev/nfs e configuramos o NFS com o argumento nfsroot=10.0.2.2:/rootfs-bb  onde o IP é o IP da máquina host que o QEMU roteia e o caminho na máquina host do rootfs.

Também é importante notar que estamos avisando o Linux para carregar o programa /sbin/init como sendo o programa init (ou PID 1). Este programa é responsável por carregar todos os outros programas no sistemas. Na verdade, todos os outros programas em user-space são filhos do init (criados através da system call fork()). Em sistemas atuais o programa init é muitas vezes o SystemD, porém em muitos sistemas embarcados, e o nosso, ainda usa-se uma implementação do SysV Init que é bem mais antiga (1983) porém simples, poderosa e bem elegante.

Agora sim, pode executar o QEMU! 😎

Se você viu esta mensagem de boas vindas, então fique tranquilo que o mais chato e mais difícil já passou! 😉

Agora sim vamos instalar nosso módulo no rootfs e carregá-lo:

No host (~/devel/linux)

Como citado no início do artigo, repare que a ferramenta do Linux depmod  foi chamada para gerar o arquivo de dependências dos módulos no target.

O arquivo gerado é o modules.dep no caminho $INSTALL_MOD_PATH/lib/modules/<kernel-version>/modules.dep. O depmod consegue linkar dependências de módulos lendo as funções que o módulo usa e e exporta (para dependências de outros módulos) e comparando com o arquivo System.map (que é uma tabela de símbolos do Kernel) que fica no diretório output do Linux (~/devel/linux/build/System.map). Por sua vez, o modprobe  pode carregar as dependências corretas do módulo em tempo de execução. Fantástico! 😛

Agora no target (QEMU)

Viram as mensagens?

Significa que seu módulo foi inserido no e removido do Kernel com sucesso! Parabéns!

Repare que a mensagem impressa foi direta do Kernel, já que contém o timestamp de quanto tempo o Kernel está rodando.

Eu recomendo você brincar um pouco com seu módulo, tentando mudar o valor de retorno da função hello_init()  entre outras coisas.

Espero que este artigo não tenha sido longo demais ou muito cansativo. Eu tentei não explicar assuntos não muito relevantes, como NFS e outras coisas. Se quiser saber mais ou estiver em dúvidas pesquise a respeito ou deixe um comentário. Mas eu quero deixar claro aqui que o objetivo destes artigos é falar sobre o Linux Kernel e não distribuições Linux. Por favor, caso tenha críticas e sugestões, fique à vontade em seus comentários.


Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts and no Back-Cover Texts.

Introdução ao Linux Kernel

Próximo artigo da série: Introdução a módulos no Linux Kernel

Devido a escassez de conteúdo em português sobre o Linux Kernel eu resolvi começar uma série de artigos introdutórios sobre o Linux e programação para o mesmo. Como o Linux Kernel é um projeto bem grande, acaba sendo um pouco difícil e intimidador para novos programadores a se achegarem, já que o conteúdo dos artigos na internet são bem técnicos e em inglês.

O objetivo desta série é de servir como guia e introdução para novatos começarem a aprender sobre o Linux Kernel e, quem sabe assim, procurar mais a respeito e contribuir. 😉

Todos são bem-vindos aos estudos. Porém eu recomendo as seguintes habilidades para o maior proveito:

  • Esteja rodando alguma distribuição Linux;
  • Conhecimento intermediário em ciência da computação;
  • Conhecimento avançado (ou corajoso o suficiente) em C;
    • Eu digo isso porque quem não tem experiência com C irá se assustar com algumas coisas feitas no Kernel, como a implementação da estrutura de dados em pilha FIFO. 😎
  • Conhecimento avançado em inglês.
    • Você irá perceber que eu uso diversos links para mais informações em inglês.

O Que é o Linux?

O Linux é um Núcleo do Sistema Operacional (ou Operating System Kernel) do típo monolítico. “Apenas” isso e ponto. O sistema que você usa em seu Desktop, como Fedora, Ubuntu e outros são distribuições de um Sistema Operacional composto pelo Linux Kernel e muitos outros componentes do Sistema Operacional GNU, conseqüentemente o nome GNU/Linux. Outra distribuição que usa o Linux Kernel é o Android. E existem muitas e muitas outras. O importante entender é que o Kernel em si pode ser usado em qualquer Sistema Operacional, já que é apenas um  dos componentes (muito importante, por sinal).

Um ponto muito importante é que o Linux é licenciado sob a licença GPLv2. Isso garante a liberdade do Kernel e a do usuário, já que a licença obriga a que modificações sejam publicadas (idealmente reintegradas ao mainline) sob a mesma licença.

Como dito anteriormente, o Linux é monolítico. De forma simplificada, isto significa que o Kernel é executado num espaço chamado kernel-space (também chamado supervisor mode ou ring 0 da CPU) onde todo o código tem o maior nível de privilégio e acesso. Todo o seu código está em um bloco linear de memória e o acesso da API interna é sempre visível por todos os componentes internos. Isso tem suas vantagens e desvantagens. A principal vantagem é rapidez, já que não existe overhead entre comunicações de drivers de periféricos e outros sub sistemas do Linux. A grande desvantagem é a fragilidade do Kernel como um todo, já que um driver mal escrito pode matar o sistema inteiro (o temido Kernel Panic).

É interessante notar que o Linux suporta uma funcionalidade muito importante herdada dos Kernels do tipo microkernel: o suporte a módulos. Com o Linux é possível adicionar e remover módulos em tempo de execução. Será que isso o torna um sistema híbrido (meio monolítico e meio microkernel)? Não! Pois uma vez que o módulo é é adicionado ao Kernel, ele é incorporado da mesma forma como se ele fosse compilado diretamente no sistema. Ou seja, ele continua um sistema monolítico.

No próximo artigo escreveremos nosso primeiro módulo.

Como Obter o Linux

O código fonte do Linux é mantido num repositório git pelo Linus Torvalds. O repositório dele é o central (também chamado de mainline) do Linux, já que ele que realiza os ‘releases’ oficiais do Linux.

Você pode baixar os códigos fontes empacotados em .tar.xz no site kernel.org para cada versão do Linux, ou baixar o repositório como um todo, recomendado para quem quer desenvolver.

Vamos então baixar o repositório git do Linus no diretório ~/devel/linux, para isso execute o seguinte comando:

Pode ir tomar um café e assistir um filme porque vai demorar. O repositório atual para o v4.4-rc2 do Linus tem 2,5GB de tamanho depois de desempacotado, porém o download do repositório deve ser ~1GB. 😯

Mãos na massa

Agora vamos compilar e rodar um Kernel bem fresquinho.

Para facilidade de desenvolvimento, vamos usar o QEMU para rodar nosso Kernel em sua arquitetura nativa do seu computador. Muito provavelmente todos lendo este artigo estão rodando AMD64 (ou x86_64).

Agora no seu diretório de desenvolvimento, vamos compilar o Kernel usando o seu build system, KBuild. Para isso precisamos gerar um arquivo .config que irá conter todas as configurações do nosso Kernel. Essas configurações irão habilitar ou não funcionalidades do Kernel por dizer ao KBuild quais arquivos a compilar e quais -D MACROS devem ser passadas pro compilador (GCC) ao pre-processar os códigos fonte. O Linux já vem com diversas configurações para certas arquiteturas e máquinas pré-definidos, vamos usá-la por simplicidade.

Para gerar o .config para a arquitetura AMD64 execute:

O parâmetro ‘O‘ significa output. Eu gosto de usar o diretório ‘build‘ como a saída dos arquivos gerados pelo build system, assim deixando sua árvore de código fonte limpa.

Agora precisamos apenas compilar o Kernel com o comando (vai levar uns 3-5 minutos, dependendo da sua máquina, ou seja, dá tempo para ir ao banheiro tirar aquela água do joelho):

bzImage é a imagem do Linux (vmlinux) só que compactada usando o bzip2.  O parâmetro ‘-j4‘ é padrão do make . O número, neste caso 4, é o número de operações em paralelo que o make irá executar. O número ideal seria o mesmo número de CPUs disponíveis. Execute o commando  nproc  para retornar esse número.

Agora baixe o QEMU em seu sistema. Provavelmente sua distribuição já tem empacotado alguma versão recente.

No meu Fedora 23 eu precisei apenas rodar:

Para rodar o seu Kernel fresco, execute dentro do diretório que você baixou o Linux:

Se você viu um tipo similar de Kernel Panic, então tudo funcionou como deveria.

O Linux foi executado na máquina virtual e tentou ‘montar’ o root file system porém não conseguiu, gerando assim o erro. Parabéns! Você acabou de rodar a versão de desenvolvimento do Linux.

Ficamos por aqui no momento. No próximo artigo da série, iremos escrever nosso primeiro módulo do Kernel e rodar no QEMU. Por favor, caso tenha críticas e sugestões, fique à vontade em seus comentários.


Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts and no Back-Cover Texts.