Introdução ao coreboot devicetree

A primeira coisa que pode vir à mente quando se ouve "DeviceTree" é um tipo diferente de arquivo de descrição que geralmente é passado para o kernel Linux para descrever os componentes de um sistema. Tanto o devicetree quanto o devicetree do coreboot servem fundamentalmente para o mesmo propósito, mas não são relacionados e têm sintaxe completamente diferente. O termo devicetree foi usado muito antes de qualquer versão ser criada e foi usado inicialmente no coreboot como um termo genérico. O uso principal do devicetree do coreboot é definir e descrever a configuração de tempo de execução e as configurações do hardware em uma placa, chip ou nível de dispositivo. Ele define quais das funções dos chips na placa estão habilitadas e como elas são configuradas. O arquivo devicetree é analisado durante o processo de construção por um utilitário chamado sconfig, que traduz o devicetree em uma árvore de estruturas C contendo os dispositivos incluídos. Este código é colocado no arquivo static.c e alguns arquivos de cabeçalho, todos sob o diretório de construção. Este arquivo é então construído nos binários para os vários estágios do coreboot e é referenciado durante o processo de inicialização do coreboot. Para os estágios iniciais do processo de inicialização do coreboot, os dados gerados pelo sconfig são um recurso útil, mas essa estrutura é a cola arquitetônica crítica do ramstage. Essa estrutura é preenchida com ponteiros para o código de inicialização de cada chip, permitindo que o ramstage encontre e inicialize esses dispositivos por meio das estruturas chip_operations.

Histórico do devicetree do coreboot

O devicetree inicial no coreboot foi introduzido em 2003 por Ron Minnich como parte do arquivo de configuração do linuxbios, 'config.lb'. Neste ponto, tanto o devicetree quanto as opções de configuração estavam no mesmo arquivo. Em 2009, quando o Kconfig foi adicionado à compilação do coreboot, o devicetree foi dividido em seu próprio arquivo para cada placa-mãe em um commit com esta mensagem:

devicetree.cb
The devicetree that formerly resided in src/mainboard/*/*/Config.lb.
Just without the build system crap

A estrutura devicetree foi inicialmente usada principalmente apenas no ramstage para enumeração de dispositivos PCI, configuração e alocação de recursos. Desde então, ela foi expandida para uso nos estágios pré-ram como uma estrutura somente leitura. A linguagem usada no devicetree foi expandida muito desde que foi introduzida pela primeira vez, adicionando novos recursos a cada ano ou mais.

Registros Devicetree

No coreboot, a configuração do registro devicetree é um dos dois métodos principais usados ​​para configurar as propriedades de uma placa. Dessa forma, o devicetree é similar em função ao Kconfig. Ele é mais flexível em muitos aspectos, pois pode especificar não apenas valores únicos, mas também matrizes ou estruturas. Ele também é ainda mais estático que o Kconfig porque não há mecanismo de atualização para ele além de editar os arquivos devicetree.

Adicionando novas opções de configuração estática: Devicetree ou Kconfig

Ao adicionar opções para uma nova placa ou chip, frequentemente há uma decisão que precisa ser tomada em termos de como a opção deve ser adicionada. Usar o devicetree ou o Kconfig são os dois métodos típicos de configuração em tempo de compilação. Abaixo estão algumas regras gerais de quando cada um deve ser usado. O Kconfig deve ser usado se a opção for usada para configurar a compilação em um Makefile, ou se a opção for algo que deve ser selecionável pelo usuário. O Kconfig também é preferível se a configuração for uma opção global e não limitada a um único chip. Outra coisa em que o Kconfig é bom é lidar com decisões baseadas em outras opções de configuração, o que não é algo que o devicetree pode realmente fazer. O Devicetree deve obviamente ser usado para definir a configuração da hierarquia de hardware, mas também é preferível se a opção for usada apenas no código C e for estática para uma placa-mãe, ou se a opção for específica do chip. Conforme mencionado anteriormente, os registradores do Devicetree também podem ser usados ​​para criar estruturas ou matrizes que o Kconfig não pode. Tanto o Kconfig quanto o devicetree podem ser usados ​​no código C para configuração de tempo de execução, mas há uma grande diferença no back-end sobre como eles são manipulados. Como o Kconfig gera um #define da escolha, o compilador pode eliminar quaisquer caminhos de código não usados ​​pela opção. As opções do Devicetree, por outro lado, são seleções de tempo de execução reais e o código para todas e quaisquer escolhas permanecerá na compilação final.

Três níveis de arquivos devicetree

Atualmente, há três níveis diferentes de devicetrees usados ​​para construir a estrutura de componentes e registrar valores no coreboot. Do nível mais baixo e geral ao mais alto e específico, eles são chipset.cb, devicetree.cb e overridetree.cb. A menos que haja um motivo específico para nomeá-los com algo diferente desses nomes, eles são os nomes que devem ser usados. Para SoCs e chipsets mais novos, geralmente haverá um arquivo chipset.cb, e cada placa-mãe precisa ter um arquivo devicetree.cb, embora ele possa estar vazio se, de alguma forma, tudo for igual ao arquivo de nível SoC. Um arquivo overridetree.cb só é necessário se as variantes tiverem diferenças do devicetree no nível da placa-mãe primária.

Nível SoC/chipset, chipset.cb

O arquivo chipset.cb foi adicionado em outubro de 2020, permitindo que um único chipset ou SoC forneça um devicetree de "nível básico", reduzindo a duplicação entre placas-mãe. O arquivo chipset.cb também normalmente definirá "aliases" legíveis por humanos para dispositivos específicos para que as placas-mãe possam usá-los em vez de apenas os números de roteamento PCI e similares. O uso do arquivo chipset.cb é especificado no Kconfig pelo símbolo CONFIG_CHIPSET_DEVICETREE. No arquivo chipset.cb, você pode ver algumas linhas como esta:

device pci 17.0 alias sata off end
device pci 1e.0 alias uart0 off end

Isso define os aliases para esses dispositivos PCI e os desabilita por padrão.

Nível da placa-mãe primária, devicetree.cb

Cada placa-mãe deve ter um arquivo devicetree.cb. O nome do arquivo e o caminho são definidos para um valor padrão src/mainboard/<VENDOR><MBNAME>/devicetreepelo símbolo CONFIG_DEVICETREE no Kconfig. Se uma placa-mãe e variantes normalmente quisessem ambos os dispositivos habilitados, você veria o seguinte em devicetree.cb:

device ref sata on end
device ref uart0 on end

Nível de variante da placa-mãe, overrridetree.cb

Introduzido em 2018 para reduzir a duplicação e a manutenção de placas variantes, o arquivo overridetree.cb é o nível mais específico do arquivo devicetree. Isso permite um devicetree base no nível superior da placa-mãe que é compartilhado por todas as variantes, e cada variante só precisa atualizar para seus valores específicos. O nome do arquivo da árvore de substituição é definido no Kconfig com o símbolo OVERRIDE_DEVICETREE e normalmente é chamado de "overridetree.cb". Finalmente, se uma das variantes de uma placa-mãe veio sem um conector sata, ela poderia desabilitar novamente o sata com o seguinte em overridetree.cb:

    device ref sata on end

Arquivos adicionais

arquivos chip.h

O coreboot olha para o chip como uma coleção de dispositivos. Essa coleção pode ser um único dispositivo ou vários dispositivos diferentes. A palavra-chave 'chip', que será descrita mais adiante, inicia essa coleção de dispositivos. Após a palavra-chave 'chip', há um nome de diretório que contém o código para lidar com essa coleção de dispositivos. Opcionalmente, pode haver um arquivo chip.h nesse diretório que também será analisado para criar as estruturas de dados do devicetree. Se o arquivo chip.h estiver presente nesse diretório, ele será usado para definir uma estrutura contendo as "definições de registro" para o chip. Os valores para os membros dessa estrutura são definidos em um dos arquivos devicetree. Se não for definido especificamente em um dos arquivos devicetree, o valor do registro será definido como 0 por padrão. O arquivo chip.h frequentemente também contém macros, enums e subestruturas usadas para definir os membros da estrutura do registro. A estrutura para a definição de registro do chip é nomeada após o diretório que conteria o arquivo chip.h, com as barras alteradas para sublinhados e, em seguida, anexadas por '_config'. O diretório src/ é omitido. Isso significa que uma linha em um arquivo devicetree contendo chip drivers/i2c/hidusaria src/drivers/i2c/hid/chip.h, e a estrutura de definição de registro que ele contém seria chamada drivers_i2c_hid_config. Aqui está o conteúdo desse arquivo chip.h:

/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __DRIVERS_I2C_HID_CHIP_H__
#define __DRIVERS_I2C_HID_CHIP_H__
#include <drivers/i2c/generic/chip.h>
#define I2C_HID_CID         "PNP0C50"
struct drivers_i2c_hid_config {
        struct drivers_i2c_generic_config generic;
        uint8_t hid_desc_reg_offset;
};
#endif /* __I2C_HID_CHIP_H__ */

O utilitário sconfig e os arquivos gerados

coreboot/util/sconfig

Sconfig é a ferramenta que analisa as devicetrees do coreboot e as transforma em uma coleção de estruturas de estilo C. Esta é uma ferramenta específica do coreboot, compilada com flex & bison para definir e analisar a linguagem específica de domínio usada pelo dispositivo do coreboot. Sconfig é chamado pelo makefile durante o processo de construção e geralmente não precisa ser executado diretamente. Apesar disso, ele obviamente pode ser executado manualmente, se necessário, e fornece as opções básicas de linha de comando usadas para controlá-lo.

usage: sconfig <options>
 -c | --output_c          : Path to output static.c file (required)
 -r | --output_h          : Path to header static.h file (required)
 -d | --output_d          : Path to header static_devices.h file (required)
 -f | --output_f          : Path to header static_fw_config.h file (required)
 -m | --mainboard_devtree : Path to mainboard devicetree file (required)
 -o | --override_devtree  : Path to override devicetree file (optional)
 -p | --chipset_devtree   : Path to chipset/SOC devicetree file (optional)

entradas sconfig

Os arquivos de entrada sconfig chip.h, chipset.cb, devicetree.cb e overridetree.cb foram discutidos anteriormente. Como o uso acima mostra, o único arquivo de entrada necessário é o devicetree da placa-mãe. Os arquivos devicetree adicionais, chipset.cb e overridetree.cb são opcionais, e os arquivos chip.h não precisam ser especificados, porque sua localização está contida dentro dos arquivos. A construção dos arquivos de entrada devicetree será discutida mais tarde.

saídas sconfig

estático.c

Este arquivo é o arquivo primário gerado pelo sconfig. Ele contém os dados principais sobre os barramentos e interfaces do componente. Por razões históricas, static.c é gerado no build/mainboard/<VENDOR>/<MBNAME> directory

estático.h

O arquivo static.h é o arquivo de cabeçalho principal incluído pela maioria dos arquivos coreboot que precisam usar os dados do devicetree. Ele é incluído por src/include/device/device.h, que contém todas as definições, estruturas e protótipos de função para lidar com a saída gerada pelo devicetree. O Static.h costumava conter toda a saída diretamente, mas, a partir de outubro de 2020, contém apenas instruções include trazendo os outros dois arquivos de cabeçalho gerados. Isso permite que os dois cabeçalhos sejam incluídos separadamente para que as opções de configuração do firmware possam ser usadas em um payload.

dispositivos_estáticos.h

O arquivo static_devices.h contém declarações externas de todas as estruturas de dispositivos definidas em static.c.

static_fw_config.h

static_fw_config.h contém apenas as macros FW_CONFIG_FIELD_*, o que o torna facilmente consumível por uma carga útil ou qualquer outra coisa que deseje usar os campos FW_CONFIG da plataforma.

Exemplo de Devicetree

Uma devicetree muito simples

Este é o arquivo devicetree.cb em src/mainboard/sifive/hifive-unleashed com números de linha adicionados. Arquivos devicetree não-X86 tendem a ser muito simples assim.

    1  # SPDX-License-Identifier: GPL-2.0-only
    2  chip soc/sifive/fu540
    3          device cpu_cluster 0 on  end
    4  end

Isso pode ser dividido da seguinte forma: Linha 1: Comentários começam com o caractere '#'. Esta linha é o que é conhecido como cabeçalho SPDX, que identifica como este arquivo em particular é licenciado. Linha 2: "chip" inicia uma seção para uma coleção de dispositivos. Ele é seguido por um diretório que contém o código para um arquivo chip.h opcional. Linha 3: "device" inicia um bloco de dispositivo, mas precisa ser seguido por um tipo de dispositivo. "cpu_cluster" é o tipo, e isso precisa ser seguido por um identificador - "0". O dispositivo pode ser marcado como habilitado "on" ou desabilitado "off", então o bloco de dispositivos é fechado com a palavra-chave "end". Linha 4: Finalmente "end" fecha o bloco iniciado pela palavra-chave "chip".

Arquivos gerados

Continuando com o devicetree simples da placa-mãe sifive/hifive-unleashed, estes são os arquivos gerados do devicetree a partir de 2022-05-01. Como o devicetree é tão simples, não há quase nada nos arquivos de cabeçalho

construir/estático.h

#ifndef __STATIC_DEVICE_TREE_H
#define __STATIC_DEVICE_TREE_H
#include <static_fw_config.h>
#include <static_devices.h>
#endif /* __STATIC_DEVICE_TREE_H */

construir/dispositivos_estáticos.h

#ifndef __STATIC_DEVICES_H
#define __STATIC_DEVICES_H
#include <device/device.h>
/* expose_device_names */
#endif /* __STATIC_DEVICE_NAMES_H */

construir/static_fw_config.h

Como este arquivo não usa nenhuma definição de configuração de firmware no devicetree, também não há nenhuma aqui.

#ifndef __STATIC_FW_CONFIG_H
#define __STATIC_FW_CONFIG_H
#endif /* __STATIC_FW_CONFIG_H */

construir/placa-mãe/sifive/hifive-unleashed/static.c

Inclui

1  #include <boot/coreboot_tables.h>
2  #include <device/device.h>
3  #include <device/pci.h>
4  #include <fw_config.h>
5  #include <static.h>

Linhas 1 a 5: Inclui arquivos de cabeçalho necessários para as seguintes estruturas.

Declarações para chip-ops

6
7   #if !DEVTREE_EARLY
8   __attribute__((weak)) struct chip_operations mainboard_ops = {};
9   extern struct chip_operations soc_sifive_fu540_ops;
10  #endif

Linhas 7 e 10: As estruturas ops dentro deste bloco IF são usadas somente no ramstage. Linhas 8 - 9: Declarações para estruturas ops de chip. Esta seção será expandida conforme mais chips forem adicionados à árvore de dispositivos.

  • A linha 8, para mainboard_ops, está sempre presente e não muda entre as placas. É definida como uma função fraca porque a placa-mãe pode ou não definir uma estrutura chip_operations.
  • A linha 9 é criada pela declaração do chip no arquivo devicetree. Haverá uma linha similar para cada chip declarado no devicetree.

Definição de ARMAZENAMENTO

11
12  #define STORAGE static __unused DEVTREE_CONST

Linha 12: Esta macro resolve para 'static __unused const' nos estágios iniciais e 'static __unused' no ramstage.

Definições de estrutura

13
14
15  /* pass 0 */
16  STORAGE struct bus dev_root_links[];
17  STORAGE struct device _dev_0;
18  DEVTREE_CONST struct device * DEVTREE_CONST last_dev = &_dev_0;

As linhas 16 a 18 declaram todas as estruturas usadas em static.c

Estruturas de Registro

19
20  /* chip configs */

A linha 20 é o início de uma seção vazia para esta placa-mãe, mas conteria quaisquer estruturas de registro de chip definidas em qualquer um dos arquivos chip.h.

Estrutura Dev_root

Linha 21 - Linha 44: dev_root: Esta é a estrutura da placa-mãe e o cabeçalho da lista vinculada para todos os dispositivos. Esta estrutura é criada para cada placa-mãe, independentemente de qualquer outra coisa na devicetree. Embora a placa-mãe seja frequentemente listada na devicetree, ela não precisa ser especificada para gerar esta estrutura.

21
22  /* pass 1 */
23  DEVTREE_CONST struct device dev_root = {
24          #if !DEVTREE_EARLY
25                  .ops = &default_dev_ops_root,
26          #endif
27                  .bus = &dev_root_links[0],
28                  .path = { .type = DEVICE_PATH_ROOT },
29                  .enabled = 1,
30                  .hidden = 0,
31                  .mandatory = 0,
32                  .on_mainboard = 1,
33                  .link_list = &dev_root_links[0],
34                  .sibling = NULL,
35          #if !DEVTREE_EARLY
36                  .chip_ops = &mainboard_ops,
37                  .name = mainboard_name,
38          #endif
39                  .next=&_dev_0,
40          #if !DEVTREE_EARLY
41          #if CONFIG(GENERATE_SMBIOS_TABLES)
42          #endif
43          #endif
44  };

Linhas 24 - 26: Isso aponta para uma estrutura device_operation ramstage padrão (descrita mais tarde) no arquivo src/device/root_device.c que não faz nada por padrão. Se você deseja usar essa estrutura de dispositivo para algo (como gerar tabelas acpi específicas da placa-mãe), ela pode ser atualizada no início do ramstage na função chip_operations::enable_dev() da placa-mãe, também descrita mais tarde. Linha 27: O ponteiro para o link raiz deste barramento. Como este dispositivo é o link raiz deste barramento, ele está apontando para sua própria estrutura de barramento. Linhas 28 - 32: Status da enumeração.

  • O oculto e o obrigatório são definidos apenas por essas palavras-chave no devicetree.
  • O status habilitado é inicialmente definido pelo devicetree, mas pode ser modificado no ramstage se o dispositivo não for detectado.
  • O sinalizador de status on_mainboard é definido para qualquer coisa especificada em devicetree. Se o dispositivo fosse encontrado durante a enumeração, esse sinalizador não seria definido. Linha 33: O ponteiro para o próximo link neste barramento na lista devicetree. Nesse caso, o próximo link e o link raiz são os mesmos. Isso é descrito mais detalhadamente na próxima seção. Linha 34: Dispositivos irmãos ou chips são aqueles definidos no mesmo nível no arquivo devicetree. Exemplos seriam chips northbridge e southbridge, ou dispositivos pci e suas funções dentro de um chip. Nada deve estar no mesmo nível que o chip da placa-mãe, então isso deve ser sempre NULL. Linha 36: O ponteiro para a estrutura chip_operations da placa-mãe. A placa-mãe é especial porque, embora não seja realmente um chip, ela ainda recebe uma estrutura chip_operations. Isso permite que a placa-mãe execute quaisquer ações necessárias nos mesmos pontos do fluxo de inicialização que um chip seria capaz de fazer. Linha 37: O nome da placa-mãe. Esta é uma variável global definida no momento da compilação, também no arquivo src/device/root_device.c.

Links_raiz_de_dev

linhas 45 - 52: A estrutura de barramento dev_root Esta é a estrutura que está vinculada nas linhas 27 e 33 acima. Ela fornece ponteiros para o dispositivo atual e para o próximo dispositivo na lista vinculada. Uma nova estrutura de barramento é criada para cada tipo de dispositivo diferente na devicetree. A mesma estrutura de barramento pode ser compartilhada para os mesmos tipos de dispositivo, mesmo em chips diferentes, desde que o barramento esteja contido no mesmo cpu_cluster ou domínio. Normalmente, placas x86 de soquete único terão quatro dessas estruturas:

  • Placa-mãe - sempre dev_root_links
  • CPU_Cluster - normalmente _dev_0_links
  • Barramento PCI, Domínio 0 - normalmente _dev_1_links
  • Barramento ISA/LPC/eSPI - _dev_X_links, onde X é o dispositivo de ponte no PCI
45  STORAGE struct bus dev_root_links[] = {
46          [0] = {
47                  .link_num = 0,
48                  .dev = &dev_root,
49                  .children = &_dev_0,
50                  .next = NULL,
51          },
52  };

Linha 47: .link: O índice deste link. Linha 48: .dev: Ponteiro para a estrutura atual do dispositivo de ponte. Linha 49: .children: A estrutura superior do dispositivo no barramento atrás deste dispositivo de ponte. Linha 50: .next: O próximo dispositivo de ponte no barramento atual.

_dev_0

Linha 53 - Linha 72: O dispositivo cpu_cluster

53  STORAGE struct device _dev_0 = {
54          #if !DEVTREE_EARLY
55                  .ops = NULL,
56          #endif
57                  .bus = &dev_root_links[0],
58                  .path = {.type=DEVICE_PATH_CPU_CLUSTER,{.cpu_cluster={ .cluster = 0x0 }}},
59                  .enabled = 1,
60                  .hidden = 0,
61                  .mandatory = 0,
62                  .on_mainboard = 1,
63                  .link_list = NULL,
64                  .sibling = NULL,
65          #if !DEVTREE_EARLY
66                  .chip_ops = &soc_sifive_fu540_ops,
67          #endif
68          #if !DEVTREE_EARLY
69          #if CONFIG(GENERATE_SMBIOS_TABLES)
70          #endif
71          #endif
72  };

Linhas 54-56: Ponteiro para a estrutura device_operations. Como este é um chip, não um dispositivo, este ponteiro é NULL. Linha 57: Ponteiro para o link raiz deste barramento. Como este dispositivo está diretamente sob a placa-mãe, isto aponta para o link de barramento da placa-mãe. Linha 58: A estrutura device_path, definida em src/include/device/path.h é uma estrutura específica do tipo de caminho que detalha a identidade do dispositivo. Estes devem ser únicos no devicetree. Este é o campo que é referenciado ao pesquisar o devicetree para um dispositivo. Linhas 59 - 62: Status de enumeração. O mesmo que em dev_root Linhas 63 e 64: Ponteiros de lista de links para outros dispositivos na árvore. Estes são NULL neste caso porque o único dispositivo no devicetree que gerou esta estrutura é o chip SoC. Linhas 65 - 67: Ponteiro para a estrutura processor chip_ops. Isto é usado apenas em ramstage. Linhas 68 - 71: Vazio aqui, mas é aqui que qualquer coisa definida por smbios_dev_info ou smbios_slot_desc iria.

Comments

Popular posts from this blog

TUTORIAL: Testando Placas de Vídeo NVIDIA com MATS / MODS

BIOS E ESQUEMA ELÉTRICO ITAUTEC ST 4272

Programa Para Testar a VRAM de Placas de Vídeo Nvidia: MATS