-
Notifications
You must be signed in to change notification settings - Fork 651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow muxing I2C userspace driver and I2C Devices #3867
base: master
Are you sure you want to change the base?
Conversation
9f1436a
to
747aaa4
Compare
7255ae8
to
536e2b0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me, in particular also the part about I2CDevice
s being able to change their address at runtime (e.g., to support peripherals where the address is flashed into the device on initial configuration). I'm perhaps slightly concerned that this will make it easier to create address conflicts on a given bus (as the kernel can't know what userspace is doing on this bus), but presumably the kernel has some amount of trust towards the app when it gives it access to a shared I2C bus, and later changes could introduce some optional filtering infrastructure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this is digging us a deeper hole. I2CDevice
is explicitly supposed to restrict a user to a single I2C device (or really a single I2C address).
I2C is an example of why virtualizing with a different type is problematic.
I think we need to add a different virtualization layer.
This doesn't change that. If a board only creates an If a board designer wants to expose an I don't see how a different virtuliser would help here. What do you think it should virtulise? |
536e2b0
to
42ea7b1
Compare
@@ -230,6 +230,7 @@ pub trait I2CDevice { | |||
fn write(&self, data: &'static mut [u8], len: usize) -> Result<(), (Error, &'static mut [u8])>; | |||
fn read(&self, buffer: &'static mut [u8], len: usize) | |||
-> Result<(), (Error, &'static mut [u8])>; | |||
fn set_address(&self, addr: u8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't add this API. The address is fixed, and grants the holder of an I2CDevice
access to only the specified device (address).
/// It gives an interface for communicating with a specific I2C
/// device.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the device address changes though? It's not uncommon to have programmable I2C addresses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think either a capsule could use the I2CMaster
interface, or a shim layer that tracks the address changes below the I2CDevice
would work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @bradjc, for this abstraction, anyway, the whole point of the I2CDevice
trait is that it isolates drivers talking to different peripherals over the same I2C bus. If a client of I2CDevice
can modify the address arbitrarily, it completely negates the point of this abstraction.
There could be a different interface. For example, an extension trait might work, but I'd really want to understand the use case. Is the idea that some application/driver would change the I2C device dynamically after boot? What's a specific case for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal is to be able to share an I2C bus with multiple kernel drivers and with userspace. Something like:
App1 App2
=====syscall=====
Sensor Screen I2CMasterDriver
--------- --------- -----------------
I2CDevice I2CDevice I2CMaster
---------------------------------------------
Virtual I2C
---------------------------------------------
I2C bus
I don't think there is a better way to do this. The other approach is to modify the That would involve adding something like In the long run though, that seems even worse. Then there is no way to filter or prevent userspace from accessing other I2C devices. This current implementation will allow us to restrict access to I2C devices from userspace as required. We can also check for conflicts in |
Can you outline what you are trying to achieve? It seems like something like this might work? Feels like we need multiple virtualization layers to support both kernel and userspace and multiple userspace apps.
|
I'm trying to use a single I2C bus for some Another virtuliser layer to virtulise |
In terms of latency? Or memory use? It sounds like you need a new capsule, once which uses the |
Memory usage
I don't think that's a great idea though, see #3867 (comment) |
How about a second instance of the system call driver? AFAIK they were always assumed to be duplicable and can be instantiated at a second syscall driver number. This would solve many such problems across subsystems, is something that downstream users are likely to do anyways, and may motivate userspace APIs that can be supplied an alternative driver number. |
Yes that would be no problem, but userspace would only have access to a single I2C device (address) without a different virtualizer. |
What should we do with this PR? Is it helpful to serve as a placeholder? Or close it? I think the intent is completely fine, but we want to preserve the current |
I think this is something that needs to be supported. Otherwise we loose the entire I2C bus when one Maybe some else can have a go doing it in a way you approve of? |
A capsule that exposes |
How does that fix the issue? |
The existing virtual_i2c layer could be used to provide |
I don't see how that helps the original problem though. How is the I2C bus then exposed to userspace? |
I don't think it makes sense to provide an I2CDevice to a kernel driver, and then also provide access to the same device (address) via a low level bus interface to userspace. I mean it's possible using another virtualizer, but I don't think it fits the Tock ethos of isolation to allow userspace to interfere with the kernel. |
The end goal is what the PR description says To allow an I2C bus to support devices created by the board and still be exposed to userspace let's convert the I2CMasterDriver to use an I2CDevice instead of the physical I2C hardware. This allows us to use the Mux with userspace applications and kernel uses. If you have an I2C bus on a board, with multiple I2C devices connected to a physical bus. Some of those devices are supported by kernel capsules (say a https://github.com/tock/tock/blob/master/capsules/extra/src/bmp280.rs) and some aren't (such as a GNSS device). Currently you have to set up Tock such that every single physical I2C device has a capsule written for it. Otherwise you can't access it from user space (unless you use no kernel capsules). This PR changes the virtulisation such that a I2C bus can have both kernel supported capsules AND still expose the other devices to userspace.
Yes. This PR does not address the existing issues with the I2C syscall drivers. The drivers are already used and enabled by default on a range of Tock boards, so I'm not sure why this PR is the issue. If there are multiple apps running on any Tock board using an I2C driver they can conflict with each other. They can also conflict with the kernel as well. The nRF as well as pretty much every board already has this problem. This PR doesn't change that in anyway. One thing that this PR does do, is move in a direction where we can in the future add filtering to the I2C accesses. That would then allow us to ensure no two users are communicating with the same address. This PR does NOT do that, it just makes that easier in the future. Just to be clear, this PR doesn't fix or worsen the existing power of the I2C driver to access the entire bus. It just allows a board design to also use
"I want to be able to talk to devices from processes which are on the same bus as other devices which are used in kernel components, but are distinct devices" is exactly what this PR allows. Before #3881 I was using this PR to get NMEA sentences from a I2C device while also allowing the kernel to communicate with other I2C devices. Why do you think this PR doesn't accomplish that? Basically the idea is that Tock can be flashed to a device. I can plug in an I2C device and I don't have to re-flash the kernel. Instead I can install an app that can communicate with that device via I2C. Considering how hard it is to support new I2C devices (see #3881) I think this is a reasonable approach. I think @bradjc's suggestion has a few issues, as I pointed out in #3867 (comment). I might be misunderstanding the suggestion, but I haven't heard a response explaining what I'm missing. |
Currently we support two different I2C setups, if you will:
This PR merges the two, and we need to compare to setup #1 as the baseline (not #2). To maintain isolation guarantees for the kernel, we need to do this very carefully. That the "debugging" I2C setup (setup #2) allows for resource conflict does not mean we can compromise setup #1. We need to ensure that the kernel can maintain strong I2C isolation guarantees as that is what existing I2C-in-kernel users expect. |
I don't know how userspace would know the syscall driver numbers, but I assume it would work similarly to how userspace would know what I2C addresses are available and what those addresses correspond to. This is a fundamental issue with exposing any low-level/generic interface.
I don't think we can have everything. The ideal solution might support these features:
If feature 2 is enabled, then feature 3 is also a hard requirement for upstream code. Out-of-tree code of course does not need to uphold 3.
|
Agreed. This PR doesn't change this setup
Yep. This is what a lot of Tock boards do today. I see it as this PR changes this option to be a little more flexible. It allows exposing the I2C bus to user space (as most boards do), while also having dedicated drivers on the same bus. A production kernel probably does not want to do this. Although this PR does move towards address filtering which might change that. If the issue is specifically the
Wouldn't a simple filter on top of this get 1, 2, 3 and 4? We could just check if a device is already in use before allowing |
This is the issue. We absolutely cannot add this API to
Maybe. I'm concerned it would be fragile and easy to get wrong. Unless there is a way to use the type system to ensure that all |
Would a public function not in the HIL work?
If there is a general consensus that this can be accepted with filtering I could add it. I would rather no add it if this isn't going to be accepted though. It should be as simple as (untested) fn set_address_check(&self, new_addr: u8) -> bool {
self
.i2c_devices
.iter()
.find(|node| node.addr.get() == new_addr).is_none()
}
fn set_address(&self, addr: u8) -> Result<> {
if self.mux.set_address_check(addr) {
self.addr.set(addr);
Ok(())
} else {
Err(())
}
} That should avoid conflicts with |
I think a design where the I2C mux exposes the "rest" of the I2C bus as a separate object would be workable. I don't think it should be called I think that would balance the competing concerns. |
Ensure there is a minimum clock_period_nanos of 1, otherwise depending on the CPU frequency we can divide by zero. Signed-off-by: Alistair Francis <[email protected]>
Support changing the address of an I2CDevice. This is the first step towards allowing an I2C bus to have kernel devices created by the board and still be exposed to userspace. This also allows supporting devices with runtime configurable addresses. Signed-off-by: Alistair Francis <[email protected]>
To allow an I2C bus to have devices created by the board and still be exposed to userspace let's convert the I2CMasterDriver to use an I2CDevice instead of a I2C hardware. This allows muxing an I2C bus with both userspace and devices in the kernel. This commit also adds a I2CMasterDriverComponent to help in creating the I2CMasterDriver. Signed-off-by: Alistair Francis <[email protected]>
Signed-off-by: Alistair Francis <[email protected]>
42ea7b1
to
cb67b50
Compare
I pushed an update that I think addresses this. It builds but is untested, hence the draft status. Let me know if it is in the right direction and I'll test it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking more about this and I think this idea is very workable. I do think we want a clear distinction between an I2CDevice and the "remaining" I2C bus.
@@ -307,9 +398,17 @@ impl<'a, I: i2c::I2CMaster<'a>> i2c::I2CDevice for I2CDevice<'a, I> { | |||
} | |||
} | |||
|
|||
impl<'a, I: i2c::I2CMaster<'a>> i2c::I2CMultiDevice for I2CDevice<'a, I, true, NoSMBus> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use I2CMaster
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel the layering is a little weird using I2CMaster
. As the I2CDevice
layers on top of I2CMaster
it feels a little cleaner to then also layer the "rest of the bus" on I2CMaster
as well.
To me it also makes it clearer that we only have access to some of the bus, instead of full access that I2CMaster
has.
By using I2CMultiDevice
we can piggy back on the existing I2CDevice
support. Plus then we need to check the address less often, only when setting it instead of on every operation
@@ -37,14 +37,14 @@ struct Transaction { | |||
read_len: OptionalCell<usize>, | |||
} | |||
|
|||
pub struct I2CMasterDriver<'a, I: i2c::I2CMaster<'a>> { | |||
pub struct I2CMasterDriver<'a, I: i2c::I2CMultiDevice> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we wouldn't need to change this.
cb67b50
to
af7c51c
Compare
Signed-off-by: Alistair Francis <[email protected]>
af7c51c
to
4b1c55f
Compare
Ready to go then |
Ping! |
Ping! |
2 similar comments
Ping! |
Ping! |
Ping! |
Pull Request Overview
To allow an I2C bus to support devices created by the board and still be exposed to userspace let's convert the I2CMasterDriver to use an
I2CDevice
instead of the physical I2C hardware.This allows us to use the Mux with userspace applications and kernel uses.
Testing Strategy
Communicating with a I2C device from userspace while still supporting kernel devices.
TODO or Help Wanted
Documentation Updated
/docs
, or no updates are required.Formatting
make prepush
.