Skip to content
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

Tock 2.0 IPC #2434

Merged
merged 2 commits into from
Feb 17, 2021
Merged

Tock 2.0 IPC #2434

merged 2 commits into from
Feb 17, 2021

Conversation

alevy
Copy link
Member

@alevy alevy commented Feb 16, 2021

Pull Request Overview

This pull request ports the IPC driver to the Tock 2.0 driver interface.

This is a mostly fairly straightforward port. However, the old driver API relied on allow to return a discovered IPC endpoint upon success, which is no longer possible in the 2.0 driver interface (allow cannot return a value).

Recall that IPC works as follows:

  1. A client "discovers" an IPC service by supplying the kernel with the service's name (taken from the process's TBF name header)
  2. If such a service exists, the kernel supplies the client process with a 32-bit non-zero descriptor to reference the service (implemented as the AppId + 1, but that's not guaranteed).
  3. The client can use this descriptor to share memory with the service (using allow) and to notify it (using command).

For discovery, allow(IPC, 0) had been used to both share a buffer containing the service name and request that the kernel perform discovery, returning a descriptor on success.

This is no longer possible, and it also not canonical driver semantics.

With this PR, discovery is split into an allow_readonly (which is nice anyway so
package names can be hardcoded in ROM rather than copied to
memory) followed by a command(IPC, 1) to actually perform the discovery and return a descriptor.

To make this cleaner, I also rejiggerd the arguments in command, which now has to mean more than just notify.

Previously, the arguments were:

  1. Descriptor (as opposed to the normal command number)
  2. Service-or-client-notify (which is actually kind of like a command number)
  3. Unused

Now, the arguments are:

  1. Command number (can be "discover," "service notify," or "client notify")
  2. Target descriptor
  3. Unused

There are a number of other changes I believe could make sense to make to IPC before releasing 2.0, but I think they should be decoupled from this pull request, which merely adapts the interface very little to get it working. These are just ideas, and we should discuss all these separately:

  • Allow both read-only and read-write buffer sharing between client/service
  • Use borrow semantics for memory ownership
  • Let client subscribe to be notified when a service is available
  • Probably more...

Testing Strategy

Tested using the IPC tutorial in libtock-c (examples/tutorial/05-ipc) after applying changes from tock/libtock-c#176

TODO or Help Wanted

Still need to adapt libtock-c userspace to properly test. (Done in tock/libtock-c#176)

Still need to update documentation

Please review not only the implementation but the change in interface described above. As I said, there are a variety of changes that might make sense for IPC that go beyond these minimal changes. I ask that we get something in that reliably reproduces the existing high-level semantics for IPC and after (or maybe concurrently) decide whether other changes should happen (either before or after 2.0).

Documentation Updated

  • Updated the relevant files in /docs, or no updates are required.

Formatting

  • Ran make prepush.

@alevy alevy requested a review from a team February 16, 2021 02:25
@alevy
Copy link
Member Author

alevy commented Feb 16, 2021

Please do not merge yet, I have not tested properly and have not updated all the documentation. However, I believe it's more or less complete, so worth reviewing.

I've separated into two commits, one that changes the IPC driver and one (with a one-line change in a ton of files) that updates references to IPC in all boards using it. I suggest looking primarily at the first commit, which isn't actually that big.

@hudson-ayers
Copy link
Contributor

This increases code size by enough that the kernel no longer fits into flash on Imix (hence the CI failure). Looking at hail, it increased size by 1264 bytes.

@alevy
Copy link
Member Author

alevy commented Feb 16, 2021

1264 bytes

I'll look into it, but that's got to be an alignment thing (e.g., it maybe increased by enough of a margin to just push it over the alignment boundary or something), right?

Copy link
Contributor

@hudson-ayers hudson-ayers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good to me, I have a few comments.

kernel/src/sched.rs Outdated Show resolved Hide resolved
kernel/src/ipc.rs Outdated Show resolved Hide resolved
kernel/src/ipc.rs Outdated Show resolved Hide resolved
kernel/src/ipc.rs Outdated Show resolved Hide resolved
kernel/src/ipc.rs Outdated Show resolved Hide resolved
kernel/src/ipc.rs Show resolved Hide resolved
kernel/src/ipc.rs Show resolved Hide resolved
@hudson-ayers
Copy link
Contributor

1264 bytes

I'll look into it, but that's got to be an alignment thing (e.g., it maybe increased by enough of a margin to just push it over the alignment boundary or something), right?

Using Phil's size tool, the code size required for functions in the kernel::ipc::IPC::* namespace went from using 1006 bytes before this PR to using 2392 bytes afterwards. I think the size tool counts code space wasted due to alignment separately. Unfortunately the tool does not provide any resolution on where the size is going within individual methods on the struct. The one place in the changes where it is clear size might have changed is command() -- before, the code for service notify and client notify was almost entirely shared, and now it is duplicated across the two match arms. Still it makes no sense how that could over double the code size used..

@phil-levis
Copy link
Contributor

* Use borrow semantics for memory ownership

This is interesting; fundamentally the idea of this shared memory goes against Rust's ownership model, so does need to be protected by some sort of lock. If we do decide to follow the borrow semantics, though, this becomes message passing (except if it's read-only on both sides)?

@hudson-ayers
Copy link
Contributor

This is interesting; fundamentally the idea of this shared memory goes against Rust's ownership model, so does need to be protected by some sort of lock. If we do decide to follow the borrow semantics, though, this becomes message passing (except if it's read-only on both sides)?

I suspect that the way we use shared memory now would be difficult to use without undefined behavior for a rust userspace? I realize libtock-rs does not currently support IPC.

I am not sure what exactly defines "message passing", but it seems that the ability to

  • take a lock
  • modify 1 byte of the shared memory
  • release the lock

is lower overhead than having to pass the entire shared memory region back as a message. Maybe this is more of a semantic difference

@phil-levis
Copy link
Contributor

This increases code size by enough that the kernel no longer fits into flash on Imix (hence the CI failure). Looking at hail, it increased size by 1264 bytes.

I've been banging up on this a bit with imix. We should do a spring cleaning and see where we could reclaim some space. ADC for example is 6.1k!

@alevy
Copy link
Member Author

alevy commented Feb 16, 2021

@phil-levis:

I've been banging up on this a bit with imix. We should do a spring cleaning and see where we could reclaim some space. ADC for example is 6.1k!

I totally agree, but also I'm not sure how reliable those numbers are at that granularity (the tool is great! don't get me wrong). For example, removing the nrf51 capsule from Hail adds ~1kB to the kernel, and the scheduler module occupies ~10kB on Hail, but only ~5kB on the Nano BLE, and ~8kB on the nrf52840DKs

The code changes really shouldn't require adding much code space at all, so I'll debug this enough to figure out what's going on (though again, removing things sometimes increases the size, so it's pretty brittle) and get imix inline.

But agree that we should do a deeper more comprehensive dive into this. These modules are all far bigger than they need to be.

@phil-levis
Copy link
Contributor

the scheduler module occupies ~10kB on Hail, but only ~5kB on the Nano BLE, and ~8kB on the nrf52840DKs

I think this is inlining: modules higher in the call tree can take on code from lower ones. Happens less in the opposite direction.

@bradjc bradjc added the blocked Waiting on something, like a different PR or a dependency. label Feb 16, 2021
Comment on lines 110 to 104
process.add_mpu_region(
slice.ptr(),
slice.len(),
slice.len(),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably look at #1993 since we are here. What happens if add_mpu_region fails?

@alevy alevy force-pushed the tock-2.0-ipc branch 3 times, most recently from 362f7ea to 7164d50 Compare February 16, 2021 21:13
@alevy
Copy link
Member Author

alevy commented Feb 16, 2021

Please still don't merge, I'm currently testing with libtock-c and still need to update documentation

@bradjc
Copy link
Contributor

bradjc commented Feb 16, 2021

I was playing around with IPC today and I found some issues. #1993 Might not be relevant for this PR but wanted to make a note of it.

@alevy
Copy link
Member Author

alevy commented Feb 17, 2021

I pushed a commit (which I will squash before we merge) that fixes a bug I had introduced in schedule_callback. Because the bug was subtle and hard to catch, I also updated the names of the arguments. While after testing I'm fairly certain that function is now correct, it would be very useful for someone to quickly review my changes (just in that last commit should be fine) and verify that what's going on in that function kind of actually makes sense.

@alevy
Copy link
Member Author

alevy commented Feb 17, 2021

Testing complete. Just going to update documentation, then I think we should be good (modulo squashing the last commit with the first one)

@alevy
Copy link
Member Author

alevy commented Feb 17, 2021

Documentation updated

Copy link
Contributor

@hudson-ayers hudson-ayers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The most recent commit looks correct to me, it seems to match the behavior from when expose_to() was used, and the updated names are helpful

@alevy
Copy link
Member Author

alevy commented Feb 17, 2021

Squashed

kernel/src/mem.rs Outdated Show resolved Hide resolved
kernel/src/sched.rs Show resolved Hide resolved
@bradjc bradjc changed the title Tock 2.0 ipc Tock 2.0 IPC Feb 17, 2021
Mostly a fairly straightforward port, however, I had to change the driver interface
in order to handle 2.0's new allow-semantics. Specifically, allow (both
readonly and readwrite) can no longer return a value, which is how IPC
returned a target descriptor for discovery until now. No matter, the
logic is better suited elsewhere anyway.

Now, discovery is split into an allow_readonly (which is nice so that
large package names can be hardcoded in ROM rather than copied to
memory) followed by a command(IPC, 0). allow_readwrite is _only_ used to
share buffers between apps.

To make this cleaner, I also rejiggerd the arguments to command.
Previous, the first argument to command was the target descriptor for a
notify, while the second argument differentiated between a service and
client notify (the third was unused). Now, the first argument is a
command number, differentiating between a discover (0), service notify
(1), and client notify (2), the second is the target descriptor and the
third is still unused.
@alevy alevy removed the blocked Waiting on something, like a different PR or a dependency. label Feb 17, 2021
@alevy
Copy link
Member Author

alevy commented Feb 17, 2021

bors r+

@bors bors bot merged commit 0b698a8 into tock-2.0-dev Feb 17, 2021
@bors bors bot deleted the tock-2.0-ipc branch February 17, 2021 19:12
@phil-levis
Copy link
Contributor

Hurray!

lschuermann pushed a commit to lschuermann/tock that referenced this pull request Mar 22, 2021
2434: Tock 2.0 IPC r=alevy a=alevy

### Pull Request Overview
This pull request ports the IPC driver to the Tock 2.0 driver interface.

This is a mostly fairly straightforward port. However, the old driver API relied on `allow` to return a discovered IPC endpoint upon success, which is no longer possible in the 2.0 driver interface (allow cannot return a value).

Recall that IPC works as follows:

1. A client "discovers" an IPC service by supplying the kernel with the service's name (taken from the process's TBF name header)
2. If such a service exists, the kernel supplies the client process with a 32-bit non-zero descriptor to reference the service (implemented as the AppId + 1, but that's not guaranteed).
3. The client can use this descriptor to share memory with the service (using allow) and to `notify` it (using command).

For discovery, `allow(IPC, 0)` had been used to both share a buffer containing the service name _and_ request that the kernel perform discovery, returning a descriptor on success.

_This is no longer possible_, and it also not canonical driver semantics.

With this PR, discovery is split into an `allow_readonly` (which is nice anyway so
package names can be hardcoded in ROM rather than copied to
memory) followed by a `command(IPC, 1)` to actually perform the discovery and return a descriptor.

To make this cleaner, I also rejiggerd the arguments in `command`, which now has to mean more than _just_ notify.

Previously, the arguments were:

1. Descriptor (as opposed to the normal command number)
2. Service-or-client-notify (which is actually kind of like a command number)
3. Unused

Now, the arguments are:

1. Command number (can be "discover," "service notify," or "client notify")
2. Target descriptor
3. Unused

There are a number of other changes I believe _could_ make sense to make to IPC before releasing 2.0, but I think they should be decoupled from this pull request, which merely adapts the interface very little to get it working. These are just ideas, and we should discuss all these _separately_:

- Allow both read-only and read-write buffer sharing between client/service
- Use borrow semantics for memory ownership
- Let client subscribe to be notified when a service is available
- Probably more...

### Testing Strategy

Tested using the IPC tutorial in libtock-c (examples/tutorial/05-ipc) after applying changes from tock/libtock-c#176

### TODO or Help Wanted

~~Still need to adapt libtock-c userspace to properly test.~~ (Done in tock/libtock-c#176)

Still need to update documentation

Please review not only the implementation but the change in interface described above. As I said, there are a variety of changes that might make sense for IPC that go beyond these minimal changes. I ask that we get something in that reliably reproduces the existing high-level semantics for IPC and after (or maybe concurrently) decide whether other changes should happen (either before or after 2.0).

### Documentation Updated

- [x] Updated the relevant files in `/docs`, or no updates are required.

### Formatting

- [x] Ran `make prepush`.


Co-authored-by: Amit Aryeh Levy <amit@amitlevy.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants