diff options
-rw-r--r-- | extra/i2c_pseudo/Documentation.md | 279 | ||||
-rw-r--r-- | extra/i2c_pseudo/Documentation.rst | 306 | ||||
-rw-r--r-- | extra/i2c_pseudo/README | 14 |
3 files changed, 594 insertions, 5 deletions
diff --git a/extra/i2c_pseudo/Documentation.md b/extra/i2c_pseudo/Documentation.md new file mode 100644 index 0000000000..ebcef6a01e --- /dev/null +++ b/extra/i2c_pseudo/Documentation.md @@ -0,0 +1,279 @@ +# i2c-pseudo driver + +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: + +* 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. + +* Implementing I2C device driver tests that are impractical with 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 at a + certain address must change during the sequence. + +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 + + +--- + +Read Command + +: `I2C_PSEUDO_ID <num>` + +Example + +: `"I2C_PSEUDO_ID 98\\n"` + +Details + + +--- + +Read Command + +: `I2C_BEGIN_XFER` + +Example + +: `"I2C_BEGIN_XFER\\n"` + +Details + + +--- + +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 + + +--- + +Read Command + +: `I2C_COMMIT_XFER` + +Example + +: `"I2C_COMMIT_XFER\\n"` + +Details + +## 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 + + +--- + +Write Command + +: `SET_ADAPTER_TIMEOUT_MS <ms>` + +Example + +: `"SET_ADAPTER_TIMEOUT_MS 2000\\n"` + +Details + + +--- + +Write Command + +: `ADAPTER_START` + +Example + +: `"ADAPTER_START\\n"` + +Details + + +--- + +Write Command + +: `GET_ADAPTER_NUM` + +Example + +: `"GET_ADAPTER_NUM\\n"` + +Details + + +--- + +Write Command + +: `GET_PSEUDO_ID` + +Example + +: `"GET_PSEUDO_ID\\n"` + +Details + + +--- + +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 + + +--- + +Write Command + +: `ADAPTER_SHUTDOWN` + +Example + +: `"ADAPTER_SHUTDOWN\\n"` + +Details + +## 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 1 0 0x0070 0x0000 0\n"); +dprintf(fd, "I2C_XFER_REPLY 1 1 0x0070 0x0001 0 0B\n"); + +/* Destroy the I2C adapter. */ +close(fd); +``` diff --git a/extra/i2c_pseudo/Documentation.rst b/extra/i2c_pseudo/Documentation.rst new file mode 100644 index 0000000000..2527eb5337 --- /dev/null +++ b/extra/i2c_pseudo/Documentation.rst @@ -0,0 +1,306 @@ +================= +i2c-pseudo driver +================= + +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: + +- 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. + +- Implementing I2C device driver tests that are impractical with 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 at a + certain address must change during the sequence. + +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 address + is right-aligned without any read/write bit. + | + | 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 transaction 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. (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 to 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 1 0 0x0070 0x0000 0\n"); + dprintf(fd, "I2C_XFER_REPLY 1 1 0x0070 0x0001 0 0B\n"); + + /* Destroy the I2C adapter. */ + close(fd); diff --git a/extra/i2c_pseudo/README b/extra/i2c_pseudo/README index 96efa062b1..1d1ef75641 100644 --- a/extra/i2c_pseudo/README +++ b/extra/i2c_pseudo/README @@ -2,12 +2,16 @@ This directory contains the i2c-pseudo Linux kernel module. The i2c-pseudo module was written with the intention of being submitted upstream in the Linux kernel. This copy exists because of as 2019-03 this module is not -yet in the upstream kernel, and even if/when this is included, it may take years -before making its way to the prepackaged Linux distribution kernels typically -used by CrOS developers. +yet in the upstream kernel, and even if/when this is included, it may take a +long time to get included in prepackaged Linux distribution kernels, especially +those based on Linux LTS branches. -See Documentation.txt for more information about the module itself. That file -is Documentation/i2c/pseudo-controller-interface in the upstream patch. +See Documentation.rst or Documentation.md for more information about the module +itself. The reStructuredText (.rst) file is +Documentation/i2c/pseudo-controller-interface.rst in the upstream patch. The +Markdown file (.md) is generated using rst2md from +nb2plots (https://github.com/matthew-brett/nb2plots) which uses +Sphinx (https://www.sphinx-doc.org/). When servod starts, if the i2c-pseudo module is loaded servod will automatically create an I2C pseudo adapter for the Servo I2C bus. That I2C adapter may then |