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.