-
Notifications
You must be signed in to change notification settings - Fork 714
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
base: master
Are you sure you want to change the base?
Conversation
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
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.
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.
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.
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.
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() { |
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 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.
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.
Remove file.
boards/configurations/nrf52840dk/nrf52840dk-test-dynamic-app-load/src/main.rs
Outdated
Show resolved
Hide resolved
Ok, I don't know why it keeps saying the documentation for app_loader.rs is missing. |
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?
|
No. And as a side note, that will be fixed when #4037 is merged. |
deleted kernel/src/dynamic_process_metadata.rs because it was an empty file.
Co-authored-by: Brad Campbell <bradjc5@gmail.com>
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. |
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; |
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.
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.
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 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 |
Work expected to resume fall semester '24 |
Todo:
|
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
capsules/core/src/driver.rs
Outdated
@@ -57,6 +57,7 @@ pub enum NUM { | |||
NvmStorage = 0x50001, | |||
SdCard = 0x50002, | |||
Kv = 0x50003, | |||
AppLoader = 0x50004, |
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 should be 0x10001
I think,
Moved the binary header validity check to process_loading, eliminating the need to track process flash slice in DPL.
Pull Request Overview
This pull request adds dynamic userland application loading to the kernel.
There are three stages to this:
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:app_loader
user app is installed on the device.app_loader
user app and then loads it.app_loader
.adc
and the other wasapp_loader
. In this case, the dynamic process loader finds available flash after the two apps and writesblink
there after which it loads the new app.app_loader
and more padding. Dynamic process loader fits the app in the padding area beforeapp_loader
, and writes new padding betweenblink
andapp_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
Documentation Updated
✅ Updated the relevant files in
/docs
, or no updates are required.Formatting
✅ Ran
make prepush
.