Target support

Kernel layer

Overview

The kernel layer is comprised of

  1. an abstract interface and associated support captured by

    ${FIAT_PATH_REPO}/src/fiat/target/kernel/kernel.h
    ${FIAT_PATH_REPO}/src/fiat/target/kernel/kernel.c
    
  2. a concrete implementation captured by

    ${FIAT_PATH_REPO}/src/fiat/target/kernel/imp/kernel_imp.h
    ${FIAT_PATH_REPO}/src/fiat/target/kernel/imp/kernel_imp.c
    

    plus associated configuration

    ${FIAT_PATH_REPO}/src/fiat/target/kernel/imp/kernel_imp.conf
    

noting that:

  • The kernel state is reflected by a set of registers, each identified by an 8-bit index

     8 7 6 5 4 3 2 1
    +-+-+-+-+-+-+-+-+
    | | | | | | | | |
    +-+-+-+-+-+-+-+-+
     ^ \___________/
     |       |
     |       |
     |       +--------- address
     +-----------------   class : 0 = GPR, 1 = SPR
    

    meaning the set is divided into two classes (using the MSB): General-Purpose Registers (GPRs) are defined by the kernel, whereas Special-Purpose Registers (SPRs) are defined by the driver.

  • Each register has an associated 8-bit type, i.e., some meta-data

    8 7 6 5 4 3 2 1
    +-+-+-+-+-+-+-+-+
    | | | | | | | | |
    +-+-+-+-+-+-+-+-+
              ^ ^ ^ 
              | | | 
              | | | 
              | | +---    read access : 0 = false, 1 = true
              | +-----   write access : 0 = false, 1 = true
              +------- content length : 0 = fixed, 1 = variable
    

    noting that the terms Read-Only (RO), Write-Only (WO), and Read-Write (RW), are used as a short-hand to describe read and/or write access cases (viewed from the perspective of an external client using the target implementation).

  • The standard SPRs defined are as follows:

    • index 0x80 = 128, i.e., SPR address 0x00 = 0 is called ret: this is an RO register used for a return code,

    • index 0x81 = 129, i.e., SPR address 0x01 = 1 is called tsc: this is an RO register used for a time-stamp counter.

API

ret_t kernel(int op, kernel_reg_t *spr, kernel_reg_t *gpr)

The kernel functionality.

Parameters:
  • op – an operation identifier (or opcode), allowing selection of sub-functionality.

  • spr – the special-purpose register set.

  • gpr – the general-purpose register set.

Returns:

a return code indicating failure (e.g., EXIT_FAILURE) or success (e.g., EXIT_SUCCESS).

ret_t kernel_prologue_major(int op, kernel_reg_t *spr, kernel_reg_t *gpr)

A function that performs “major” initialisation, optionally, and manually invoked before the kernel functionality (e.g., to allocate memory).

Parameters:
  • op – an operation identifier (or opcode), allowing selection of sub-functionality.

  • spr – the special-purpose register set.

  • gpr – the general-purpose register set.

Returns:

a return code indicating failure (e.g., EXIT_FAILURE) or success (e.g., EXIT_SUCCESS).

ret_t kernel_epilogue_major(int op, kernel_reg_t *spr, kernel_reg_t *gpr)

A function that performs “major” finalisation, optionally, and manually invoked after the kernel functionality (e.g., to deallocate memory).

Parameters:
  • op – an operation identifier (or opcode), allowing selection of sub-functionality.

  • spr – the special-purpose register set.

  • gpr – the general-purpose register set.

Returns:

a return code indicating failure (e.g., EXIT_FAILURE) or success (e.g., EXIT_SUCCESS).

void kernel_prologue_minor(int op, kernel_reg_t *spr, kernel_reg_t *gpr)

A function that performs “minor” initialisation, automatically invoked before the kernel functionality.

Parameters:
  • op – an operation identifier (or opcode), allowing selection of sub-functionality.

  • spr – the special-purpose register set.

  • gpr – the general-purpose register set.

void kernel_epilogue_minor(int op, kernel_reg_t *spr, kernel_reg_t *gpr)

A function that performs “minor” finalisation, automatically invoked after the kernel functionality.

Parameters:
  • op – an operation identifier (or opcode), allowing selection of sub-functionality.

  • spr – the special-purpose register set.

  • gpr – the general-purpose register set.

Driver layer

Overview

The driver layer essentially implements the interface between the client (i.e., the user) and the kernel (i.e., the functionality being used, as part of the target implementation), using features in and so support from the the board layer. For example, it is responsible for

  • management of UART-based communication protocol, that e.g., allows transfer of data into and from the kernel register set and invocation of kernel functionality, and

  • management of GPIO pins to, e.g., act as a trigger signal.

Irrespective of driver type, the generic usage model is captured by

Client                                     Target
======================================================================
:
wr index, data           ------ cmd -----> kernel_gprs[ index ] = data
:                        <----- ack -----
:
kernel_prologue op       ------ cmd -----> kernel_prologue_major( op )
:                        <----- ack -----
:
kernel          op, rep  ------ cmd -----> kernel_prologue_minor( op ) -+
:                                          trigger = 1                  |   
:                                          kernel               ( op )  |-- repetition     0
:                                          trigger = 0                  |   
:                                          kernel_epilogue_minor( op ) -+
:
:                                          kernel_prologue_minor( op ) -+
:                                          trigger = 1                  |   
:                                          kernel               ( op )  |-- repetition     1
:                                          trigger = 0                  |   
:                                          kernel_epilogue_minor( op ) -+
:
:                                          :
:
:                                          kernel_prologue_minor( op ) -+
:                                          trigger = 1                  |   
:                                          kernel               ( op )  |-- repetition rep-1
:                                          trigger = 0                  |   
:                                          kernel_epilogue_minor( op ) -+
:                        <----- ack -----
:
kernel_epilogue op       ------ cmd -----> kernel_epilogue_major( op )
:                        <----- ack -----
:
rd index                 ------ cmd -----> data = kernel_gprs[ index ]
:                        <----- ack -----
:
======================================================================

where, read top-to-bottom, the idea is that

  • the client (optionally) issues the wr command to write any input data,

  • the client (optionally) issues the kernel_prologue command to perform “major” initialisation,

  • the client issues the kernel command to invoke the kernel some number of times; in each repetition, it

    • performs some “minor” initialisation,

    • sets the GPIO trigger pin to 1,

    • executes the the kernel itself,

    • sets the GPIO trigger pin to 0,

    • performs some “minor” finalisation,

  • the client (optionally) issues the kernel_epilogue command to perform “major” finalisation,

  • the client (optionally) issues the rd command to read any output data.

Notation

The following notation is used to specify a given protocol:

  • communicated messages are structured using the following types:

    1. a char, i.e., an 8-bit word representing an unsigned integer,

    2. a byte, i.e., an 8-bit word representing an ASCII character,

    3. a n-element byte sequence, with elements communicated in little-endian order,

    4. a variable-length unsigned integer vint (or varint, which is itself a byte sequence,

  • <T:X> denotes a field X of type T,

  • [T:X^n] denotes a field X which is an n-element sequence whose i-th element X[i] is of type T,

  • if a field has a fixed value Y, it is specified, e.g., as <T:X>=Y.

  • || denotes concatenation.

  • {X} denotes optionality, e.g., that X may or may not form part of a message depending on some configuration.

The binary driver

This driver implementation uses a binary protocol: the focus is on machine-friendly, programmatic interaction, which e.g., emphasises efficiency in support of “bulk” target implementation use-cases.

Communication is stream-based, with request and acknowledge messages are represented using a sequence of raw bytes. Note that the binary driver includes a CRC-based checksum with every message (computed over all preceding, non-CRC content): this contrast with the text driver, which does not. Based on this, the following command set is supported:

  • Ping.

    • req syntax: <byte:req>='!' || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:crc^2>

  • Reset.

    • req syntax: <byte:req>='*' || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:crc^2>

  • Query version.

    • req syntax: <byte:req>='$' || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:patch> || <byte:minor> || <byte:major> || <byte:crc^2>

  • Query identifier of, or “nameof” a register.

    • req syntax: <byte:req>='"' || <byte:index> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <vint:size> || [byte:data^size] || <byte:crc^2>

  • Query allocated size of, or “sizeof” a register (measured in bytes).

    • req syntax: <byte:req>='|' || <byte:index> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <vint:size> || <byte:crc^2>

  • Query used size of, or “usedof” a register (measured in bytes).

    • req syntax: <byte:req>='#' || <byte:index> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <vint:size> || <byte:crc^2>

  • Query type of, or “typeof” a register.

    • req syntax: <byte:req>='?' || <byte:index> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:type> || <byte:crc^2>

  • Transfer content (i.e., write) into a register.

    • req syntax: <byte:req>='>' || <byte:index> || <vint:size> || [byte:data^size] || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:crc^2>

  • Transfer content (i.e., read) from a register.

    • req syntax: <byte:req>='<' || <byte:index> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <vint:size> || [byte:data^size] || <byte:crc^2>

  • Execute the kernel.

    • req syntax: <byte:req>='=' || <byte:op> || <vint:rep> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:crc^2>

    • note: op is an operation identifier passed to the implementation, and rep is a repeat count

  • Execute the kernel prologue (or “major” initialisation).

    • req syntax: <byte:req>='[' || <byte:op> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:crc^2>

    • note: op is an operation identifier passed to the implementation

  • Execute the kernel epilogue (or “major” finalisation).

    • req syntax: <byte:req>=']' || <byte:op> || <byte:crc^2>

    • ack syntax:

      • on failure, <byte:ack>='-' || <byte:err> || <byte:crc^2>

      • on success, <byte:ack>='+' || <byte:crc^2>

    • note: op is an operation identifier passed to the implementation

The text driver

This driver implementation uses a text protocol: the focus is on human-friendly interaction, which e.g., emphasises usability in support of “ad-hoc” target implementation use-cases such as debugging.

In concept at least, it is similar to the ChipWhisperer SimpleSerial protocol. Communication is line-based, with request and acknowledge messages are represented using a sequence of ASCII characters terminated with a Carriage Return (CR) only; a byte is represented by 2 hexadecimal characters (per the printf format specifier %02X). Based on this, the following command set is supported:

  • Ping.

    • req syntax: <char:req>='!'

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+'

  • Reset.

    • req syntax: <char:req>='*'

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+'

  • Query version.

    • req syntax: <char:req>='$'

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+' <byte:patch> <byte:minor> <byte:major>

  • Query identifier of, or “nameof” a register.

    • req syntax: <char:req>='"' <byte:index>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+' <vint:size> [byte:data^size]

  • Query allocated size of, or “sizeof” a register (measured in bytes).

    • req syntax: <char:req>='|' <byte:index>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+' <vint:size>

  • Query used size of, or “usedof” a register (measured in bytes).

    • req syntax: <char:req>='#' <byte:index>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+' <vint:size>

  • Query type of, or “typeof” a register.

    • req syntax: <char:req>='?' <byte:index>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+' <byte:type>

  • Transfer content (i.e., write) into a register.

    • req syntax: <char:req>='>' <byte:index> <vint:size> [byte:data^size]

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+'

  • Transfer content (i.e., read) from a register.

    • req syntax: <char:req>='<' <byte:index>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+' <vint:size> [byte:data^size]

  • Execute the kernel.

    • req syntax: <char:req>='=' <byte:op> <vint:rep>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+'

  • Execute the kernel prologue (or “major” initialisation).

    • req syntax: <char:req>='[' <byte:op>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+'

  • Execute the kernel epilogue (or “major” finalisation).

    • req syntax: <char:req>=']' <byte:op>

    • ack syntax:

      • on failure, <char:ack>='-' <byte:err>

      • on success, <char:ack>='+'

Board layer

Overview

The board layer is comprised of

  1. an abstract interface and associated support captured by

    ${FIAT_PATH_REPO}/src/fiat/target/board/board.h
    ${FIAT_PATH_REPO}/src/fiat/target/board/board.c
    
  2. a concrete implementation captured by

    ${FIAT_PATH_REPO}/src/fiat/target/board/imp/board_imp.h
    ${FIAT_PATH_REPO}/src/fiat/target/board/imp/board_imp.c
    

    plus associated configuration

    ${FIAT_PATH_REPO}/src/fiat/target/board/imp/Dockerfile.in
    ${FIAT_PATH_REPO}/src/fiat/target/board/imp/Makefile.in
    

noting that although it essentially acts as a Hardware Abstraction Layer (HAL), it is potentially as simple as a shim layer over a platform-specific HAL.

API

int board_init()

Initialise the board.

Returns:

a flag indicating failure (false) or success (true).

void board_uart_wr(byte x)

Write a byte to the UART, blocking until successful.

Parameters:
  • x – the value to write.

byte board_uart_rd()

Read a byte from the UART, blocking until successful.

Returns:

the value read.

void board_gpio_wr(pin_t p, int x)

Write a value to (or assert a value on) a GPIO pin.

Parameters:
  • p – the GPIO pin identifier.

  • x – the value to write.

int board_gpio_rd(void)

Read a value from (or sample a value from) a GPIO pin.

Parameters:
  • p – the GPIO pin identifier.

Returns:

the value read.

tsc_t board_tsc_rd()

Read (or sample) the time-stamp counter.

Returns:

the value read.

int board_rng_rd(byte *r, int n)

Read (or sample) n bytes of randomness.

Parameters:
  • r – the destination buffer.

Returns:

the number of bytes read (and then written into r, which may be less than n).

Shared

enum req_t

An enumeration that captures communicated request tags.

enum ack_t

An enumeration that captures communicated acknowledgement tags.

enum pin_t

An enumeration that captures GPIO pin identifiers.

type ret_t

A type that captures return code values.

type tsc_t

A type that captures Time-Stamp Counter (TSC) values.