iolinki-master Architecture
Source snapshot: docs/ARCHITECTURE.md,
d23fd3034c02.
The companion device stack is iolinki.
iolinki-master is a standalone, hardware-independent IO-Link master protocol
library. The design goal is a portable embedded master core with clean boundaries:
no heap, caller-owned storage, a public API that hides private state, and no
coupling to the device-stack singleton. This document describes the layering. It
does not restate the build order — that is ROADMAP.md — and it
cross-references the PHY contract in PHY_BOUNDARY.md rather
than duplicating it.
1. Repository and shared-helper boundary
The master is a sibling of the device stack, not a fork of it. It compiles only
the narrow shared helper sources it needs from a local iolinki checkout — crc.c
and frame.c (CRC6/checksum and frame encode/decode) — into its own build via
CMakeLists.txt. It must never add_subdirectory() the full device stack or call
device singleton entry points (iolink_init(), iolink_process()). The device
checkout is located through -DIOLINKI_DEVICE_DIR and defaults to a sibling
../iolinki.
2. Public API vs private state
The central architectural move: callers own opaque storage; private state lives
under src/.
- Public users allocate
iolink_master_port_t(per port) oriolink_master_controller_t(per multi-port controller). These are unions sized by audited budgets —IOLINK_MASTER_PORT_STORAGE_SIZE(1280 B) andIOLINK_MASTER_CONTROLLER_STORAGE_SIZE(32 B) — giving embedded integrators a fixed, auditable RAM ceiling and keeping the ABI heap-free and caller-owned. - The real layout lives in
src/master_internal.hand is reached only throughiolink_master_port_state()insidesrc/master_*.c; the public header (include/iolinki_master/master.h) exposes none of it. - Every public function has a documented return contract expressed as named result
codes (
IOLINK_MASTER_STATUS_OK,IOLINK_MASTER_STATUS_PENDING,IOLINK_MASTER_ERR_*, and the per-domain..._ISDU_ERR_*/..._SIO_ERR_*/..._PARAM_ERR_*enums), never bare magic integers.
This boundary matters because every later feature becomes harder to change once external users compile against the storage sizes and result codes.
3. Protocol core
src/master_port.c is the core engine. It owns:
- Port lifecycle:
INACTIVE → STARTUP → PREOPERATE → OPERATE, plusERROR(iolink_master_state_t), driven byiolink_master_process/iolink_master_poll_rx. - Startup: wake-up request, Type-0 idle exchange, operate transition command,
and OPERATE entry; fixed baudrate or auto-baud scan across COM3→COM2→COM1 with a
configurable per-baud
wake_retry_limit. - Cyclic process data: configured PD in/out for M-sequence Types 0, 1_1/1_2/1_V,
2_1/2_2/2_V (
iolink_master_m_seq_type_t), exposed viaiolink_master_set_pd_out/iolink_master_get_pd_in/iolink_master_get_od_status. - RX/retry: byte accumulation, checksum/CRC verification, bounded retry, and the
diagnostics counters (
checksum_errors,response_timeouts,cycle_slips, jitter, derived link quality).
Frame encode/decode and CRC come from the shared ../iolinki helpers; the core adds
master-side sequencing on top.
4. Timing and scheduler
The core does not own a clock — the caller does. Timing is expressed as explicit monotonic 100µs inputs so it is testable without wall-clock sleeps:
iolink_master_tick/iolink_master_tick_eventtake an explicit tick event (IOLINK_MASTER_TICK_CYCLE_DUE,..._RESPONSE_TIMEOUT,..._NONE).iolink_master_tick_at(port, event, now_100us)appliesmin_cycle_timepacing and response-deadline scheduling against caller-supplied time.iolink_master_get_next_tick_time(port, now_100us, out_next_100us)tells the caller-owned hardware timer when the port is next due.iolink_master_get_timingexposes a read-only snapshot of scheduler-visible state (iolink_master_timing_t).
Response timeout (response_timeout_100us) is kept distinct from cycle spacing
(min_cycle_time); a zero response timeout falls back to min_cycle_time. The
MasterCycleTime octet (time-base + multiplier) is decoded to 100µs units by
iolink_master_decode_min_cycle_time_100us for both validation and pacing. This is
the current architecture-priority layer — see ROADMAP.md §4.
5. PHY adapter boundary
The core is board-agnostic. Board support enters only through iolink_phy_api_t
(shared PHY struct: init/set_mode/set_baudrate/send/recv_byte, plus
optional set_cq_line/detect_wakeup/get_voltage_mv/is_short_circuit) and the
fallible adapter hooks carried in iolink_master_config_t
(set_mode_checked, set_baudrate_checked, flush_rx, prepare_tx, prepare_rx,
read_cq_line / read_cq_line_checked, wake_up). The PHY is retained by
pointer and must outlive the port. iolink_master_validate_phy_contract checks
that a PHY/config pair is complete enough for real hardware. The physical 80µs WURQ
pulse and t_WU/t_REN/TDMT startup timing are the adapter's responsibility and
are unverified on silicon. Full contract: PHY_BOUNDARY.md;
implementing an adapter: PORTING.md.
6. Controller / multi-port runtime
src/master_controller.c initializes and drives an array of ports. It fans one
controller timestamp out to per-port cycle pacing
(iolink_master_controller_tick_at), applies per-port response deadlines, supports
independent per-port modes/timings, and exposes port count and per-port access
(iolink_master_controller_get_port_count / ..._get_port). A failing port
returns its result without corrupting siblings. The controller computes the earliest
next due time across ports (iolink_master_controller_get_next_tick_time). It is a
fan-out helper, not yet a full port-policy scheduler.
7. Services layer
Services sit above cyclic transport in src/master_isdu.c (with identity in
src/master_parameters.c), kept independent of startup policy:
- ISDU read/write with segmentation into fixed buffers.
- Direct Parameter Page 1 parse/apply/get/validate and capability-driven config
selection (
src/master_parameters.c), plus VendorID/DeviceID inspection. - Events: code/detail read and ack, with optional rising-edge event-pending and per-event dispatch callbacks.
- Data Storage: read/write/restore with readback verification.
- Block parameterization: download/upload/store system commands and a write-with-readback sequence.
- SIO DI/DQ and dynamic mode transitions in
src/master_sio.c.
8. Design principles
- No dynamic memory anywhere in
src/orinclude/— all state is in caller-owned fixed storage. - No board headers in
src/master_*.c; no sleeping inside core calls. - Fixed-width types (
<stdint.h>) throughout for portability. - Named result codes, checked at every call site.
Note on maturity: the core is protocol- and simulation-validated only. It is not
timing-certified, hardware-validated, or IO-Link-conformance validated — see
IMPLEMENTATION_STATUS.md.