O problema, em três sintomas
Eu precisava ligar mais de três monitores no notebook, e descobri uma coisa frustrante: quase nenhum adaptador USB de vídeo tem suporte real para Linux. Windows e Mac, sempre. Linux, quase nunca. Foram três dias de pesquisa até achar um que prometia funcionar. Comprei aliviado. E aí fui instalar o driver oficial.
O hardware é um adaptador USB de vídeo WAVLINK, modelo WL-UG7602HC, baseado no chip Silicon Motion SM768. A máquina é um notebook com Ubuntu 24.04, GNOME em Wayland e GPU AMD. O fabricante distribui um driver oficial para Linux, e com ele o que se obtém é isto:
- O instalador oficial aborta. Termina com
Failed to install evdi ... to the kernelebad exit status: 2, desfaz o que tinha feito e nada fica instalado. - Se o instalador passa, o desktop sobe numa tela cinza congelada. Só se entra pelo modo de recuperação.
- Ao conectar o monitor USB, ele acende, pisca o logotipo e apaga. O desktop nunca aparece nele.
Eu poderia ter devolvido o produto e desistido. Seria o mais fácil. Resolvi entender o problema, e a virada de chave veio de uma constatação: no fundo, todos esses adaptadores funcionam da mesma maneira, criando telas virtuais por uma camada aberta, licenciada em GPL. Se o problema estivesse ali, dava para corrigir.
Três sintomas diferentes, e a tentação inicial é tratar como um único "o driver não funciona". Não é. São três causas distintas, em momentos distintos: uma no momento de instalar, duas depois. E a descoberta que organiza o projeto inteiro é que as três estão na camada aberta, nenhuma no firmware nem no daemon fechado do fabricante. Isso é o que torna o problema resolvível sem engenharia reversa, e é a primeira coisa que vale aprender aqui: antes de atacar, mapear quais camadas você tem o direito de mudar.
O repositório com a correção completa está em wavlink_driver_ubuntu, com documentação em inglês e em português.
A arquitetura que o fabricante monta
Para entender as causas é preciso saber como um adaptador desses funciona no Linux. O chip não é uma GPU; ele é um dispositivo USB que recebe quadros comprimidos e os exibe. Para que o compositor de janelas enxergue isso como um monitor, usa-se o EVDI (Extensible Virtual Display Interface), um projeto de código aberto da DisplayLink com duas partes: um módulo de kernel, que cria placas de vídeo virtuais, e uma biblioteca em espaço de usuário, a libevdi, que entrega os quadros dessas placas virtuais para quem quiser. O driver do fabricante é um daemon fechado, o SMIUSBDisplayManager, que usa a libevdi para pegar os quadros e os manda pelo USB para o chip.
A pilha, portanto, é: compositor, módulo evdi no kernel, libevdi, daemon fechado, USB, chip. O fabricante empacota tudo num instalador .run, inclusive uma cópia do código-fonte do EVDI, que é compilada na máquina do usuário via DKMS. E é nessa cópia que tudo começa a dar errado.
Causa zero: o instalador compila um EVDI velho demais
O instalador oficial embute o EVDI 1.14.7, de novembro de 2024. O Ubuntu 24.04 atualizado roda kernels da série 6.8 em diante (a máquina de teste estava no 6.17), e a API interna de DRM do kernel mudou nesse intervalo. A compilação do módulo falha, o DKMS devolve o bad exit status: 2, e o instalador, que é bem comportado nesse ponto, faz rollback. O resultado é um sistema limpo e um driver que não existe.
A correção é trocar a cópia embutida pelo EVDI 1.15.0, que compila nos kernels atuais. O script prepare-vendor-driver.sh faz isso sem rodar o instalador do fabricante: extrai o .run com a opção que impede a execução, substitui o evdi.tar.gz interno, e escreve uma cópia corrigida ao lado da original. O usuário instala a partir da cópia. O pacote original fica intocado.
A versão inicial do script baixava o EVDI 1.15.0 do GitHub. Isso foi trocado por um tarball empacotado no próprio repositório, e a razão é prática: quem está rodando esse script está, por definição, com um monitor que não funciona, às vezes numa máquina em modo de recuperação, nem sempre com rede. Um passo de reparo que depende de rede falha exatamente quando é mais necessário. O único arquivo que o usuário precisa baixar é o driver fechado, que não pode ser redistribuído.
Há um ponto de Secure Boot que costuma assustar e aqui não exige nada: o DKMS assina o módulo com a chave MOK que o Ubuntu já tem registrada na máquina, a mesma que usa para o VirtualBox e outros módulos. Não se cria nem se registra chave nova.
Causa um: um strlen(NULL) num wrapper descontinuado
Com o driver instalado, conectar o monitor matava o daemon do fabricante com SIGSEGV. Toda vez. A investigação levou a uma função da libevdi marcada como descontinuada, evdi_open_attached_to, que o daemon fechado ainda chama passando NULL como argumento para pedir um dispositivo genérico. Na versão 1.15 da biblioteca, esse wrapper virou uma linha que chama a versão nova, evdi_open_attached_to_fixed, passando o comprimento da string. E para calcular o comprimento, chama strlen no ponteiro. Com NULL, isso é um acesso ao endereço zero, e o processo morre antes de a função nova, que trata NULL corretamente, receber o ponteiro.
A correção é uma linha:
// deprecated, use evdi_open_attached_to_fixed
evdi_handle evdi_open_attached_to(const char *sysfs_parent_device)
{
return evdi_open_attached_to_fixed(sysfs_parent_device,
sysfs_parent_device ? strlen(sysfs_parent_device) : 0);
}
O que eu acho instrutivo nesse bug é a combinação que o produziu. Nenhuma das duas partes está errada sozinha. A biblioteca aberta avançou e refatorou um wrapper antigo assumindo que ninguém mais passaria NULL. O daemon fechado continuou usando a chamada antiga do jeito antigo, porque ninguém o recompilou. O ponto de encontro entre um software que evolui e um que ficou parado é onde o segfault mora. E a correção precisa ir na parte que evolui, porque é a única que se pode compilar.
O install.sh faz exatamente isso: extrai o código-fonte do EVDI que o próprio fabricante empacotou, aplica o patch, compila a libevdi, guarda a original como libevdi.so.orig, e instala a corrigida no lugar. O daemon fechado continua idêntico. Ele só passa a chamar uma biblioteca que não morre com o argumento que ele sempre mandou.
Causa dois: a tela cinza é uma corrida de GPUs
O terceiro sintoma é o mais assustador, porque impede o uso do computador, e tem a causa mais sutil. O instalador do fabricante cria /etc/modules-load.d/evdi.conf, que força o carregamento do módulo evdi em todo boot, com quatro placas virtuais. Essas quatro placas aparecem para o sistema durante a inicialização, no mesmo instante em que o driver da GPU real, o amdgpu, está subindo. O compositor do GNOME em Wayland, o mutter, precisa escolher uma GPU primária nessa confusão, e às vezes escolhe errado: pega o framebuffer de boot do EFI, o simpledrm, tenta renderizar nele, falha com gbm_surface_lock_front_buffer failed, e o que aparece na tela é cinza.
A pista era que o problema era intermitente e dependia do tempo de boot: condição de corrida clássica. A correção não é um patch; é uma decisão de projeto: o módulo não precisa existir no boot. Ele só é útil quando há um monitor USB conectado. Então o arquivo de carregamento forçado é removido (movido para dentro da pasta do fabricante, para poder ser restaurado), e o serviço do daemon passa a ser iniciado por uma regra udev no momento em que o adaptador é conectado. O evdi carrega sob demanda, depois que o compositor já escolheu a GPU certa. Desconectou, o serviço para. O boot fica limpo porque não há nada para disputar.
Coisas que só servem em certas condições não devem estar presentes fora delas. Parece óbvio escrito assim, e é a regra que o instalador do fabricante viola ao carregar o módulo sempre. A versão sob demanda não é só mais correta; ela elimina uma classe inteira de problemas, porque um módulo que não está carregado não participa de corrida nenhuma.
Quatro placas virtuais: a parte em que foi preciso dizer "o ganho é pequeno"
Depois das três correções, sobrou uma pergunta: por que quatro placas virtuais? O módulo evdi pré-cria um pool de dispositivos ao carregar, controlado pela opção initial_device_count. O padrão do projeto EVDI é zero. Os quatro vêm do fabricante. E a quantidade que você realmente precisa é uma por saída física do adaptador: um monitor ligado direto no HDMI do notebook não passa pelo EVDI e não consome nenhuma.
O repositório ganhou um script para ajustar isso, e a parte que eu mais prezo na documentação dele é a honestidade sobre o tamanho do ganho. Cada placa ociosa custa kilobytes, não megabytes, porque nenhum framebuffer é alocado até um monitor conectar de fato. Baixar de quatro para duas não vai aparecer no gráfico de memória. Os benefícios reais são outros: uma lista de monitores que significa o que diz, sem saídas "desconectadas" fantasmas que o X11 e as Configurações enumeram; menos nós DRM para o compositor errar na escolha da GPU primária, que é a classe de problema que motivou o projeto; e um padrão que reflete o hardware em vez de um chute do fabricante.
E por que o fabricante escolheu quatro? O próprio EVDI documenta a opção como um workaround para o X.Org, que monta a lista de GPUs uma vez na inicialização e pode travar se um dispositivo aparece depois. Pré-criar um pool evita isso. No Wayland a restrição não existe. É um exemplo limpo de configuração que fazia sentido num contexto e foi copiada para outro sem que ninguém revisitasse o motivo.
Um detalhe que torna o ajuste seguro de experimentar: se o pool for pequeno demais, a libevdi cria uma placa sob demanda escrevendo em /sys/devices/evdi/add. Errar para baixo não deixa ninguém sem tela.
A parte mais trabalhosa: fazer tudo ter volta
Olhando as três correções, o código que resolve o problema é pequeno: uma linha de C, um arquivo movido, um tarball trocado. O que ocupou a maior parte do trabalho foi outra coisa: garantir que cada passo possa ser testado sem risco e desfeito sem rastro. Quem roda esses scripts está num sistema que já quebrou uma vez. A última coisa de que precisa é de um script que quebra de outro jeito.
| Comando | O que faz | Precisa de root |
|---|---|---|
diagnose.sh | Relata distribuição, GPU, adaptador, dependências e estado da correção. Só lê. | Não |
install.sh --build-only | Compila a biblioteca corrigida num diretório temporário e confere. Não instala nada. | Não |
install.sh --dry-run | Imprime cada ação que faria. Não muda nada. | Não |
install.sh | Aplica a correção, com backup da biblioteca e da configuração originais. | Sim |
uninstall.sh | Restaura a biblioteca e a configuração do fabricante. | Sim |
Três camadas de verificação antes de qualquer mudança, backups feitos uma única vez (um segundo install.sh não sobrescreve o .orig com uma versão já corrigida), e um caminho de volta documentado, inclusive com o aviso de que restaurar a configuração de boot do fabricante pode trazer a tela cinza de volta, e o que fazer se trouxer. O guia de contribuição pede o mesmo de quem quiser mandar código: scripts idempotentes, reversíveis, validados nos modos de leitura.
O que essa solução não resolve, dito de frente
- O binário fechado continua lá. A correção contorna o comportamento do daemon; não o corrige. Se o fabricante publicar uma versão que chama a função nova, o patch vira desnecessário. Se publicar uma que chama a antiga de outro jeito, o patch pode não bastar.
- O patch é para a
libevdi1.15. Outra versão pode ter o wrapper em outro formato, e opatchfalha de forma explícita. Isso é intencional: melhor falhar do que aplicar uma correção em cima de código que não é o esperado. - Testado numa configuração. Ubuntu 24.04.4, kernel 6.17, GNOME 46, Wayland,
amdgpu, um adaptador, um monitor 1080p a 60 Hz. O README lista isso de propósito. Em outra GPU, a corrida de boot pode se manifestar diferente ou não se manifestar. - Reinstalar o driver do fabricante desfaz tudo. O instalador dele vai sobrescrever a biblioteca corrigida e recriar o carregamento no boot. É preciso rodar o
install.shde novo, e isso precisa estar na cabeça de quem atualiza.
Primeiro, separar sintomas antes de procurar causa: três sintomas, três causas, e tentar um "fix" único teria falhado nos três. Segundo, achar a camada que se pode mudar: a aberta, compilável, com licença que permite. Terceiro, preferir remover a presença ao invés de consertar a interação: o módulo sob demanda elimina a corrida em vez de tentar vencê-la. E quarto, gastar o tempo que for preciso para que cada passo tenha dry-run e tenha volta, porque quem vai rodar aquilo já está com o sistema quebrado.
Por que isso está no GitHub e não só na minha máquina
Depois de uma noite de trabalho, o adaptador funciona por completo no Ubuntu 24.04 com GNOME e Wayland. Eu poderia ter parado aí. Em vez disso, coloquei tudo aberto, documentado em dois idiomas, com os scripts de diagnóstico e de reversão, para que qualquer pessoa com o mesmo problema instale e resolva sem passar pelos três dias que eu passei. É o que o open source tem de mais bonito: um problema que trava uma pessoa vira uma solução que destrava várias. Colaboração não é sobre fazer sozinho. É sobre não deixar o próximo começar do zero.
Resumo da conversa
- Três sintomas, três causas, todas na camada aberta do EVDI: EVDI 1.14.7 que não compila em kernel 6.8+,
strlen(NULL)num wrapper descontinuado que o daemon fechado ainda chama, e um módulo forçado no boot que disputa a GPU primária com oamdgpu. - As correções: trocar o EVDI embutido por 1.15.0 (offline), uma linha de guarda de
NULLnalibevdi, e carregar o módulo sob demanda via udev em vez de no boot. - O pool de quatro placas virtuais é um workaround do X.Org que o Wayland não precisa. Reduzir ajuda na clareza e na corrida de boot, não na memória, e a documentação diz isso.
- O código que conserta é pequeno. O trabalho de verdade foi diagnóstico só-leitura, build sem instalar, dry-run, backup único e desinstalação com volta.
- O binário fechado continua fechado. A solução contorna, não corrige, e os limites estão escritos no README.
Igor Nascimento