1 pontos por GN⁺ 2025-01-10 | 1 comentários | Compartilhar no WhatsApp
  • 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_acm foi 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 .tar do 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, ttyS0 e ttyS1 apareciam 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 outb e inb
  • 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, 0x41 faz com que o outro lado receba A, 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 PCISerialDevice existente 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 em Kernel/Arch/init.cpp, foi possível rastrear o avanço até estreitar a falha em Memory::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 nop entre 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, cut e xxd

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 Controller e ASSERTION FAILED: !m_controllers.is_empty()
    • Com isso, o kernel panic acontecia em StorageManagement::enumerate_storage_devices()
  • 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_configuration no 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
  • 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

 
GN⁺ 2025-01-10
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

    • Uma das filosofias do Serenity é construir tudo do zero sempre que possível, então mesmo que os drivers do NetBSD sejam fáceis de adaptar e a licença seja compatível, provavelmente eles prefeririam escrever os próprios drivers
    • Fiquei pensando se, para um OS novo ou de hobby, não seria melhor mirar desde o começo em um single-board computer popular como o Raspberry Pi
      Como a configuração de hardware é quase fixa, fica mais fácil criar ou portar drivers e testar o sistema
    • rump kernel/anykernel é essa ideia
      Ele permite executar drivers no espaço do usuário com suporte mínimo de base
      https://en.wikipedia.org/wiki/Rump_kernel
    • O lado do NetBSD é confiável. A libc é muito limpa, e já a reaproveitei em alguns projetos
      Mas da parte de drivers eu não sei muito
    • A solução é escolher um bom conjunto de hardware e, se possível, definir equipamentos vendidos pelo próprio autor do software, criando drivers só para esse hardware
      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

    • Na prática, se comunicar com hardware moderno é relativamente simples; o essencial é ler e escrever na memória do hardware
      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
    • Eu comecei com LDD. É um livro de uns 10 anos atrás, mas ainda continua relevante
      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/
    • Uns 15 anos atrás fiz uma disciplina de desenvolvimento de OS na faculdade e usamos o Minix
      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
    • Sinceramente, o primeiro passo é entender a finalidade de cada componente, como OS, driver e dispositivo
      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
    • Este tutorial foi realmente muito bom
      https://wiki.osdev.org/Bare_Bones
  • Gosto do conceito do SerenityOS e do navegador Ladybird, então é bom ver esse progresso

    • Infelizmente, os dois agora seguiram caminhos separados
      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?

    • É verdade que ele já rodou em hardware real no passado
      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")

    • Como leigo, eu realmente queria entender o que esse código quer dizer