Partilhar via


Guia de implementação do firmware CFU (Component Firmware Update)

A Atualização de Firmware de Componente (CFU) é um protocolo e um processo para enviar novas imagens de firmware a serem instaladas no dispositivo de destino.

Observação

CFU está disponível no Windows 10, versão 2004 (Windows 10 May 2020 Update) e versões posteriores.

Os envios CFU para o firmware residente são pares de ficheiros, sendo um ficheiro a parte da oferta e o outro ficheiro a parte do conteúdo. Cada submissão de CFU (cada oferta e par de conteúdo) deve ser criada offline antes de ser transmitida para o firmware que implementa o processo de CFU.

No código-fonte de exemplo de Firmware no repositório CFU no GitHub, o código comum, agnóstico em termos de implementação geral para o CFU, está contido em ComponentFwUpdate.c. Todos os outros arquivos são arquivos auxiliares que podem ser atualizados ou modificados para a implementação exclusiva do desenvolvedor.

Conteúdos

A oferta e as partes de conteúdo

A oferta e o conteúdo compõem um par de ficheiros no esquema CFU.

A parte da oferta é simplesmente um ficheiro de 16 bytes de comprimento que mapeia para a estrutura FWUPDATE_OFFER_COMMAND descrita abaixo.

A parte de conteúdo, o firmware real a ser atualizado está no formato ditado pelo desenvolvedor do usuário final. O código de exemplo CFU fornecido usa arquivos SREC para conteúdo de firmware.

A oferta é uma sequência de 16 bytes. Esta estrutura de oferta é colocada no ficheiro de oferta. São essencialmente dados binários, não texto, porque a oferta contém campos de bits de significado específico.

A oferta que está representada no ficheiro corresponde a esta estrutura C:

typedef struct
{
   struct
   {
       UINT8 segmentNumber;
       UINT8 reserved0 : 6;
       UINT8 forceImmediateReset : 1;
       UINT8 forceIgnoreVersion : 1;
       UINT8 componentId;
       UINT8 token;
   } componentInfo;

   UINT32 version;
   UINT32 hwVariantMask;
   struct
   {
       UINT8 protocolRevision : 4;
       UINT8 bank : 2;
       UINT8 reserved0 : 2;
       UINT8 milestone : 3;
       UINT8 reserved1 : 5;
       UINT16 productId;
   } productInfo;

} FWUPDATE_OFFER_COMMAND;

De endereço baixo para endereço alto, o primeiro byte da oferta é um número de segmento.

  <------- 4 bytes -----------> <-- 8 bytes -->  <-------- 4 bytes --------->
+================================-=============================================+
|  15:0 7:3  2:0  7:6  5:4  3:0   31:0   31:0     7:0  7:0  7:7  6:6  5:0  7:0 |
|  PI | R1 | MS | R0 | BK | PR  | VM   | VN   |   TK | CI | FV | FR | R0 | SN  |
+================================-=============================================+

Do endereço alto para o endereço baixo:

Byte(s)    Value
---------------------------------------------------------
15:14   |  (PI)  Product ID is 2 bytes
13      |  (R1)  Reserved1 5-bit register
        |  (MS)  Milestone 3-bit register
12      |  (R2)  Reserved2 2-bit register
        |  (BK)  Bank 2-bit register
        |  (PR)  Protocol Revision  2-bit register
11:8    |  (VM)  Hardware Variant Mask 32-bit register
7:4     |  (VN)  Version 32-bit register
3       |  (TK)  Token 8-bit register
2       |  (CI)  Component ID 8-bit register
1       |  (FV)  Force Ignore Version 1-bit register
        |  (FR)  Force Immediate Reset  1-bit register
        |  (R0)  Reserved0 6-bit register
0       |  (SN)  Segment Number 8-bit register
---------------------------------------------------------

Detalhes do registo de ofertas

O ID do produto. Um valor de ID de produto único para a imagem CFU pode ser aplicado a este campo.

UINT16 productID;  

A etapa do firmware que o conteúdo da oferta representa. As etapas podem ser diferentes versões da compilação de hardware, por exemplo, versão EV1, versão EV2, e assim por diante. A definição do marco e a atribuição de valor são deixadas para o desenvolvedor.

UINT8 milestone : 3;

Se o firmware se destina a um banco específico - o campo de 2 bits suporta quatro bancos. O uso de um registo de banco está incluído no formato da oferta porque há casos em que os dispositivos de destino usam regiões de firmware em banco.

Se fosse esse o caso, e a oferta se destinasse a atualizar um banco em uso, o firmware que implementa CFU no alvo pode rejeitar a oferta. Caso contrário, o firmware no alvo que implementa CFU pode tomar outras ações conforme necessário.

Se o banco de imagens de firmware NÃO estiver no design do firmware do usuário final, então é razoável ignorar este campo (definido para quaisquer valores que sejam convenientes, mas o valor no campo banco é opcional e depende da maneira como o firmware no destino implementa CFU).

UINT8 bank : 2;

A versão do protocolo CFU utilizada é em 4 bits.

UINT8 protocolRevision : 4;

A máscara de bits correspondente a todo o hardware distinto em que esta imagem de firmware pode operar. Por exemplo, a oferta pode significar que pode ser executada na versão X do hardware, mas não na versão Y do hardware. A definição de bits e a atribuição de valor são deixadas para o desenvolvedor.

UINT32 hwVariantMask;

A versão do firmware que está sendo oferecida.

UINT32 version;

Um token de byte para identificar o software específico do usuário que faz a oferta. Isso se destina a diferenciar entre drivers e ferramentas que podem estar tentando atualizar o mesmo firmware em execução. Por exemplo, um driver de atualização CFU pode ser atribuído ao token 0xA e uma ferramenta de atualização de desenvolvimento ao token 0xB. Agora, o firmware em execução pode escolher seletivamente aceitar ou ignorar comandos com base no processo que está tentando atualizá-lo.

UINT8 token;

O componente no dispositivo destinado a aplicar a atualização de firmware.

UINT8 componentId;

oferecer indicadores de interpretação: Se quisermos que o firmware in situ ignore a incompatibilidade de versão (mais antiga sobre a mais recente), defina o bit para forçar a ignorar a versão.

UINT8 forceIgnoreVersion: 1;

Forçar a redefinição imediata é definido com um bit. Se esse bit for definido, o software do host espera que o firmware in situ faça com que o dispositivo execute uma reinicialização. As ações da redefinição são específicas da plataforma. O firmware do dispositivo pode optar por tomar medidas que substituam as partições de memória para tornar o firmware recém-atualizado o firmware ativo in situ. Ou não. Resta a implementação do firmware. A expectativa geralmente é que, se a reinicialização imediata for forçada, o dispositivo fará o necessário para que o firmware atualize a nova versão do banco de firmware, tornando-se o firmware ativo em execução no dispositivo alvo.

UINT8 forceImmediateReset : 1;

No caso de a parte de conteúdo da oferta e do par de conteúdos envolver várias componentes de conteúdo.

UINT8 segmentNumber;

Processamento de ofertas

A API ProcessCFWUOffer aceita dois argumentos:

void ProcessCFWUOffer(FWUPDATE_OFFER_COMMAND* pCommand,
                     FWUPDATE_OFFER_RESPONSE* pResponse)

Neste caso de uso, suponha que o software do usuário envia bytes de dados para o firmware em execução, então a primeira mensagem é a mensagem de oferta.

A mensagem de oferta é uma mensagem de 16 bytes descrita acima (a estrutura FWUPDATE_OFFER_COMMAND).

Essa mensagem de oferta é os dados usados pelo firmware em execução para processar a oferta.

Durante o processamento da oferta, o firmware que está a funcionar notifica o remetente preenchendo os campos na estrutura FWUPDATE_OFFER_RESPONSE.

Interpretação da oferta

O firmware em execução deve manter o controle de seu estado no processo CFU. Pode estar pronto/à espera de aceitar uma oferta, no meio de uma transação CFU, ou à espera de trocar bancos entre firmware ativo/inativo.

Se o firmware em execução estiver no meio de uma transação CFU, não aceite/processe esta proposta e notifique o sistema anfitrião de forma adequada.

   if (s_currentOffer.updateInProgress)
   {
       memset(pResponse, 0, sizeof (FWUPDATE_OFFER_RESPONSE));

       pResponse->status = FIRMWARE_UPDATE_OFFER_BUSY;
       pResponse->rejectReasonCode = FIRMWARE_UPDATE_OFFER_BUSY;
       pResponse->token = token;
       return;
   }

O campo ID do componente da oferta pode ser usado para sinalizar ao firmware em execução que uma ação especial é solicitada do firmware em execução. No exemplo de código CFU, um comando de oferta especial é usado pelo host para recuperar o status do mecanismo CFU - se o software em execução é capaz e pronto para aceitar ofertas CFU.

   else if (componentId == CFU_SPECIAL_OFFER_CMD)
   {
       FWUPDATE_SPECIAL_OFFER_COMMAND* pSpecialCommand =
           (FWUPDATE_SPECIAL_OFFER_COMMAND*)pCommand;
       if (pSpecialCommand->componentInfo.commandCode == CFU_SPECIAL_OFFER_GET_STATUS)
       {
           memset(pResponse, 0, sizeof (FWUPDATE_OFFER_RESPONSE));

           pResponse->status = FIRMWARE_UPDATE_OFFER_COMMAND_READY;
           pResponse->token = token;
           return;
       }
   }

Por fim, é feita uma verificação se houver um swap bancário pendente. A troca de banco refere-se ao firmware que persiste na informação sobre se ainda está ou não no processo de mudança da aplicação ativa em execução para a imagem recém-transferida.

Como e onde a mudança de banco é realizada é uma tarefa específica de implementação para o firmware incorporado. O protocolo e o processo CFU permitem a troca de informações entre a aplicação de utilizador remoto que conduz a CFU e o firmware in situ em execução.

   else if (s_bankSwapPending)
   {
       memset(pResponse, 0, sizeof (FWUPDATE_OFFER_RESPONSE));

       pResponse->status = FIRMWARE_UPDATE_OFFER_REJECT;
       pResponse->rejectReasonCode = FIRMWARE_UPDATE_OFFER_SWAP_PENDING;
       pResponse->token = token;
       return;
   }

Finalmente, se o estado do firmware em execução não estiver ocupado, e o componentId não for um comando especial e não houver troca bancária pendente - ENTÃO podemos processar esta oferta.

O processamento de uma oferta envolve, mas não está limitado a, as quatro etapas descritas abaixo:

Passo 1 - Verificar banco

Verifique o banco de dados da aplicação em execução em relação ao banco na oferta. São iguais ou diferentes?

Se for igual, então rejeite a proposta (não queremos substituir a imagem que está em execução/ativa).

Caso contrário, continue.

Passo 2 - Verifique hwVariantMask

O firmware em execução verifica o hwVariantMask na oferta em relação ao HW em que está sendo executado. Isso permite que o firmware incorporado rejeite uma oferta se a oferta for inválida para o alvo. (por exemplo, se o firmware em execução estiver numa compilação HW antiga e o novo firmware oferecido se destinar a uma compilação HW mais recente - então o firmware em execução deve rejeitar esta oferta)

Se for inválido, rejeite a oferta.

Caso contrário, continue.

Passo 3 - Verifique a versão do firmware

Verifique se a versão do conteúdo do firmware oferecido tem uma versão mais antiga ou mais recente do que o firmware do aplicativo atual.

Cabe à implementação dos usuários decidir como verificar qual firmware é maior do que outro e se permitir que o campo 'forceIgnoreVersion' na oferta seja usado. O desenvolvimento típico de firmware permitiria que o campo 'forceIgnoreVersion' fosse usado durante o desenvolvimento do produto e em versões de depuração do firmware, mas seria desativado (evitando que o firmware mais antigo fosse atualizado sobre o novo firmware) na versão final do firmware do produto/lançamento.

Se esta verificação falhar, rejeite a oferta.

Caso contrário, continue.

Passo 4 - Aceitar oferta

A oferta é boa. Aceite a oferta com uma resposta adaptada à forma como as mensagens e o estado são devolvidos pelo firmware à aplicação de utilizador remoto. A denominada "resposta" são dados (uma estrutura de dados compactada, tal como ilustrado nos ficheiros de cabeçalho de demonstração) e esses dados são gravados na aplicação do utilizador pelos meios apropriados para o dispositivo.

Processar o conteúdo

O processamento do conteúdo é geralmente um processo de várias etapas. Os vários passos referem-se à capacidade do firmware de aceitar a imagem do firmware em partes, também conhecidas como "blocos" de dados. Nem sempre é viável enviar a imagem inteira de uma só vez para o firmware incorporado, por isso é realista esperar que a implementação do protocolo CFU e o processo aceitem conteúdo em pequenos pedaços.

Esta discussão parte do pressuposto na descrição do processo do conteúdo de CFU.

A máquina de estado do processamento de conteúdo envolve três estados.

  1. O estado de processamento do primeiro bloco.

  2. O estado de processamento do último bloco.

  3. O estado de processamento de qualquer bloco entre o primeiro e o último.

A estrutura do comando de conteúdo

Tal como a oferta, o conteúdo tem uma estrutura com campos que são utilizados pelos algoritmos CFU na demonstração.

typedef struct
{
   UINT8 flags;
   UINT8 length;
   UINT16 sequenceNumber;
   UINT32 address;
   UINT8 pData[MAX_UINT8];
} FWUPDATE_CONTENT_COMMAND;

A estrutura do comando de conteúdo é mais simples do que a estrutura de oferta. O conteúdo é definido como uma sequência de bytes a serem gravados na memória. O preâmbulo do conteúdo são os campos desta estrutura:

  1. UINT8 flags Indica se o "bloco" de conteúdo é o primeiro, o último ou um outro qualquer.

  2. UINT8 length Marca o comprimento do pData campo. No código de demonstração para CFU, o limite no tamanho do pData é de 255 bytes. Outras implementações podem variar o tamanho máximo do "bloco".

  3. UINT16 sequenceNumber Marca o contador de índice do bloco que está a ser submetido como conteúdo.

  4. UINT32 address O deslocamento de endereço do bloco. Na demonstração do CFU desta versão, a implementação tem informações predefinidas sobre o endereço físico de cada região do aplicativo. Por exemplo, uma implementação de firmware de dois bancos pode ter App1 começar no endereço 0x9000 e App2 começar no endereço 0xA0000. Assim, dependendo de como a imagem do firmware foi preparada (S-Records), o endereço no SREC pode corresponder ao endereço físico ou representar um deslocamento. Em qualquer caso, precisa haver um entendimento compartilhado entre a preparação do conteúdo e as rotinas específicas de implementação do processamento de conteúdo CFU para determinar o verdadeiro endereço físico de onde escrever o bloco na memória. Cabe ao desenvolvedor de firmware adotar as melhores práticas e verificar intervalos de endereços válidos para cada blog de conteúdo. Por exemplo, o código CFU demonstra uma verificação para ver se o App1 (destinado a 0x9000) possui endereços que se sobrepõem ao App2, e assim por diante.

  5. UINT8 pData[MAX_UINT8] - Estes são os bytes brutos do bloco de imagem do firmware. No aplicativo do usuário, é tomado cuidado para colocar apenas length bytes no fluxo completo de bytes do bloco de conteúdo.

Não são utilizados campos de bits na estrutura de conteúdo, conforme a demonstração CFU no código fornecido.

O primeiro bloco

O primeiro bloco inicia o download do conteúdo do firmware. O firmware em execução tenta gravar o bloco na memória não volátil. É claro que o "bloco" de conteúdo contém informações sobre onde na memória o bloco deve ser escrito, quantos dados escrever e outros campos.

Cada dispositivo de destino componentID é diferente e há vários métodos para persistir os dados na memória. Por exemplo, um componentId pode exigir a gravação em flash interno, outro componentId pode gravar em um flash SPI externo ou outro pode utilizar o protocolo I2C de outro IC para atualizar sua imagem. A demonstração incluída neste documento destaca o uso de uma função chamada ICompFwUpdateBspWrite que cada firmware exclusivo deve implementar com conhecimento das funções de E/S de memória não voláteis subjacentes do alvo para o qual foi projetado.

Qualquer outro bloco, exceto o primeiro ou o último

O processo de aceitação de novos blocos continua quando o usuário-aplicativo entrega outro bloco, novamente com metadados na mensagem para o endereço de onde o bloco deve ser gravado, quantos bytes estão contidos e outros campos.

O firmware in situ trataria isso como um cenário de primeiro bloco.

No entanto, deve-se notar que, a qualquer momento, o sistema não consegue capturar e persistir o bloco na memória, cabe ao firmware in situ responder com um código de falha.

O último bloco

O último bloco apresenta um desafio apenas se o firmware in situ precisar fazer tarefas para validar a imagem que acabou de ser gravada na memória.

Primeiro, o último bloco é gravado na memória.

Em seguida, no mínimo, uma verificação CRC deve ser feita entre os dados já gravados na memória (do primeiro ao último bloco) em comparação com o campo CRC no último bloco. Resta a cada firmware de implementação saber como adquirir o CRC para a imagem baixada.

Tenha em mente que a execução da verificação CRC leva tempo. Ao contrário do fluxo normal de execução do CFU para oferta e submissão de blocos. A submissão do último bloco, se incluir uma verificação CRC, terá um certo atraso devido ao facto de que a verificação CRC está potencialmente a examinar uma grande região de memória. Dependendo do dispositivo alvo e de outros fatores, isso pode não ser uma preocupação.

Importante

A verificação CRC da imagem recebida é opcional e pode ser comentada. No entanto, devem ser postas em prática as melhores práticas para, pelo menos, adotar esta verificação. Recomenda-se vivamente que, nesta fase do processo de CFU, sejam realizadas outras ações para garantir a integridade da imagem descarregada. Algumas dessas ações podem incluir verificar uma parte "assinada" da imagem e/ou verificar cadeias de confiança de certificados ou outras abordagens de práticas recomendadas para garantir uma imagem de firmware segura. Estes são deixados para o desenvolvedor do firmware.

Limpeza após o último bloco

Agora que o último bloco foi gravado e a verificação CRC foi concluída, o firmware pode indicar uma falha caso alguma parte da validação falhe.

Caso contrário, a expectativa é que o processo CFU no firmware responda com um status bem-sucedido.

Reinicialização forçada verificada

O sinalizador de redefinição forçada na oferta é usado para determinar se o MCU do dispositivo-alvo deve passar por uma redefinição (redefinição definida pelo usuário).

Normalmente, quando uma redefinição é forçada, a intenção é fazer com que o MCU faça uma redefinição para fazer com que o banco de aplicativos mude. A atualização das variáveis persistentes para indicar em qual imagem de firmware inicializar durante a reinicialização é responsabilidade do desenvolvedor do firmware.