- O SerenityOS funcionava bem principalmente no QEMU, mas em um notebook real os obstáculos começaram um por um — boot, depuração e acesso ao armazenamento — expondo as lacunas no suporte a hardware
- O alvo do experimento foi um Dell 3100 Chromebook com Intel Celeron N4020, 4GB de DDR4, 32GB de eMMC e tela TN de 1366×768, mas a depuração em gabinete fechado via Cr50 que parecia promissora falhou nessa placa
- Com o caminho do Cr50 bloqueado, foi colocado um Pi Pico baseado em RP2040 dentro do equipamento e ligado diretamente à UART e ao flash SPI, criando com CircuitPython e serprog um dispositivo temporário de depuração e gravação chamado PicoCCD
- Como era difícil usar logo de início a UART 16550 MMIO atrás do PCI para obter os primeiros logs de boot, foi aproveitada a porta de E/S 0x80 que o ChromeOS EC registra como um canal de saída temporário e lento
- O suporte a eMMC avançou até algumas sessões gráficas parciais, passando por diferenças de inicialização entre SD e MMC, ausência de controle de energia no SDHCI e desativação de comandos exclusivos de SD, mas ainda restam desempenho, estabilidade e organização dos patches
Dell 3100 Chromebook escolhido como hardware real de teste
- Ao tentar se envolver mais a fundo com o SerenityOS, a primeira fraqueza que saltou aos olhos foi que ele roda no QEMU, mas tem pouco suporte a hardware real
- O suporte a UEFI já estava sendo desenvolvido pelo spholz, então isso não foi mexido; para este trabalho, bastava que o kernel da branch master inicializasse por GRUB no ambiente UEFI do TianoCore
- Queria evitar depurar o sistema operacional no mesmo equipamento usado como máquina principal de desenvolvimento e escolhi um hardware relativamente recente, algo que pudesse até ser usado no dia a dia
- Procurando Chromebooks baratos no Allegro, comprei um Dell 3100 por 95 PLN, cerca de 25 EUR
- Intel Celeron N4020, 2 núcleos, sem Hyper-Threading
- 4GB DDR4
- 32GB de eMMC onboard
- Tela TN de 1366×768 acionada pela IGP UHD600
- 2 portas USB-A, 2 portas USB-C, conector de 3,5 mm
- Um teclado que pareceu melhor do que o de notebooks corporativos mais caros da Dell
- A partir daqui, esse equipamento é chamado de octopus
A expectativa com a depuração via Cr50 e o fracasso
- Um dos grandes motivos para escolher um Chromebook foi que o chip de segurança Cr50 e o controlador embarcado oferecem recursos úteis para depuração em gabinete fechado
- Quase todos os Chromebooks de 2018 em diante permitem usar uma das portas USB-C para depuração com um cabo SuzyQ, e o Cr50 normalmente expõe três dispositivos ttyUSB
- Console interno do Cr50
- Console da AP, ou seja, a porta serial do Chromebook
- Console do cros_ec, isto é, o controlador embarcado
- O objetivo era acessar o console serial sem deixar fios pendurados com o notebook aberto e, considerando a emulação de teclado do cros_ec e o controle de estado de energia, parecia até viável montar algo no estilo de um KVM simples
- No octopus real, porém, o CCD do Cr50 não funcionou
- Foi feito um novo cabo SuzyQ e o estado das soldas foi validado em outro Chromebook, mas sem sucesso
- O octopus era um dos poucos notebooks em que a Dell não populou alguns resistores da placa, impedindo o funcionamento do CCD
- Algumas pessoas relataram sucesso limitado sob certas combinações de orientação da porta e carregador conectado, mas neste equipamento não funcionou de jeito nenhum
- Informações verificadas depois sugerem que o resistor ausente deveria afetar apenas a gravação via SPI, não a ponte USB em si, então o motivo exato de a depuração do Cr50 não ter funcionado continua incerto
PicoCCD feito com Pi Pico
- Depois que o caminho via Cr50 falhou, foi verificado se havia espaço suficiente dentro do equipamento para colocar uma placa Pi Pico comum, e havia
- Foram consultados esquemas de notebooks parecidos, mas não existia um esquema que batesse exatamente com o octopus
- Uma das grandes portas de depuração da placa era voltada a JTAG e pontos de teste da Intel, sem servir ao objetivo
- A outra era o Google Servo, mas o Google lançou várias sondas de depuração sob o nome Servo e a documentação era limitada, dificultando encontrar o que interessava
- Como referência, foi usada a documentação do Servo
- Com o applet de UART do Glasgow e a detecção de frequência ativados, foram sondados diretamente os pads suspeitos de UART TX enquanto o Linux emitia repetidamente pela
/dev/ttyS1- O pad de TX foi encontrado em poucos minutos
- O RX foi mais difícil, porque exige transmissão ativa e encostar na linha errada pode resetar a placa; de fato, isso aconteceu duas vezes
- Depois disso, os pinos RX/TX do EC também foram encontrados em cerca de 10 minutos
- Os fios soldados foram fixados com epóxi curado por UV, e não houve problema de conexão durante 6 meses
- O periférico SPI do RP2040 também foi aproveitado, com 6 fios soldados ao chip de flash; a trilha que ia ao pino de write-protect foi cortada e ligada ao GND para obter acesso de escrita sem autorização do Cr50
- No software, a escolha foi pelo CircuitPython
- Porque permite subir scripts e dados via armazenamento em massa USB
- Fazer a ponte da UART para um dispositivo USB
cdc_acmfoi simples - Como o flash SPI também estava ligado, era necessário ter funcionalidade de gravação
- Como ferramenta aberta e comum para gravação de EEPROM/SPI, foi usado o flashrom, e o serprog, que faz proxy de SPI por UART, se encaixou no objetivo
- Já existia a implementação em C pico-serprog do stacksmashing, mas ter de regravar o Pico toda vez que o BIOS fosse gravado não se encaixava no uso desejado
- Em vez disso, foi implementado serprog em CircuitPython, com bastante referência ao applet serprog do Glasgow
- O código resultante acabou organizado como a solução improvisada de depuração em gabinete fechado PicoCCD, com repositório em PicoCCD no Forgejo
- WeirdTreeThing também escreveu código em C para RP2040 com objetivo parecido, e existe a versão dele do PicoCCD
Obtendo os logs de boot do SerenityOS
- Foi instalado o Alpine Linux para depuração e configurado um conjunto básico de utilitários para trazer o kernel do SerenityOS compilado externamente
- Depois disso, a estrutura cresceu até incluir download automático dos artefatos da máquina de build, descompactação e sobrescrita do kernel, e uma entrada do GRUB que também extraía o
.tardo espaço de usuário - O tempo de iteração até testar uma mudança era de cerca de 20 segundos no momento da escrita, o que já era bem razoável para hacking em bare metal
- A primeira entrada de boot do GRUB era basicamente
multiboot /Kernel serial_debug, mas não aparecia nada nem na tela nem na serial - Para o problema de saída na tela, foi encontrada a ideia de adicionar
insmod all_videoà entrada de boot do GRUB; isso não resolveu sozinho, mas apontou na direção certa - A ausência de saída serial era um problema maior
- Os logs do coreboot ainda apareciam até poucos segundos antes
- A UART daquele equipamento não era uma 16550 tradicional mapeada em porta, mas uma 16550A baseada em MMIO
- Nos logs do Linux,
ttyS0ettyS1apareciam como 16550A em endereços MMIO - No
lspci, ela aparecia como dispositivo PCI Intel Celeron/Pentium Silver Processor Serial IO UART Host Controller
O contorno com UART 16550 e porta 0x80
- Tradicionalmente, periféricos externos do IBM PC eram mapeados em E/S por porta na CPU x86, com acesso por instruções como
outbeinb - Muitos dispositivos depois migraram para MMIO, mas a porta serial permaneceu no modelo antigo por muito tempo, já que disputar desempenho em alta velocidade não era importante, e isso a tornou útil como porta de depuração
- Em um ambiente comum, algo como
outb 0x3f8, 0x41faz com que o outro lado recebaA, e a inicialização também é curta, o que facilita sua implementação em projetos pequenos - A UART do octopus era um dispositivo MMIO atrás do PCI, e no começo do boot do SerenityOS não dava para contar nem com a inicialização do PCI
- O SerenityOS tem implementação de barramento PCI, mas aquele estágio do boot era cedo demais
- O
PCISerialDeviceexistente também nunca havia sido usado em contexto MMIO - Criar esse driver sem saída de depuração não era o cenário ideal
- O controlador embarcado de dispositivos ChromeOS registra em log todas as escritas na porta de E/S 0x80
- Essa porta era tradicionalmente usada para relatar estado do POST
- Os displays de código de boot de 7 segmentos em placas-mãe funcionam decodificando a porta 80
- A hipótese foi testada no Linux com um script que escrevia bytes em
/dev/port, e esses bytes puderam ser lidos no console do cros_ec - Inserindo código como
IO::out8(0x80, 1);perto do ponto de entrada emKernel/Arch/init.cpp, foi possível rastrear o avanço até estreitar a falha emMemory::MemoryManager::initialize(0); - Depois disso, foi tentada uma abordagem de mudar o endereço da rotina de escrita serial de 0x3f8 para 0x80
- No começo saíam muitos bytes, mas o cros_ec não conseguia retransmiti-los de forma estável, e aconteciam overflows
- Mais adiante, até a própria saída de log do cros_ec passava a sair corrompida
- Ao contrário de um chip serial de verdade, ali não havia um grande buffer
- O contorno foi inserir muitos estados de espera baseados em
nopentre as escritas- O boot, que levava segundos para exibir todas as mensagens, passou a levar minutos
- Ainda assim, isso foi considerado um custo aceitável para depuração em bare metal
- Para fazer parsing automático das linhas do log do cros_ec e decodificá-las em ASCII, foi usada uma one-liner de Bash combinando
picocom,watch,grep,sed,cutexxd
Framebuffer e a primeira saída gráfica
- Depois de conseguir os logs de boot, foram gastos alguns dias tentando entender o problema só lendo o código, mas no fim foi preciso pedir ajuda à comunidade
- O spholz indicou o PR #24435 do SerenityOS, que estava aberto na época, e ao compilar aquela branch o generic framebuffer passou a funcionar
- A tela passou a mostrar um resultado em que o trabalho falhava, mas de um jeito que parecia quase sucesso, e então os problemas de armazenamento vieram à tona de vez
eMMC e os problemas de inicialização SD/MMC
- O travamento existente no StorageManagement vinha de uma assertion causada pelo fracasso ao inicializar o SD Host Controller, deixando a lista de controladores vazia
- Nos logs apareciam
PCI: Failed to initialize SD Host ControllereASSERTION FAILED: !m_controllers.is_empty() - Com isso, o kernel panic acontecia em
StorageManagement::enumerate_storage_devices()
- Nos logs apareciam
- O octopus tem um chip eMMC de 32GB, e como o SerenityOS já possuía parte de um driver SD, parecia que bastaria adicionar suporte a MMC
- Para usar cartões SD/MMC, são necessários basicamente três elementos
- Host Controller: em hardware moderno, normalmente o SDHCI especificado pela SD Association
- O barramento ao qual o Host Controller está conectado: neste caso, PCI
- A implementação do protocolo de comunicação entre host e cartão
- Pelo log da falha, o SerenityOS já tinha os dois primeiros elementos; o problema restante estava no protocolo
- O protocolo SD tem especificação pública, mas o MMC passou a ser um padrão JEDEC em 2007 e o acesso formal ficou pago
- SD e MMC têm sequências de inicialização diferentes
- O SerenityOS começava enviando CMD0 e esperando resposta, algo que deveria funcionar tanto em SD quanto em MMC
- Em seguida, enviava CMD8 para configurar tensão, mas MMC não suporta isso e deveria retornar erro
- Algumas fontes sugerem então resetar o cartão e tratá-lo como MMC
- Outras mostram um fluxo mais abrangente, usando a combinação dos resultados de CMD8 e CMD58 para distinguir até versão SD e tipo de capacidade
- Em vez de implementar toda a checagem avançada de compatibilidade, foi implementada apenas a verificação básica
O registro ausente de controle de energia e a correção
- O fluxo de inicialização do MMC, em resumo, seguia estas etapas
- Após o reset, ajustar o clock para 400 KHz
- Esperar 1 ms e depois mais 74 clocks
- Enviar CMD0 e aguardar resposta
- Repetir CMD1 até que o bit 31 da resposta vire 1
- Quando o loop termina, salvar o valor no registro Operating Conditions
- Continuar com o algoritmo de inicialização SD, exceto pela consulta a registradores exclusivos de SD
- Opcionalmente detectar compatibilidade com High-Speed e ativar um dos vários modos HS
- O código chegava até perto da etapa 4, mas depois disso o eMMC não respondia mais a nenhuma requisição
- Depois de vários dias sem achar a causa, ao remover o código relacionado ao reset do controlador o eMMC voltou a responder, e o problema foi isolado à função
reset_host_controller() - O ponto estranho dessa função era que não se encontrava o registro
host_configurationno padrão- O código anterior tinha agrupado arbitrariamente vários registradores em dois grupos
host_configuration - A inicialização também não estava completa, e o primeiro grupo estava sendo simplesmente zerado
- O código anterior tinha agrupado arbitrariamente vários registradores em dois grupos
- Nesse primeiro grupo estava incluído o registrador Power Control, responsável por controlar o regulador de energia do cartão
- Em alguns hardwares, incluindo implementações com eMMC, esse registrador é necessário para realmente ligar o cartão
- Em outros projetos, onde a alimentação é ligada diretamente ao slot, essa configuração pode ser ignorada
- Como solução temporária, foi usado o valor original contido em
host_configuration_0 - O ponto central era que se estava tentando se comunicar com o cartão sem ligar sua alimentação
- Depois disso, ainda foram necessárias algumas horas para encontrar e desativar certos comandos válidos apenas para cartões SD, e com saídas de depuração mais úteis vindas do controlador o restante do trabalho seguiu de forma relativamente comum
Estado atual e o que ainda falta
- No fim, o SerenityOS conseguiu abrir, muito lentamente, uma sessão gráfica parcialmente corrompida, mas travava logo em seguida
- Esse problema da sessão gráfica e o processo de normalização do framebuffer serão tratados em um texto futuro
- Todo o trabalho foi um processo de aprendizado ao longo de cerca de 6 meses, em paralelo com outras atividades
- O próximo objetivo é organizar os patches e enviá-los para upstream ainda este ano
1 comentários
Comentários do Hacker News
Li certa vez que adaptar drivers do NetBSD para um kernel customizado é relativamente fácil, então fico pensando se o Serenity não poderia seguir esse caminho
Para um OS nascente, drivers de dispositivo são um grande obstáculo
Como a configuração de hardware é quase fixa, fica mais fácil criar ou portar drivers e testar o sistema
Ele permite executar drivers no espaço do usuário com suporte mínimo de base
https://en.wikipedia.org/wiki/Rump_kernel
Mas da parte de drivers eu não sei muito
Parece mais ou menos o que a Apple faz desde o início, e é o único caso de grande sucesso entre Unix voltado ao consumidor
A System76 é quase um exemplo disso, e a Frame.work é parecida, embora menos focada no próprio OS
Fazer isso rodar em uma máquina com todas as condições contra si é realmente um trabalho de hacking impressionante, e parece ser o resultado de muito esforço de gente talentosa
Quando leio textos assim, fico curioso sobre como entrar no mundo de drivers e sistemas operacionais
Parece tudo tão complexo que nem sei por onde começar
Isso é chamado de memory-mapped I/O (MMIO). Em aplicações normais, o kernel bloqueia o acesso direto à memória do hardware, então isso não é possível
Para começar, você precisa de uma linguagem como Rust/C++/C/Zig que consiga gerar código de máquina para a CPU-alvo, e é melhor que não tenha runtime nem GC. Se for sua primeira linguagem de baixo nível, eu recomendaria C por ter muitos exemplos
Também é preciso aprender o básico de assembly da CPU-alvo, porque algumas instruções podem não existir como intrínsecos da linguagem de alto nível
Depois disso, você escreve um kernel hello world e aprende como a CPU inicializa o kernel, como os modos de execução e níveis de privilégio são divididos
Em seguida, no x86, por exemplo, você configura a CPU do jeito que quiser, como mudar para long mode para usar instruções de 64 bits, e geralmente também configura memória virtual nessa etapa
Quando chega até aí, já começa a entender como a CPU se encaixa com o OS, como enumerar os dispositivos disponíveis e encontrar posições de memória, embora ainda reste muito trabalho como sistema de arquivos e escalonador
A diferença entre software rodando sobre o OS e o kernel do OS acaba sendo o modo da CPU em que o código está executando; no nível mais privilegiado, você pode usar instruções que apps comuns não podem
Depois encontrei um monte de material valioso escondido na documentação do FreeBSD, e o FreeBSD Architecture Handbook e o FreeBSD Developers' Handbook podem ajudar bastante
https://lwn.net/Kernel/LDD3/
https://docs.freebsd.org/en/books/
O Minix é muito bem escrito e o kernel tem algo em torno de 5 mil linhas, além de aparecer em vários livros
Implementei um servidor simples e também fiz kernel hacking; como o Minix é um microkernel, a maioria dos drivers funciona mais ou menos desse jeito
Li o material da disciplina antes e quase nem fui às aulas, e mesmo assim tirei 8 de 10
Também ouvi muitas coisas boas sobre NetBSD e SerenityOS, e o Andreas fez muito do desenvolvimento em transmissões ao vivo
Quando você sabe por onde começar, na prática fica fácil
Por exemplo, o driver de dispositivo serve para expor uma interface pela qual outros programas em execução no computador possam acessar e controlar um dispositivo
https://m.youtube.com/watch?v=juGNPLdjLH4 é uma aula introdutória razoável
Você também pode criar um dispositivo USB simples que troque informações com o PC usando algo como um Arduino. Ex.: https://m.youtube.com/watch?v=yTc2GLXfCOY
Depois é só entender o que o subsistema que te interessa faz, como colocá-lo para funcionar, e escrever o código. Isso inclui armazenamento, dispositivos gráficos etc.
O Raspberry Pi também pode ser um bom ponto de partida para esse tipo de experimento. Ex.: Writing a bare metal operating system for the raspberry pi https://github.com/babbleberry/rpi4-osdev
https://wiki.osdev.org/Bare_Bones
Gosto do conceito do SerenityOS e do navegador Ladybird, então é bom ver esse progresso
O Ladybird não só virou um projeto independente, como também não considera mais o SerenityOS como plataforma-alvo
O Ladybird está removendo gradualmente sua própria camada Serenity e substituindo por alternativas mais mainstream
Como alguém que usa principalmente Linux, estou animado em ver o Ladybird se tornando uma alternativa de verdade no Linux
Mas, como fã do SerenityOS, é uma pena ver a energia e a inovação que iam para o Ladybird saindo do SerenityOS
Se precisar de ajuda para hackear Chromebook, é só perguntar na lista de discussão chromium-os-dev
Alguém provavelmente pode ajudar a fazer o CCD funcionar
https://groups.google.com/a/chromium.org/g/chromium-os-dev?p...
O bootloader Depthcharge também suporta boot via rede usando TFTP
Você precisa compilá-lo manualmente e gravá-lo na SPI, mas isso é um recurso excelente para desenvolvimento iterativo de kernel
https://chromium.googlesource.com/chromiumos/platform/depthc...
Eu achava que o SerenityOS já rodava em hardware real, então ele ainda funciona só dentro do QEMU?
Só que quase não havia drivers dignos de nota, então funcionava apenas no sentido mais básico, só em hardware específico, e provavelmente nem rodava tão bem assim
Esta tentativa é para fazer com que ele funcione de forma confiável em pelo menos uma plataforma de hardware real
O Serenity continua impressionante, mesmo quando eu discordo da forma como ele é implementado
Foi esse tipo de coisa assustadora que vim ver
doas dd seek=$((0x$1)) bs=1 count=1 of=/dev/port < <(xxd -p -r <<< "$2")