Design Decisions
Informative
The rationale behind the SPI driver contract. This helps implementers get the behavior right; it is not itself a requirement. Each entry points to the requirement it explains.
Why does SpiDrv have no SpiIf interface layer?
Relates to CHI-GEN-MUST-15, CHI-GEN-MUST-16.
There is no standardized interface stack above an SPI driver the way CanIf sits above a CAN
driver, so a SpiIf layer would have had no implementation and no owner. SpiDrv therefore
declares its events as callouts in Spi.h, which the integrator implements in the generated
Spi_Callout_Stubs.c. SpiDrv already worked this way for chip-select control
(Spi_Callout_CsControl), so the change makes one model out of what were previously two. The
full reasoning, and why Can / Lin / Mem / Wdg keep their interface layer, is in
Design Decisions.
Note that Spi_OnCs is not a callout despite its name: it is a service the integration code
calls to notify the driver of an externally detected chip-select edge. Callouts are the functions
carrying the Callout infix.
Implication for implementers: publish the specification’s types in Spi_DriverTypes.h and the callout prototypes in Spi.h;
do not ship a SpiIf.h.
Why asynchronous, full-duplex transfers only?
Governs CHI-SPI-MUST-11.
Every transfer API is non-blocking: it starts (or stages) the transaction and returns, with
completion reported through Spi_Callout_OnTransactionEnd. A blocking, synchronous transfer would
stall the caller for the whole chip-select sequence, which is unacceptable on a small
single-threaded ECU. Half-duplex is left out because SPI is natively full-duplex and modeling both
directions in one buffer set keeps the API uniform.
Implication for implementers: drive transfers from the interrupt / poll handler and notify completion asynchronously; do not offer a blocking transfer.
Why a buffer set rather than a single buffer?
Relates to CHI-SPI-MUST-12.
A single chip-select sequence often carries structured data — a command, then a payload, sometimes
from different memory regions. Modeling the transaction as an array of chunks
(Spi_BufferSetType) lets the integration code describe that without concatenating into one
contiguous buffer, and lets a null chunk pointer mean “send default data” (TX) or “discard” (RX) for a length without allocating memory.
Implication for implementers: iterate the chunks within one CS sequence; honour a null
bufferDataPtr per chunk and per direction.
Why split Spi_Callout_CsControl from Spi_Callout_OnTransactionEnd?
Governs CHI-SPI-MUST-14, CHI-SPI-MUST-16.
The two callouts can fire at the same moment, but they target different environments:
Spi_Callout_CsControl is a system/integration concern (toggling a GPIO), while
Spi_Callout_OnTransactionEnd is a data-flow concern belonging to whichever component owns the
transfer. Keeping them separate lets the integration code own chip-select routing without entangling
it with the transaction notification path — and lets a hardware-controlled chip select skip the
callout entirely.
Implication for implementers: call Spi_Callout_CsControl only for GPIO-controlled chip selects,
and always as a distinct call from Spi_Callout_OnTransactionEnd.
Why does a target stage its data in advance and expose Spi_OnCs?
Relates to CHI-SPI-MUST-15, CHI-SPI-MUST-17, CHI-SPI-SHOULD-01.
A target cannot know when the controller will select it, so the integration code stages the response
ahead of time with Spi_StartTransaction; Spi_CancelTransaction lets it refresh stale data
before the transfer starts. Because some SPI hardware cannot detect chip-select deactivation,
Spi_OnCs lets the integration code notify the edge (typically from a GPIO interrupt) so the
driver can still close the transaction. The configured default_data covers the case where nothing is staged in time.
Implication for implementers: drive target completion from CS deactivation (hardware or
Spi_OnCs); fall back to default_data when no buffer is staged.
Why does Spi_Init take only NULL_PTR?
Relates to CHI-SPI-MUST-09.
Configuration variants are selected at build time, not passed at runtime. Requiring
configPtr == NULL_PTR keeps the interface stable and avoids implying a runtime variant-selection
mechanism the driver does not provide.
Implication for implementers: bind the active configuration at build time; reject a non-null
configPtr as a development error.