Before you start#
Read USB core and drivers, platform firmware, and DMA ownership. Source references use Linux v6.12, a historical baseline. Hardware wiring, voltage, register semantics and controller capabilities vary. The lab reads metadata only: it does not scan a bus or access peripheral registers.
Learning goals#
Compare I2C and SPI transfer contracts, follow a real I2C/regmap probe and error path, distinguish logical GPIO state from voltage and pin muxing, and reason about register byte order without assuming a board's electrical design.
Source walkthrough#
- I2C
- Adapter transfers addressed messages; return counts messages.
- SPI
- Controller executes a message of transfers with chip-select and mode.
- GPIO / pinctrl
- Logical polarity, sleeping access and physical mux are separate.
- Regmap
- Transport and register policy require checked errors and lifetimes.
An I2C adapter represents a controller/bus and an i2c_client represents a peripheral at an address. Most devices use 7-bit addresses; 10-bit addressing is a separate protocol mode and requires suitable controller and device support. An i2c_msg contains address, flags, length and buffer for one message. A combined transaction can write a register address, issue a repeated START and read data without a STOP between the messages, when supported and requested. Whether a particular peripheral needs repeated START, permits STOP, or has read side effects comes from its protocol and board documentation, not from the generic I2C API.
__i2c_transfer, Linux v6.12, GPL-2.0-or-later, validates adapter operations, messages, suspension and quirks. One branch is:
if (!adap->algo->master_xfer) {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
The core checks that the adapter implements the raw I2C transfer callback before attempting it. A missing callback returns -EOPNOTSUPP, not a successful zero-byte peripheral read. i2c_transfer supplies the appropriate adapter bus locking around the transfer; the lower helper's caller contract must be respected. Adapter quirks can limit message count, length or combined-transfer shapes. A positive return counts messages transferred, not bytes; the caller expecting two messages must check for exactly two. A negative errno, a short message count, NACK, arbitration loss or suspended controller must be handled according to the API and device. SMBus helpers express standardized transaction forms and may be implemented natively or emulated where supported; they are not equivalent to arbitrary I2C messages.
In i2c_transfer, Linux v6.12, GPL-2.0-or-later, the public wrapper takes the adapter bus lock around the lower helper (a comment about fault reporting is omitted):
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int ret;
/* ... fault-reporting comment omitted ... */
ret = __i2c_lock_bus_helper(adap);
if (ret)
return ret;
ret = __i2c_transfer(adap, msgs, num);
i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);
return ret;
}
The excerpt shows the lock, the call to __i2c_transfer and the unlock. The return value is the lower helper result, so the message-count rule above still applies. The message validation the chapter describes is in __i2c_transfer, which this excerpt does not show.
A real I2C/regmap driver#
The ADXL345 I2C wrapper in drivers/iio/accel/adxl345_i2c.c, Linux v6.12, GPL-2.0-only, configures eight-bit register and value widths. Its probe includes:
regmap = devm_regmap_init_i2c(client, &adxl345_i2c_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(&client->dev, PTR_ERR(regmap), "Error initializing regmap\n");
The first statement creates a managed regmap for this client and configuration. The error-pointer test returns the real error, including possible deferral, instead of passing an invalid map to the shared core. The next statement in the function calls adxl345_core_probe with that map. In adxl345_core.c, the core allocates an IIO device, obtains match data, performs applicable configuration and reads the device ID, returning errors when those stages fail. The shared driver is an example, not a license to issue those register writes on a host. A managed regmap releases its registered resources at the relevant lifecycle point; the driver still needs to handle IIO registration, in-flight accesses and device removal correctly.
Regmap abstracts register access over a selected transport. Its configuration describes register/value widths, endianness, access policy and optionally a cache. Volatile registers generally must not be served as stable cached values, and precious or read-sensitive registers need special treatment to avoid unintended reads. A read can clear an interrupt status bit or advance a FIFO on some hardware; a convenient register dump can therefore change device state. Regmap locking serializes operations according to its configuration, but it does not replace every higher-level device lock or guarantee an external master cannot alter hardware. See drivers/base/regmap/regmap.c and include/linux/regmap.h.
SPI messages and completion#
SPI uses a controller and spi_device objects, with chip select, mode, clock rate and device-specific framing. A spi_message contains one or more spi_transfer entries; each can describe transmit and receive buffers, length and other parameters. SPI commonly shifts in and out simultaneously, but whether a peripheral's protocol uses both directions for meaningful data is device-specific. Chip-select behavior between transfers, maximum message length, clock polarity/phase and word size depend on controller support and message configuration. A chip-select number is not an I2C address.
spi_sync waits for a message under its API's sleepable context; asynchronous submission returns before completion and the message, transfer descriptors and buffers must remain valid until the completion callback. The callback context and controller implementation determine what operations are safe there; do not assume it can sleep. DMA may be used by a controller but requires its own mapping, ownership and completion rules. A transfer can fail after some data was clocked, so the final message status and actual length must be checked. Cleanup on probe or removal must stop new messages and coordinate in-flight completion before freeing private state. See drivers/spi/spi.c.
In spi_sync, Linux v6.12, GPL-2.0-or-later, the synchronous entry point holds the controller bus-lock mutex around the shared implementation:
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
int ret;
mutex_lock(&spi->controller->bus_lock_mutex);
ret = __spi_sync(spi, message);
mutex_unlock(&spi->controller->bus_lock_mutex);
return ret;
}
The mutex is the controller bus lock, held across the synchronous call. The excerpt does not show __spi_sync, so it does not establish how the final status and actual length reach the caller, and the asynchronous path follows a different contract.
GPIO descriptors and pin control#
A GPIO descriptor identifies a logical line managed by gpiolib. Consumer APIs can account for active-low polarity: a logical active value of 1 can correspond to a low electrical level on a line described as active-low. The physical voltage, drive mode, external pull resistor and safe state still depend on board wiring and pin configuration. Some GPIO controllers are memory-mapped and can be accessed in atomic contexts through suitable APIs; a GPIO expander on I2C or SPI may sleep. Code must use gpiod_cansleep and the corresponding sleepable accessors when required, rather than calling a sleepable bus transaction from an interrupt handler or under a spinlock. See drivers/gpio/gpiolib.c.
Pinctrl configures multiplexing and pin electrical properties such as bias or drive strength where supported. A pin can be routed to a UART, SPI function or GPIO function; obtaining a GPIO descriptor does not automatically prove that the board's selected mux and electrical state are correct. Drivers and the core can select named default and sleep states under their lifecycle rules. A state change can fail or affect pins shared with another function, so probe and suspend paths must check results and respect ownership. See drivers/pinctrl/core.c.
Consider a synthetic sensor whose 16-bit register sends bytes 0x12 then 0x34. If its binding says the first byte is most significant, the value is 0x1234 (4660); if it says little-endian, the same bytes represent 0x3412 (13330). The generic bus does not determine the sensor's encoding. Suppose a reset GPIO is active-low: logical 1 means asserted and can drive low, while logical 0 means deasserted. A board may invert the line externally or require particular voltage and timing; these examples do not authorize writing a real pin or calibrating a sensor without its datasheet and board schematic.
Synthetic teaching data, not a device capture.
Select a field to connect its position, bytes, and meaning.
2 bytes in file order. Offsets start at zero.
Selected: First byte. Big-endian: high byte, 0x1234 = 4660. Little-endian: low byte, 0x3412 = 13330.
Read the byte table
| Offset | Field | Hex bytes | Interpretation |
|---|---|---|---|
| 0 | First byte | 12 | Big-endian: high byte, 0x1234 = 4660. Little-endian: low byte, 0x3412 = 13330 |
| 1 | Second byte | 34 | Big-endian: low byte. Little-endian: high byte |
Combined transfer and failure reasoning#
Imagine an I2C register read using two messages to the same 7-bit client: message 0 writes a one-byte register index, and message 1 reads two bytes after a repeated START. If i2c_transfer returns 2, the two-message transaction completed according to the adapter interface; the caller can decode the returned bytes under the peripheral's format. A return of 1 is not a successful two-message read. A negative return can indicate a bus or adapter error. Whether the peripheral changed an internal register pointer after a partial transaction is device-specific, so blindly retrying a side-effecting write can be unsafe.
- Driveri2c_transfer
i2c_transfer(client, msgs, 2)
- i2c_transferAdapter
msg 0: write register index
- AdapterPeripheral
START, address W, register index
- i2c_transferAdapter
msg 1: repeated START, read 2 bytes
- PeripheralAdapter
Return. two data bytes
- i2c_transferDriver
Return. return 2: both messages transferred
Time runs downward. Dashed arrows show returns.
Read the text version
- Driver → i2c_transfer: i2c_transfer(client, msgs, 2)
- i2c_transfer → Adapter: msg 0: write register index
- Adapter → Peripheral: START, address W, register index
- i2c_transfer → Adapter: msg 1: repeated START, read 2 bytes
- Peripheral → Adapter (return): two data bytes
- i2c_transfer → Driver (return): return 2: both messages transferred
For an asynchronous SPI request, imagine message M points at driver-private buffers and a completion callback. Submission succeeds, then remove begins. The remove path must stop new submissions and wait or cancel under the SPI/controller contract before freeing those buffers. A managed allocation alone does not synchronize a callback. If a GPIO interrupt indicates data ready, an expander-backed GPIO read might sleep and must be handled in an appropriate threaded or deferred context. This conceptual sequence depends on the actual driver and controller; it is not a captured hardware trace.
An adapter quirk can reject a transaction before the controller's transfer callback. A regmap probe can fail before any IIO device is registered. Conversely, a successful regmap creation is not proof that a later sensor ID read or configuration write will succeed. The ADXL345 core checks the results of its register operations and match data; if a step fails it returns an error so probe cleanup can release managed resources. Check each acquisition and publication boundary rather than assuming that a successful I2C address match implies a working sensor.
I2C locking, retries, and bus ownership#
An I2C adapter's algorithm supplies the operations that the core can invoke. The transfer wrapper serializes the bus using the adapter's locking operations; a driver using a lower-level locked helper must already meet that helper's contract. The core can retry certain transient conditions under adapter policy, but a peripheral may have observed part of an operation before an error. A retry is safe only if the device protocol and operation are designed for it. An EEPROM write, command FIFO or read-to-clear status register can have side effects even when the caller later receives an error.
The two-message worked read is atomic with respect to other transfers only to the extent guaranteed by the adapter lock and combined-message implementation. It does not prevent another physical bus master from contending, nor does it prove the peripheral internally snapshots both bytes. A controller quirk can reject a combined transaction before reaching the driver's hardware callback. A return value of two reports two messages transferred under the API, not that the sensor reading is calibrated or semantically valid. Device-specific data sheets and binding requirements determine interpretation.
SMBus defines specific operations such as byte, word and block transactions, with controller functionality checks. A helper is convenient when its transaction shape matches the device and adapter; a device requiring a different repeated-start or register layout may need another supported API. Ten-bit I2C addressing is not merely a 7-bit address with extra bits silently accepted: flags, adapter functionality and the peripheral must all support it. Names and modaliases in sysfs identify registered software objects, not a safe list of addresses to probe.
SPI ownership and chip-select details#
A spi_message can contain several transfers whose chip-select and delay semantics are set through the message and controller's capabilities. Full-duplex shifting means the controller can transmit and receive bits during the same clocking period, but a device can ignore one direction or interpret a command phase differently. Clock mode, word size and speed must match the peripheral's electrical limits. The controller may split work internally or use DMA, and a completion status/actual length must be checked rather than inferred from successful submission.
In an asynchronous sequence, the submitter owns the message and buffers until the API transfers their use to the SPI core/controller; it must not place the message on a stack that disappears before completion. A completion callback may run in a context that cannot sleep and should hand off long work appropriately. On remove, preventing new submissions is only the first step: callbacks already in flight must finish before their buffers and device-private state are released. A reset GPIO or clock needed by an outstanding transfer must not be disabled prematurely. The exact drain and power order belongs to that driver and controller.
GPIO and regmap failure cases#
A GPIO descriptor's active-low flag changes the logical interpretation of an asserted value; raw accessors and logical accessors have different meanings. The board can add external inverters or open-drain wiring, so software must use the described polarity and the physical design rather than assume 1 means a high voltage. Pin control can select a mux and electrical bias, while a GPIO consumer controls a logical line through gpiolib. Those frameworks cooperate but neither guarantees the other was configured correctly.
Read the text version
- Logical 1: Consumer asserts the line → Low level: Default physical level when asserted on an active-low line (asserted: drives low)
- Logical 0: Consumer deasserts the line → High level: Default physical level when deasserted (deasserted: drives high)
Regmap can cache values for registers whose semantics permit caching. A volatile register must be read according to the configured policy, while a precious or read-to-clear register must not be casually sampled for debugging. A regmap lock serializes access within the map's contract; it does not provide an atomic transaction across unrelated devices or protect a value altered by external hardware. After a power loss, a cache can need synchronization or invalidation as specified by the driver; a cached value alone is not proof that the peripheral retained its registers.
The ADXL345 example separates transport wrapper from shared sensor core. The I2C wrapper establishes the regmap and propagates a failure; the core obtains match data, writes configuration and checks the device ID with return-value handling before registering its IIO device. This is a bounded source example, not a recommendation to read or write the sensor on the host. A missing device, power dependency or unsupported adapter can fail at different stages and needs a stage-appropriate unwind. The read-only sysfs inventory observes only existing kernel objects and does not test the bus.
Firmware description and bus observation#
An I2C or SPI peripheral is commonly instantiated from a firmware description or another explicit registration path rather than discovered by blindly probing every possible address. A modalias can allow userspace to select a driver, but an alias is not a successful transfer or a guarantee of correct wiring. Address conflicts, an unavailable clock, reset state, pin mux and regulator supply can all prevent communication even when the software object exists. SPI chip select and I2C address identify different things and should not be compared as if both were globally unique device numbers.
An I2C read can have side effects, including clearing a status register, and a bus scanner may send transactions that some devices interpret as commands. The source-only route avoids that uncertainty. The sysfs lab lists already-registered object names, driver links and modaliases; it does not establish electrical levels, detect every peripheral, verify calibration or exercise an adapter's transfer callbacks. If no objects are visible in a container, record the namespace and permission limitation instead of accessing host device nodes to fill the gap.
Practice#
Source-only route:
rg -n -A85 'int __i2c_transfer\(' "$KERNEL_SRC/drivers/i2c/i2c-core-base.c"
rg -n -A20 'static int adxl345_i2c_probe\(' "$KERNEL_SRC/drivers/iio/accel/adxl345_i2c.c"
rg -n -A65 'int adxl345_core_probe\(' "$KERNEL_SRC/drivers/iio/accel/adxl345_core.c"
rg -n -A18 'int spi_sync\(' "$KERNEL_SRC/drivers/spi/spi.c"
Verify v6.12. Mark unsupported adapter, quirk checks, regmap error and core probe's first register operations. Calculate both byte-order values and explain why only the actual device binding selects one.
Read-only metadata, without opening any device node:
python3 - <<'PY'
from pathlib import Path
for name in ('i2c', 'spi'):
p = Path('/sys/bus') / name / 'devices'
try:
entries = sorted(p.iterdir()) if p.is_dir() else []
print(name, 'visible entries:', len(entries))
if entries:
d = entries[0]
print(' example:', d.name, 'driver:', (d / 'driver').resolve().name if (d / 'driver').exists() else 'unbound or unavailable')
try: print(' modalias:', (d / 'modalias').read_text().strip())
except OSError as e: print(' modalias unavailable:', e.strerror)
except OSError as e: print(name, 'unavailable:', e.strerror)
p = Path('/sys/class/gpio')
try: print('gpiochip metadata entries:', len(list(p.glob('gpiochip*'))) if p.is_dir() else 0)
except OSError as e: print('gpio metadata unavailable:', e.strerror)
PY
No devices, missing class directories, unbound entries or permission failures are valid results. A modalias is not proof that a transfer succeeds. Do not run i2cdetect, i2cget, i2cset, SPI transfers, GPIO export or register dumps on the host: probes and reads can affect real hardware. Deliver the source error map, arithmetic and metadata result or absence.
Knowledge check#
- What does a return of 1 mean when a caller requested two I2C messages?
- What does the ADXL345 I2C probe do if regmap creation returns an error pointer?
- Why can an expander GPIO not necessarily be read from atomic context?
- How can logical GPIO 1 correspond to a low voltage?
- Why does
0x12 0x34not determine one register value without a device format?
Answers#
- Only one message was transferred according to that return; it is not success for the intended two-message transaction.
- It returns the encoded error through
dev_err_proberather than calling the shared core with an invalid regmap. - Access can require a sleepable I2C or SPI transaction; the descriptor's
cansleepcontract and appropriate accessor matter. - An active-low descriptor maps logical assertion to a low physical level; actual board wiring must still be understood.
- Endianness is defined by the peripheral/binding: the bytes could encode
0x1234or0x3412under the stated alternatives.
Checkpoint#
Save the two-message and asynchronous-SPI timelines, byte-order calculation and metadata observation. Pass when you distinguish adapter versus client, chip select versus I2C address, GPIO logical polarity versus pin mux, and managed-resource cleanup versus asynchronous completion. If hardware is absent, use the source-only route. This is practice, not certification.