Host API

Header: zbc_host.h

The host API provides functions for processing semihosting requests from guest code. Use this when implementing a semihosting device in an emulator or virtual machine.

Types

struct zbc_host_mem_ops_t

Memory access callbacks for reading/writing guest memory.

You must implement all four callbacks. The ctx parameter is passed through from zbc_host_init().

Public Members

uint8_t (*read_u8)(uintptr_t addr, void *ctx)

Read single byte.

void (*write_u8)(uintptr_t addr, uint8_t val, void *ctx)

Write single byte.

void (*read_block)(void *dest, uintptr_t addr, size_t size, void *ctx)

Read block.

void (*write_block)(uintptr_t addr, const void *src, size_t size, void *ctx)

Write block.

struct zbc_host_state_t

Host state structure.

Initialize with zbc_host_init() before use.

Public Members

zbc_host_mem_ops_t mem_ops

Memory operation callbacks.

void *mem_ctx

Context for memory callbacks.

const struct zbc_backend_s *backend

Backend vtable.

void *backend_ctx

Backend-specific state.

uint8_t *work_buf

Working buffer for RIFF parsing.

size_t work_buf_size

Size of working buffer.

uint8_t guest_int_size

Guest integer size (from CNFG)

uint8_t guest_ptr_size

Guest pointer size (from CNFG)

uint8_t guest_endianness

Guest endianness (from CNFG)

uint8_t cnfg_received

1 if config known (CNFG or platform)

void (*on_proto_error)(void *ctx, int error_code)

Called when a request fails and the host cannot (or must not) write an ERRO chunk into guest memory — e.g.

the RIFF container was unparseable, or the guest did not pre-allocate an ERRO chunk.

The embedding device should latch the code into its ERROR_CODE register (0x1A-0x1B) and set STATUS bit 2 (ZBC_STATUS_PROTO_ERROR). The host library never writes to guest memory on these paths.

void *proto_error_ctx

Context for on_proto_error.

Functions

void zbc_host_init(zbc_host_state_t *state, const zbc_host_mem_ops_t *mem_ops, void *mem_ctx, const struct zbc_backend_s *backend, void *backend_ctx, uint8_t *work_buf, size_t work_buf_size)

Initialize host state.

Parameters:
  • state – Host state structure to initialize

  • mem_ops – Memory operation callbacks

  • mem_ctx – Context passed to memory callbacks

  • backend – Backend vtable (from zbc_backend_*() factory)

  • backend_ctx – Backend-specific state

  • work_buf – Working buffer for RIFF parsing

  • work_buf_size – Size of working buffer (recommended: 1024 bytes)

Example:

static zbc_host_state_t host;
static uint8_t work_buf[1024];
static zbc_ansi_insecure_state_t backend_state;

zbc_host_mem_ops_t mem_ops = {
    .read_u8 = my_read_u8,
    .write_u8 = my_write_u8,
    .read_block = my_read_block,
    .write_block = my_write_block
};

zbc_ansi_insecure_init(&backend_state);

zbc_host_init(&host, &mem_ops, NULL,
              zbc_backend_ansi_insecure(), &backend_state,
              work_buf, sizeof(work_buf));
void zbc_host_set_platform_config(zbc_host_state_t *state, int int_size, int ptr_size, int endianness)

Provide platform-supplied guest configuration defaults.

An emulator typically knows the guest CPU’s integer size, pointer size, and endianness without being told (address space width, target triple). Calling this makes the CNFG chunk optional: requests that omit CNFG use these values, and a CNFG chunk, if present, overrides them for the session. Hosts that never call this require CNFG on the first request and report ZBC_PROTO_ERR_MISSING_CNFG otherwise.

Call after zbc_host_init() and before the first zbc_host_process().

Parameters:
  • state – Initialized host state

  • int_size – Guest integer size in bytes (1, 2, 4, or 8)

  • ptr_size – Guest pointer size in bytes (1, 2, 4, or 8)

  • endianness – ZBC_ENDIAN_LITTLE or ZBC_ENDIAN_BIG

Example:

/* Emulator knows the guest CPU: CNFG becomes optional */
zbc_host_set_platform_config(&host, /*int_size=*/2, /*ptr_size=*/2,
                             ZBC_ENDIAN_LITTLE);
void zbc_host_set_proto_error_cb(zbc_host_state_t *state, void (*cb)(void *ctx, int error_code), void *ctx)

Register the device notification callback for protocol errors.

See the on_proto_error field documentation in zbc_host_state_t. Optional: without a callback, register-channel diagnostics are dropped (failures are still logged via ZBC_LOG).

Parameters:
  • state – Initialized host state

  • cb – Callback invoked with a ZBC_PROTO_ERR_* code (may be NULL)

  • ctx – Context passed to the callback

Example:

/* Latch register-channel diagnostics into the device registers */
static void on_proto_error(void *ctx, int code) {
    my_device_t *dev = ctx;
    dev->error_code = (uint16_t)code;       /* ERROR_CODE @ 0x1A */
    dev->status |= ZBC_STATUS_PROTO_ERROR;  /* STATUS bit 2 */
}

zbc_host_set_proto_error_cb(&host, on_proto_error, &my_device);
int zbc_host_process(zbc_host_state_t *state, uintptr_t riff_addr)

Process a semihosting request.

Call this when the guest writes to DOORBELL. The function:

  1. Reads the RIFF request from guest memory

  2. Parses the CNFG chunk (first request only)

  3. Parses the CALL chunk and sub-chunks

  4. Dispatches to the appropriate backend function

  5. Writes the RETN (or ERRO) chunk back to guest memory

After this returns, set STATUS bit 0 (RESPONSE_READY) in your device register emulation.

Parameters:
  • state – Initialized host state

  • riff_addr – Guest address of RIFF buffer

Returns:

ZBC_OK on success, error code on failure

Example:

void on_doorbell_write(uintptr_t riff_ptr) {
    zbc_host_process(&host, riff_ptr);
    /* Response (or register error) is complete: set RESPONSE_READY */
    my_device.status |= ZBC_STATUS_RESPONSE_READY;
}

Helper Functions

intptr_t zbc_host_read_guest_int(const zbc_host_state_t *state, const uint8_t *data, size_t size)

Read an integer from guest-endian data.

Converts from guest endianness to host integer.

Parameters:
  • state – Host state (for guest endianness)

  • data – Pointer to integer data

  • size – Size of integer in bytes

Returns:

Integer value

void zbc_host_write_guest_int(const zbc_host_state_t *state, uint8_t *data, uintptr_t value, size_t size)

Write an integer in guest-endian format.

Converts from host integer to guest endianness.

Parameters:
  • state – Host state (for guest endianness)

  • data – Destination buffer

  • value – Integer value to write

  • size – Size of integer in bytes

Work Buffer Sizing

The work buffer should be large enough to hold the largest RIFF request plus space for the response. Recommended sizes:

  • Minimum: 256 bytes (most syscalls)

  • Typical: 1024 bytes (comfortable for file operations)

  • Large reads: Match the largest expected SYS_READ count plus overhead

For SYS_READ operations, the buffer is split: half for reading the request, half for the read data. So a 1024-byte buffer supports reads up to ~500 bytes.