coreboot-libre-fam15h-rdimm/3rdparty/chromeec/extra/i2c_pseudo/Documentation.txt

291 lines
12 KiB
Plaintext
Raw Permalink Normal View History

2024-03-04 11:14:53 +01:00
----Introduction----
Usually I2C adapters are implemented in a kernel driver. It is also possible to
implement an adapter in userspace, through the /dev/i2c-pseudo-controller
interface. Load module i2c-pseudo for this.
Use cases for this module include:
[A] Using local I2C device drivers, particularly i2c-dev, with I2C busses on
remote systems. For example, interacting with a Device Under Test (DUT)
connected to a Linux host through a debug interface, or interacting with a
remote host over a network.
[B] Support I2C device driver tests that are too complex for the i2c-stub
module. For example, when simulating an I2C device where its driver might
issue a sequence of reads and writes without interruption, and the value a
certain address must change during the sequence.
Any possible use case could of course be implemented as a kernel driver.
However, it can be much faster and easier to implement such things in userspace,
thanks to the far greater code reuse possibilities (libraries), the plethora of
programming language options for rapid iteration, and not needing to understand
how to implement Linux kernel drivers.
This is not intended to replace kernel drivers for actual I2C busses on the
local host machine.
----Details----
Each time /dev/i2c-pseudo-controller is opened, and the correct initialization
command is written to it (ADAPTER_START), a new I2C adapter is created. The
adapter will live until its file descriptor is closed. Multiple pseudo adapters
can co-exist simultaneously, controlled by the same or different userspace
processes. When an I2C device driver sends an I2C message to a pseudo adapter,
the message becomes readable from its file descriptor. If a reply is written
before the adapter timeout expires, that reply will be sent back to the I2C
device driver.
Reads and writes are buffered inside i2c-pseudo such that userspace controllers
may split them up into arbitrarily small chunks. Multiple commands, or portions
of multiple commands, may be read or written together.
Blocking I/O is the default. Non-blocking I/O is supported as well, enabled by
O_NONBLOCK. Polling is supported, with or without non-blocking I/O. A special
command (ADAPTER_SHUTDOWN) is available to unblock any pollers or blocked
reads or writes, as a convenience for a multi-threaded or multi-process program
that wants to exit.
It is safe to access a single controller fd from multiple threads or processes
concurrently, though it is up to the controller to ensure proper ordering, and
to ensure that writes for different commands do not get interleaved. However,
it is recommended (not required) that controller implementations have only one
reader thread and one writer thread, which may or may not be the same thread.
Avoiding multiple readers and multiple writers greatly simplifies controller
implementation, and there is likely no performance benefit to be gained from
concurrent reads or concurrent writes due to how i2c-pseudo serializes them
internally. After all, on a real I2C bus only one I2C message can be active at
a time.
Commands are newline-terminated, both those read from the controller device, and
those written to it.
----Read Commands----
The commands that may be read from a pseudo controller device are:
Read Command: I2C_ADAPTER_NUM <num>
Example: "I2C_ADAPTER_NUM 5\n"
Details: This is read in response to the GET_ADAPTER_NUM command being written.
The number is the I2C adapter number in decimal. This can only occur after
ADAPTER_START, because before that the number is not known and cannot be
predicted reliably.
Read Command: I2C_PSEUDO_ID <num>
Example: "I2C_PSEUDO_ID 98\n"
Details: This is read in response to the GET_PSEUDO_ID command being written.
The number is the pseudo ID in decimal.
Read Command: I2C_BEGIN_XFER
Example: "I2C_BEGIN_XFER\n"
Details: This indicates the start of an I2C transaction request, in other words
the start of the I2C messages from a single invocation of the I2C adapter's
master_xfer() callback. This can only occur after ADAPTER_START.
Read Command: I2C_XFER_REQ <xfer_id> <msg_id> <addr> <flags> <data_len> [<write_byte>[:...]]
Example: "I2C_XFER_REQ 3 0 0x0070 0x0000 2 AB:9F\n"
Example: "I2C_XFER_REQ 3 1 0x0070 0x0001 4\n"
Details: This is a single I2C message that a device driver requested be sent on
the bus, in other words a single struct i2c_msg from master_xfer() msgs arg.
The xfer_id is a number representing the whole I2C transaction, thus all
I2C_XFER_REQ between a I2C_BEGIN_XFER + I2C_COMMIT_XFER pair share an xfer_id.
The purpose is to ensure replies from the userspace controller are always
properly matched to the intended master_xfer() request. The first transaction
has xfer_id 0, and it increases by 1 with each transaction, however it will
eventually wrap back to 0 if enough transactions happen during the lifetime of a
pseudo adapter. It is guaranteed to have a large enough maximum value such that
there can never be multiple outstanding transactions with the same ID, due to an
internal limit in i2c-pseudo that will block master_xfer() calls when the
controller is falling behind in its replies.
The msg_id is a decimal number representing the index of the I2C message within
its transaction, in other words the index in master_xfer() *msgs array arg.
This starts at 0 after each I2C_BEGIN_XFER. This is guaranteed to not wrap.
The addr is the hexadecimal I2C address for this I2C message.
The flags are the same bitmask flags used in struct i2c_msg, in hexadecimal
form. Of particular importance to any pseudo controller is the read bit, which
is guaranteed to be 0x1 per Linux I2C documentation.
The data_len is the decimal number of either how many bytes to write that will
follow, or how many bytes to read and reply with if this is a read request.
If this is a read, data_len will be the final field in this command. If this is
a write, data_len will be followed by the given number of colon-separated
hexadecimal byte values, in the format shown in the example above.
Read Command: I2C_COMMIT_XFER
Example: "I2C_COMMIT_XFER\n"
Details: This indicates the end of an I2C transacton request, in other words the
end of the I2C messages from a single invocation of the I2C adapter's
master_xfer() callback. This should be read exactly once after each
I2C_BEGIN_XFER, with a varying number of I2C_XFER_REQ between them.
----Write Commands----
The commands that may be written to a pseudo controller device are:
Write Command: SET_ADAPTER_NAME_SUFFIX <suffix>
Example: "SET_ADAPTER_NAME_SUFFIX My Adapter\n"
Details: Sets a suffix to append to the auto-generated I2C adapter name. Only
valid before ADAPTER_START. A space or other separator character will be placed
between the auto-generated name and the suffix, so there is no need to include a
leading separator in the suffix. If the resulting name is too long for the I2C
adapter name field, it will be quietly truncated.
Write Command: SET_ADAPTER_TIMEOUT_MS <ms>
Example: "SET_ADAPTER_TIMEOUT_MS 2000\n"
Details: Sets the timeout in milliseconds for each I2C transaction, in other
words for each master_xfer() reply. Only valid before ADAPTER_START. The I2C
subsystem will automatically time out transactions based on this setting. Set
to 0 to use the I2C subsystem default timeout. The default timeout for new
pseudo adapters where this command has not been used is configurable at
i2c-pseudo module load time, and itself has a default independent from the I2C
subsystem default. (Though if the i2c-pseudo module level default is set to 0,
that has the same meaning as here.)
Write Command: ADAPTER_START
Example: "ADAPTER_START\n"
Details: Tells i2c-pseudo the actually create the I2C adapter. Only valid once
per open controller fd.
Write Command: GET_ADAPTER_NUM
Example: "GET_ADAPTER_NUM\n"
Details: Asks i2c-pseudo for the number assigned to this I2C adapter by the I2C
subsystem. Only valid after ADAPTER_START, because before that the number
is not known and cannot be predicted reliably.
Write Command: GET_PSEUDO_ID
Example: "GET_PSEUDO_ID\n"
Details: Asks i2c-pseudo for the pseudo ID of this I2C adapter. The pseudo ID
will not be reused for the lifetime of the i2c-pseudo module, unless an internal
counter wraps. I2C clients can use this to track specific instances of pseudo
adapters, even when adapter numbers have been reused.
Write Command: I2C_XFER_REPLY <xfer_id> <msg_id> <addr> <flags> <errno> [<read_byte>[:...]]
Example: "I2C_XFER_REPLY 3 0 0x0070 0x0000 0\n"
Example: "I2C_XFER_REPLY 3 1 0x0070 0x0001 0 0B 29 02 D9\n"
Details: This is how a pseudo controller can reply to I2C_XFER_REQ. Only valid
after I2C_XFER_REQ. A pseudo controller should write one of these for each
I2C_XFER_REQ it reads, including for failures, so that I2C device drivers need
not wait for the adapter timeout upon failure (if failure is known sooner).
The fields in common with I2C_XFER_REQ have their same meanings, and their
values are expected to exactly match what was read in the I2C_XFER_REQ command
that this is in reply to.
The errno field is how the pseudo controller indicates success or failure for
this I2C message. A 0 value indicates success. A non-zero value indicates a
failure. Pseudo controllers are encouraged to use errno values to encode some
meaning in a failure response, but that is not a requirement, and the I2C
adapter interface does not provide a way to pass per-message errno values to a
device driver anyways.
Pseudo controllers are encouraged to reply in the same order as messages were
received, however i2c-pseudo will properly match up out-of-order replies with
their original requests.
Write Command: ADAPTER_SHUTDOWN
Example: "ADAPTER_SHUTDOWN\n"
Details: This tells i2c-pseudo that the pseudo controller wants to shutdown and
intends to close the controller device fd soon. Use of this is OPTIONAL, it is
perfectly valid to close the controller device fd without ever using this
command.
This commands unblocks any blocked controller I/O (reads, writes, or polls), and
that is its main purpose.
Any I2C transactions attempted by a device driver after this command will fail,
and will not be passed on to the userspace controller.
This DOES NOT delete the I2C adapter. Only closing the fd will do that. That
MAY CHANGE in the future, such that this does delete the I2C adapter. (However
this will never be required, it will always be okay to simply close the fd.)
----Example userspace controller code----
In C, a simple exchange between i2c-pseudo and userspace might look like the
example below. Note that for brevity this lacks any error checking and
handling, which a real pseudo controller implementation should have.
int fd;
char buf[1<<12];
fd = open("/dev/i2c-pseudo-controller", O_RDWR);
/* Create the I2C adapter. */
dprintf(fd, "ADAPTER_START\n");
/*
* Pretend this I2C adapter number is 5, and the first I2C xfer sent to it was
* from this command (using its i2c-dev interface):
* $ i2cset -y 5 0x70 0xC2
*
* Then this read would place the following into *buf:
* "I2C_BEGIN_XFER\n"
* "I2C_XFER_REQ 0 0 0x0070 0x0000 1 C2\n"
* "I2C_COMMIT_XFER\n"
*/
read(fd, buf, sizeof(buf));
/* This reply would allow the i2cset command above to exit successfully. */
dprintf(fd, "I2C_XFER_REPLY 0 0 0x0070 0x0000 0\n");
/*
* Now pretend the next I2C xfer sent to this adapter was from:
* $ i2cget -y 5 0x70 0xAB
*
* Then this read would place the following into *buf:
* "I2C_BEGIN_XFER\n"
* "I2C_XFER_REQ 1 0 0x0070 0x0000 1 AB\n"
* "I2C_XFER_REQ 1 1 0x0070 0x0001 1\n'"
* "I2C_COMMIT_XFER\n"
*/
read(fd, buf, sizeof(buf));
/*
* These replies would allow the i2cget command above to print the following to
* stdout and exit successfully:
* 0x0b
*
* Note that it is also valid to write these together in one write().
*/
dprintf(fd, "I2C_XFER_REPLY 3 0 0x0070 0x0000 0\n"
dprintf(fd, "I2C_XFER_REPLY 3 1 0x0070 0x0001 0 0B\n");
/* Destroy the I2C adapter. */
close(fd);