Client API

Header: zbc_client.h

The client API provides functions for guest/embedded code to make semihosting calls to the host system.

Types

struct zbc_client_state_t

Client state structure.

Initialize with zbc_client_init() before use.

Public Members

volatile uint8_t *dev_base

Pointer to device registers.

uint8_t cnfg_sent

1 if CNFG chunk has been sent

uint8_t int_size

sizeof(int) on this platform

uint8_t ptr_size

sizeof(void*) on this platform

uint8_t endianness

ZBC_ENDIAN_LITTLE or ZBC_ENDIAN_BIG.

void (*doorbell_callback)(void*)

For testing.

void *doorbell_ctx

Context for doorbell callback.

const struct zbc_transport_s *transport

Transport vtable.

zbc_client_init() selects the RIFF/doorbell device transport; assign a different vtable before the first zbc_call() to override (this is the entire selection-override mechanism — there is no dedicated API).

void *transport_ctx

Transport-private context.

struct zbc_response_t

Response from a semihosting call.

Populated by zbc_call() and zbc_parse_response().

Public Members

int result

Syscall return value.

int error_code

Errno value from host.

const uint8_t *data

Pointer to DATA payload (if any)

size_t data_size

Size of DATA payload.

int is_error

1 if ERRO chunk received

int proto_error

Protocol error code from ERRO.

Initialization

void zbc_client_init(zbc_client_state_t *state, volatile void *dev_base)

Initialize client state with device base address.

The function detects the platform’s integer size, pointer size, and endianness automatically using compile-time configuration macros.

Parameters:
  • state – Client state structure to initialize

  • dev_base – Memory-mapped device base address

int zbc_client_check_signature(const zbc_client_state_t *state)

Check if a semihosting device is present by reading the signature.

Call this before making semihosting calls to verify the device exists.

Parameters:

state – Initialized client state

Returns:

ZBC_OK if device signature matches “SEMIHOST”, ZBC_ERR_NULL_ARG if state or dev_base is NULL, ZBC_ERR_DEVICE_ERROR if signature mismatch

void zbc_client_reset_cnfg(zbc_client_state_t *state)

Reset the CNFG sent flag, forcing resend on next call.

Normally the CNFG chunk is sent only once. Use this if you need to resend configuration (e.g., after device reset).

Parameters:

state – Initialized client state

Making Calls

int zbc_call(zbc_response_t *response, zbc_client_state_t *state, void *buf, size_t buf_size, int opcode, uintptr_t *args)

Execute a semihosting syscall.

This is the main entry point for making semihosting calls. The function:

  1. Builds a RIFF request from the opcode table

  2. Submits the request to the device (synchronous)

  3. Parses the response into the response structure

On success, check response->result for the syscall return value and response->error_code for the host errno.

Parameters:
  • response[out] Receives parsed response (result, errno, data pointer)

  • state – Initialized client state

  • buf – RIFF buffer (caller-provided)

  • buf_size – Size of buffer in bytes

  • opcode – SH_SYS_* opcode from zbc_protocol.h

  • args – Array of arguments (layout depends on opcode), may be NULL

Returns:

ZBC_OK on success, ZBC_ERR_* on protocol/transport error

Example:

zbc_response_t response;
uintptr_t args[1];
args[0] = (uintptr_t)"Hello\n";

int rc = zbc_call(&response, &client, buf, sizeof(buf),
                  SH_SYS_WRITE0, args);
if (rc != ZBC_OK) {
    /* Protocol error */
}
uintptr_t zbc_semihost(zbc_client_state_t *state, uint8_t *riff_buf, size_t riff_buf_size, uintptr_t op, uintptr_t param)

ARM-compatible semihost entry point.

This is a thin wrapper around zbc_call() that accepts the ARM-style parameter block format (op, pointer-to-args) used by picolibc and newlib.

Use this to implement sys_semihost() for libc integration.

Parameters:
  • state – Initialized client state

  • riff_buf – RIFF buffer

  • riff_buf_size – Size of buffer

  • op – SH_SYS_* opcode

  • param – Pointer to args array (cast from uintptr_t*)

Returns:

Syscall result, or (uintptr_t)-1 on error

Use this to implement sys_semihost() for libc integration:

uintptr_t sys_semihost(uintptr_t op, uintptr_t param)
{
    return zbc_semihost(&client, riff_buf, sizeof(riff_buf), op, param);
}

Low-Level Functions

int zbc_client_submit(zbc_client_state_t *state, void *buf, size_t size)

Submit a RIFF request to the semihosting device.

This writes the buffer address to RIFF_PTR and triggers DOORBELL. The host processes the request synchronously - when this function returns, the response is already in the RIFF buffer. Most users should use zbc_call() instead.

Parameters:
  • state – Initialized client state

  • buf – RIFF buffer containing the request

  • size – Size of request data

Returns:

ZBC_OK on success, error code on failure

int zbc_parse_response(zbc_response_t *response, const uint8_t *buf, size_t capacity, const zbc_client_state_t *state)

Parse response from RIFF buffer.

Extracts the result, errno, and data from a RETN or ERRO chunk. Most users should use zbc_call() instead.

Parameters:
  • response[out] Receives parsed response

  • buf – RIFF buffer containing the response

  • capacity – Buffer capacity

  • state – Client state (for int_size/endianness)

Returns:

ZBC_OK on success, ZBC_ERR_PARSE_ERROR on failure

Configuration Macros

These macros can be overridden at compile time to configure the client for non-standard platforms:

ZBC_CLIENT_INT_SIZE

Size of int in bytes. Default: sizeof(int)

ZBC_CLIENT_PTR_SIZE

Size of pointers in bytes. Default: sizeof(void *)

ZBC_CLIENT_ENDIANNESS

Endianness (ZBC_ENDIAN_LITTLE or ZBC_ENDIAN_BIG). Default: detected from __BYTE_ORDER__