I2C muxes and complex topologies
1. Introduction
构建比使用一个适配器和一个或多个设备的直接 I2C 总线更复杂的 I2C 拓扑有几个原因。
一些示例用例是:
-
总线上可能需要多路复用器来防止地址冲突。
-
总线可以从某些外部总线主控器访问,并且可能需要仲裁来确定是否可以访问总线。
-
设备(尤其是 RF 调谐器)可能希望避免来自 I2C 总线的数字噪声(至少在大多数情况下),并且位于必须在访问设备之前操作的门后面。
I2C 多路复用器、I2C 门和 I2C 仲裁器等多种类型的硬件组件可以满足此类需求。
这些组件由 Linux 表示为 I2C 适配器树,其中每个适配器都有一个父适配器(根适配器除外)和零个或多个子适配器。 根适配器是发出 I2C 传输的实际适配器,所有具有父适配器的适配器都是“i2c-mux”对象的一部分(引用,因为它也可以是仲裁器或门)。
根据特定的多路复用器驱动程序,当其子适配器之一上存在 I2C 传输时,会发生某些情况。 显然,多路复用器驱动程序可以操作多路复用器,但它也可以与外部总线主控进行仲裁或打开门。 多路复用器驱动程序为此有两个操作:选择和取消选择。 select 在传输之前调用,(可选的)deselect 在传输之后调用。
2. Locking
I2C 多路复用器有两种可用的锁定变体,它们可以是多路复用器锁定多路复用器或父级锁定多路复用器。
2.1 Mux-locked muxes
在完整的选择-传输-取消选择事务期间,多路复用器锁定的多路复用器不会锁定整个父适配器,仅锁定父适配器上的多路复用器。 如果选择和/或取消选择操作必须使用 I2C 传输来完成其任务,则多路复用器锁定多路复用器非常有趣。 由于父适配器在完整事务期间未完全锁定,因此不相关的 I2C 传输可能会交错事务的不同阶段。 这样做的好处是多路复用器驱动程序可能更容易、更简洁地实现,但它有一些注意事项。
2.2 Mux-locked Example
.----------. .--------.
.--------. | mux- |-----| dev D1 |
| root |--+--| locked | '--------'
'--------' | | mux M1 |--. .--------.
| '----------' '--| dev D2 |
| .--------. '--------'
'--| dev D3 |
'--------'
当访问 D1 时,会发生这种情况:
- 有人向 D1 发出 I2C 传输。
- M1 将多路复用器锁定在其父适配器(本例中为根适配器)上。
- M1 调用 ->select 来准备多路复用器。
- M1(大概)执行一些 I2C 传输作为其选择的一部分。 这些传输是锁定父适配器的正常 I2C 传输。
- M1 将步骤 1 中的 I2C 传输馈送到其父适配器,作为锁定父适配器的正常 I2C 传输。
- M1 调用 ->取消选择(如果有的话)。
- 与步骤 4 中的规则相同,但适用于 -> 取消选择。
- M1 解锁其父级上的多路复用器。
这意味着对 D2 的访问在整个操作期间都被锁定。 但对 D3 的访问可能在任何时候都是交错的。
2.3 Mux-locked caveats
使用多路复用器锁定多路复用器时,请注意以下限制:
[ML1]
如果您构建的拓扑中的多路复用器锁定多路复用器是父级锁定多路复用器的父级,则这可能会破坏父级锁定多路复用器在事务期间锁定根适配器的预期。
[ML2]
当这些非同级多路复用器的子适配器上的设备之间存在地址冲突时,使用两个(或更多)非同级多路复用器锁定多路复用器构建任意拓扑是不安全的。
选择-转移-取消选择交易目标,例如 mux-one 后面的设备地址 0x42 可以与 mux-2 后面的针对设备地址 0x42 的类似操作交错。 在这个假设的示例中,这种拓扑的目的是不应同时选择多路复用器一和多路复用器二,但多路复用器锁定多路复用器并不能保证在所有拓扑中都能做到这一点。
[ML3]
驱动程序不能将多路复用器锁定的多路复用器用于自动关闭门/多路复用器,即在给定数量(在大多数情况下为一)的 I2C 传输后自动关闭的东西。 不相关的 I2C 传输可能会悄悄进入并过早关闭。
[ML4]
如果 mux 驱动程序中的任何非 I2C 操作更改了 I2C mux 状态,则驱动程序必须在该操作期间锁定根适配器。 否则,当在非 I2C 多路复用器更改操作期间进行不相关的 I2C 传输时,从多路复用器后面的设备可以看到,总线上可能会出现垃圾。
2.4 Parent-locked muxes
父锁定多路复用器在完整的选择-传输-取消选择事务期间锁定父适配器。 这意味着多路复用器驱动程序必须确保事务期间通过该父适配器的任何和所有 I2C 传输都是未锁定的 I2C 传输(使用例如 __i2c_transfer),否则将出现死锁。
2.5 Parent-locked Example
.----------. .--------.
.--------. | parent- |-----| dev D1 |
| root |--+--| locked | '--------'
'--------' | | mux M1 |--. .--------.
| '----------' '--| dev D2 |
| .--------. '--------'
'--| dev D3 |
'--------'
当访问 D1 时,会发生这种情况:
- 有人向 D1 发出 I2C 传输。
- M1 将多路复用器锁定在其父适配器(本例中为根适配器)上。
- M1 锁定其父适配器。
- M1 调用 ->select 来准备多路复用器。
- 如果 M1 (在此根适配器上)执行任何 I2C 传输作为其选择的一部分,则这些传输必须是解锁的 I2C 传输,以便它们不会使根适配器死锁。
- M1 将步骤 1 中的 I2C 传输作为未锁定的 I2C 传输提供给根适配器,这样就不会导致父适配器发生死锁。
- M1 调用 ->取消选择(如果有的话)。
- 与步骤 5 中的规则相同,但适用于 -> 取消选择。
- M1 解锁其父适配器。
- M1 解锁其父级上的多路复用器。
这意味着对 D2 和 D3 的访问在整个操作期间都被锁定。
2.6 Parent-locked Caveats
使用父锁定多路复用器时,请注意以下限制:
[PL1]
如果您构建一个拓扑,其中父锁定多路复用器是另一个多路复用器的子级,则这可能会打破子多路复用器的可能假设,即根适配器在其选择操作和实际传输之间未使用(例如,如果子多路复用器是自动的) - 关闭并且父多路复用器发出 I2C 传输作为其选择的一部分)。 如果父多路复用器被多路复用器锁定,则尤其是这种情况,但如果父多路复用器被父锁定,则也可能发生这种情况。
[PL2]
如果选择/取消选择调用其他子系统,例如 gpio、pinctrl、regmap 或 iio,则必须解锁由这些子系统引起的任何 I2C 传输。 这可能会很复杂,如果寻求一种可接受的干净解决方案,甚至可能是不可能的。
3. Complex Examples
3.1 Parent-locked mux as parent of parent-locked mux
这是一个有用的拓扑,但也可能很糟糕:
.----------. .----------. .--------.
.--------. | parent- |-----| parent- |-----| dev D1 |
| root |--+--| locked | | locked | '--------'
'--------' | | mux M1 |--. | mux M2 |--. .--------.
| '----------' | '----------' '--| dev D2 |
| .--------. | .--------. '--------'
'--| dev D4 | '--| dev D3 |
'--------' '--------'
当任何设备被访问时,所有其他设备在整个操作期间都会被锁定(两个多路复用器都锁定其父设备,特别是当 M2 请求其父设备锁定时,M1 将责任传递给根适配器)。
如果 M2 是自动关闭多路复用器,并且 M1->select 在根适配器上发出任何未锁定的 I2C 传输,则这种拓扑结构很糟糕,这些传输可能会泄漏并被 M2 适配器看到,从而过早关闭 M2。
3.2 Mux-locked mux as parent of mux-locked mux
这是一个很好的拓扑:
.----------. .----------. .--------.
.--------. | mux- |-----| mux- |-----| dev D1 |
| root |--+--| locked | | locked | '--------'
'--------' | | mux M1 |--. | mux M2 |--. .--------.
| '----------' | '----------' '--| dev D2 |
| .--------. | .--------. '--------'
'--| dev D4 | '--| dev D3 |
'--------' '--------'
当设备 D1 被访问时,对 D2 的访问将在整个操作期间被锁定(M1 顶部子适配器上的多路复用器被锁定)。 但对 D3 和 D4 的访问可能在任何点都交错。
对 D3 的访问会锁定 D1 和 D2,但对 D4 的访问仍然可能是交错的。
3.3 Mux-locked mux as parent of parent-locked mux
这可能是一个糟糕的拓扑:
.----------. .----------. .--------.
.--------. | mux- |-----| parent- |-----| dev D1 |
| root |--+--| locked | | locked | '--------'
'--------' | | mux M1 |--. | mux M2 |--. .--------.
| '----------' | '----------' '--| dev D2 |
| .--------. | .--------. '--------'
'--| dev D4 | '--| dev D3 |
'--------' '--------'
当设备 D1 被访问时,对 D2 和 D3 的访问将在整个操作期间被锁定(M1 锁定根适配器上的子多路复用器)。 但对 D4 的访问可能在任何点都交错。
这种拓扑通常不合适,应该避免。 原因是 M2 可能假设在调用 ->select 和 ->deselect 期间不会有 I2C 传输,如果有,任何此类传输可能会作为部分 I2C 传输出现在 M2 的从机端,即垃圾或 更差。 这可能会导致设备锁定和/或其他问题。
如果 M2 是自动关闭多路复用器,则拓扑结构尤其麻烦。 在这种情况下,任何对 D4 的交错访问都可能过早关闭 M2,任何 I2C 传输 M1->select 的一部分也可能如此。
但如果 M2 没有做出上述假设,并且 M2 没有自动关闭,则拓扑结构良好。
3.4 Parent-locked mux as parent of mux-locked mux
这是一个很好的拓扑:
.----------. .----------. .--------.
.--------. | parent- |-----| mux- |-----| dev D1 |
| root |--+--| locked | | locked | '--------'
'--------' | | mux M1 |--. | mux M2 |--. .--------.
| '----------' | '----------' '--| dev D2 |
| .--------. | .--------. '--------'
'--| dev D4 | '--| dev D3 |
'--------' '--------'
当 D1 被访问时,对 D2 的访问在整个操作期间被锁定(M1 顶部子适配器上的多路复用器被锁定)。 对 D3 和 D4 的访问可能在任何点交错,正如多路复用器锁定多路复用器所期望的那样。
当访问 D3 或 D4 时,其他所有内容都将被锁定。 对于 D3 访问,M1 锁定根适配器。 对于D4访问,直接锁定根适配器。
3.5 Two mux-locked sibling muxes
这是一个很好的拓扑:
.--------.
.----------. .--| dev D1 |
| mux- |--' '--------'
.--| locked | .--------.
| | mux M1 |-----| dev D2 |
| '----------' '--------'
| .----------. .--------.
.--------. | | mux- |-----| dev D3 |
| root |--+--| locked | '--------'
'--------' | | mux M2 |--. .--------.
| '----------' '--| dev D4 |
| .--------. '--------'
'--| dev D5 |
'--------'
当访问 D1 时,对 D2、D3 和 D4 的访问将被锁定。 但对 D5 的访问可能随时交错。
3.6 Two parent-locked sibling muxes
这是一个很好的拓扑:
.--------.
.----------. .--| dev D1 |
| parent- |--' '--------'
.--| locked | .--------.
| | mux M1 |-----| dev D2 |
| '----------' '--------'
| .----------. .--------.
.--------. | | parent- |-----| dev D3 |
| root |--+--| locked | '--------'
'--------' | | mux M2 |--. .--------.
| '----------' '--| dev D4 |
| .--------. '--------'
'--| dev D5 |
'--------'
当任何设备被访问时,对所有其他设备的访问都会被锁定。
3.7 Mux-locked and parent-locked sibling muxes
这是一个很好的拓扑:
.--------.
.----------. .--| dev D1 |
| mux- |--' '--------'
.--| locked | .--------.
| | mux M1 |-----| dev D2 |
| '----------' '--------'
| .----------. .--------.
.--------. | | parent- |-----| dev D3 |
| root |--+--| locked | '--------'
'--------' | | mux M2 |--. .--------.
| '----------' '--| dev D4 |
| .--------. '--------'
'--| dev D5 |
'--------'
当访问 D1 或 D2 时,对 D3 和 D4 的访问被锁定,而对 D5 的访问可能会交错。 当访问 D3 或 D4 时,将锁定对所有其他设备的访问。
3.8 Mux type of existing device drivers
设备是复用锁定还是父锁定取决于其实现。 以下列表在撰写本文时是正确的:
在 drivers/i2c/muxes/:
| i2c-arb-gpio-challenge | Parent-locked |
| i2c-mux-gpio | 通常,父锁定、多路复用锁定当且仅当所有涉及的 GPIO 引脚均由它们多路复用的同一个 I2C 根适配器控制。 |
| i2c-mux-gpmux | 通常是父锁定、多路复用锁定 iff 在设备树中指定。 |
| i2c-mux-ltc4306 | Mux-locked |
| i2c-mux-mlxcpld | Parent-locked |
| i2c-mux-pca9541 | Parent-locked |
| i2c-mux-pca954x | Parent-locked |
| i2c-mux-pinctrl | 通常,父锁定、多路复用锁定当且仅当所有涉及的 pinctrl 设备均由它们多路复用的同一 I2C 根适配器控制。 |
| i2c-mux-reg | Parent-locked |
在 drivers/iio/:
| gyro/mpu3050 | Mux-locked |
| imu/inv_mpu6050/ | Mux-locked |
在 drivers/media/:
| dvb-frontends/lgdt3306a | Mux-locked |
| dvb-frontends/m88ds3103 | Parent-locked |
| dvb-frontends/rtl2830 | Parent-locked |
| dvb-frontends/rtl2832 | Mux-locked |
| dvb-frontends/si2168 | Mux-locked |
| usb/cx231xx/ | Parent-locked |