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

Dynamic Userland Application Loading #3941

Open
wants to merge 31 commits into
base: master
Choose a base branch
from

Conversation

viswajith-g
Copy link

@viswajith-g viswajith-g commented Mar 28, 2024

Pull Request Overview

This pull request adds dynamic userland application loading to the kernel.

There are three stages to this:

  1. Setup Phase
  2. Flash Phase
  3. Load Phase

Setup Phase

During the setup phase, a userland application passes the size of the new binary to the app_loader capsule.
This capsule forwards the size to the kernel which determines if there is enough space in the flash to write the new app and what address the app should be written to. On success, the kernel returns the application size it has set up for to the capsule.
The capsule returns a success to the userland app.
On Failure, the kernel passes the reason for failure to the capsule, and the capsule passes a FAIL error to the userland app.

Flash Phase

After the userland app receives the Ok from the capsule, the app sends the binary of the new app 512 bytes at a time along with the offset.
The capsule checks that these values do not violate the bounds dictated by the kernel and then passes the binary data to the kernel.
Upon receiving the binary data, the kernel once again checks if the data is within the bounds specified during the setup phase and then writes the data to flash. On success, the kernel passes success to the app via the capsule. On failure, the same is propagated to the userland application.

Load Phase

Once the userland app receives confirmation that the final set of writes is completed, the app sends a load request to the kernel via the capsule. The kernel looks for the process binary at the address we just finished writing and converts the app binary into a process object. Upon success, the kernel tries to add the process object to the processes array. Once this is done, the process is officially active, and the kernel writes padding after the newly installed app to preserve the continuation of the linked list for future app loading.

Testing Strategy

This pull request was tested by running the app_loader application, a test userland app to verify if the new app (blink) was written and loaded correctly on nRF52840DK. This was tested in three configurations:

  1. When only the app_loader user app is installed on the device.
    • In this case, the dynamic app loader writes the new app after the app_loader user app and then loads it.
  2. When there is another app in addition to app_loader.
    • The device had two apps installed on it, one was adc and the other was app_loader. In this case, the dynamic process loader finds available flash after the two apps and writes blink there after which it loads the new app.
  3. When there is padding in between applications.
    • In this case, the device had padding, then app_loader and more padding. Dynamic process loader fits the app in the padding area before app_loader, and writes new padding between blink and app_loader.

A previous implementation of the process loader without the process binaries is available at this repo. This version was tested on the Imix board, and given not too much has changed, I think the new implementation should work on Imix.

TODO or Help Wanted

  1. More testing.
  2. Feedback and merge.

Documentation Updated

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

Formatting

✅ Ran make prepush.

This version works by writing the app first and then creates the process and adds it to the processes array using the synchronous process loading methods. Finally it writes the padding app. There are new overheads introduced however in the form of load_processes_return_memory() and load_processes_from_flash_return() in process_loading.rs. This is because, when the board initially does setup, we want it to pass the remaining RAM available to the dynamic process loader to load new apps. Currently load_processes() does not return the remaining memory, so those two new methods were added.
fixed some warnings causing CI fail
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
changed it so that the capsule sends a subslice of the buffer to the kernel so the kernel does not have to compare lengths that the capsule provided vs the length of the buffer the capsule actually sent. Generally a safer approach.
Improved the state machine to better track if a userland app is requesting write to the bounds allocated to it during the setup phase. This state machine also ensures that when the process loader is busy, another request cannot be made.
Improved the state machine to better track if a userland app is requesting write to the bounds allocated to it during the setup phase. Also tracks if the dynamic process loader is currently busy. Performed some code cleanup.
The kernel now validates the header before writing the app into flash. In addition, upon failure at any stage, the flash region will be reclaimed for future use. Added a Fail state to help track failure modes better.
fixed two instances in app write when resets were not taking place. also fixed an improper condition check for declared length vs length in header.
An app that was successfully could not be made into a process object because of errant state tracking. That was fixed. Additionally, removed unnecessary state and parameter clears during Busy state.
@viswajith-g viswajith-g requested a review from bradjc April 2, 2024 02:40
Apps were previously aligned but we were not writing padding before new apps to match the linkedlist with the alignment. This is now fixed. A new enum was added to track the padding requirement. Additionally, the setup phase is now asynchronous to accomodate cases where you may or may not have to write padding after an app depending on if there is an app stored beyond our newly written app. This change propogates to the capsule as well.
check_padding_requirements was called twice once during postpad and during prepad to return the next_app_start_addr and previous_app_end_addr respectively. Changed the function so that the values are stored and the function does not have to be called twice.
changed the way the app address is identified during setup phase to align the app to the power of 2 based on its size and start address.
capsules/extra/src/app_loader.rs Outdated Show resolved Hide resolved
capsules/extra/src/app_loader.rs Outdated Show resolved Hide resolved
capsules/extra/src/app_loader.rs Outdated Show resolved Hide resolved
capsules/extra/src/app_loader.rs Outdated Show resolved Hide resolved
capsules/extra/src/app_loader.rs Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
Moved some logic to improve flow. Changed write so that we don't track write validity based on offset increments. Added checks to make sure the header is not manipulated without changing the whole header.
capsules/extra/src/app_loader.rs Outdated Show resolved Hide resolved
capsules/extra/src/app_loader.rs Outdated Show resolved Hide resolved
capsules/extra/src/app_loader.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
Comment on lines 1021 to 1034
match self.find_next_available_address(flash, app_length) {
Ok(new_app_start_address) => {
let offset = new_app_start_address - flash_start;
let new_process_flash = self
.flash
.get()
.get(offset..offset + app_length)
.ok_or(ErrorCode::FAIL)?;

self.new_process_flash.set(new_process_flash);
self.new_app_start_addr.set(new_app_start_address);
self.new_app_length.set(app_length);

match self.padding_requirement.get() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is too confusing. find_next_available_address() should just do a find and return everything (including the need for padding). That state is mutated deep in here makes it hard to reason about how this works.

kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
kernel/src/dynamic_process_loading.rs Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove file.

kernel/src/process_loading.rs Outdated Show resolved Hide resolved
kernel/src/process_loading.rs Outdated Show resolved Hide resolved
kernel/src/process_loading.rs Outdated Show resolved Hide resolved
kernel/src/process_loading.rs Outdated Show resolved Hide resolved
kernel/src/process_loading.rs Outdated Show resolved Hide resolved
Removed kernel warnings
@viswajith-g
Copy link
Author

Ok, I don't know why it keeps saying the documentation for app_loader.rs is missing.

@bradjc
Copy link
Contributor

bradjc commented Jul 4, 2024

Can you remove kernel/src/dynamic_process_metadata.rs?

@viswajith-g
Copy link
Author

Can you remove kernel/src/dynamic_process_metadata.rs?

If I do that, make prepush fails, but the code works as intended. Is that an issue?

vis@Vis:~/rebase_apploader/tock$ make prepush
make: *** No rule to make target 'kernel/src/dynamic_process_metadata.rs', needed by 'tools/.format_fresh'.  Stop.

@bradjc
Copy link
Contributor

bradjc commented Jul 4, 2024

No.

And as a side note, that will be fixed when #4037 is merged.

viswajith-g and others added 2 commits July 4, 2024 00:38
deleted kernel/src/dynamic_process_metadata.rs because it was an empty file.
Co-authored-by: Brad Campbell <bradjc5@gmail.com>
@bradjc
Copy link
Contributor

bradjc commented Jul 5, 2024

Ok great I think this is to a point where I can take a deeper look and actually try it. I will try to do that soon.

Comment on lines 536 to 550
fn check_overlap_region(
&self,
new_start_address: usize,
app_length: usize,
) -> Result<(), (usize, ProcessLoadError)> {
// Find the next open process slot.
let new_process_count = self.find_open_process_slot().unwrap_or_default();
let new_process_start_address = new_start_address;
let new_process_end_address = new_process_start_address + app_length - 1;

self.procs.map(|procs| {
for (proc_index, value) in procs.iter().enumerate() {
if proc_index < new_process_count {
let process_start_address = value.unwrap().get_addresses().flash_start;
let process_end_address = value.unwrap().get_addresses().flash_end;
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately this isn't going to work. The main issue is that there is no requirement that loaded processes (ie in the PROCESSES array) represent all stored process binaries.

What we probably need to do instead is use the ProcessBinaries array.

@bradjc
Copy link
Contributor

bradjc commented Jul 10, 2024

I've gone through most of the main kernel library, cleaning things up and making the comments consistent with the kernel crate.

I removed the unsafe, and I think this PR will build again with #4079.

I was hoping to be able to test this but we need to improve the logic around searching for a window to store the new process into. Also, we shouldn't use unwrap(), and in this case the current uses are red flags that the code isn't quite right.

@alevy
Copy link
Member

alevy commented Aug 30, 2024

Work expected to resume fall semester '24

@bradjc
Copy link
Contributor

bradjc commented Jan 17, 2025

Todo:

  • Implement the logic for finding a suitable region in flash within the sequential process loader.
  • Move the new dynamic process storage mechanism to its own file (separate from the trait).

viswajith-g and others added 2 commits January 28, 2025 19:09
previous iteration erroneously used the size of the tbf header instead of the size of an already present application to compute the available address for a new app
@@ -57,6 +57,7 @@ pub enum NUM {
NvmStorage = 0x50001,
SdCard = 0x50002,
Kv = 0x50003,
AppLoader = 0x50004,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be 0x10001 I think,

@github-actions github-actions bot added the HIL This affects a Tock HIL interface. label Feb 1, 2025
@github-actions github-actions bot removed the HIL This affects a Tock HIL interface. label Feb 1, 2025
viswajith-g and others added 2 commits January 31, 2025 21:32
Moved the binary header validity check to process_loading, eliminating the need to track process flash slice in DPL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants