diff --git a/.github/workflows/tockbot-nightly.yml b/.github/workflows/tockbot-nightly.yml new file mode 100644 index 0000000000..158373505d --- /dev/null +++ b/.github/workflows/tockbot-nightly.yml @@ -0,0 +1,134 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +name: "Tockbot" + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + inputs: + dispatch-job: + description: 'Which job to execute (choose between "all", "maint-nightly")' + required: true + default: 'all' + dry-run: + description: 'Whether to execute the jobs as dry-run' + required: true + default: true + +jobs: + dispatcher: + runs-on: ubuntu-latest + + # This job determines which other jobs should be run: + outputs: + run-maint-nightly: ${{ steps.dispatch-logic.outputs.run-maint-nightly }} + dry-run: ${{ steps.dispatch-logic.outputs.dry-run }} + + steps: + # On pushes we want to check whether any changes have been made + # to the Tockbot code base. Disabled for now: + - uses: actions/checkout@v4 + + # Dispatcher business logic: + - name: Dispatch Tockbot Jobs + id: dispatch-logic + env: + DISPATCH_JOB: ${{ github.event.inputs.dispatch-job }} + DISPATCH_DRY_RUN: ${{ github.event.inputs.dry-run }} + run: | + if [ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]; then + if [ "$DISPATCH_DRY_RUN" == "true" ]; then + echo "dry-run=true" >> $GITHUB_OUTPUT + elif [ "$DISPATCH_DRY_RUN" == "false" ]; then + echo "dry-run=false" >> $GITHUB_OUTPUT + else + echo "Error: dry-run not a boolean: \"$DISPATCH_DRY_RUN\"" >&2 + exit 1 + fi + + if [ "$DISPATCH_JOB" == "all" ]; then + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + elif [ "$DISPATCH_JOB" == "maint-nightly" ]; then + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + else + echo "Error: unknown job \"$DISPATCH_JOB\"" >&2 + exit 1 + fi + elif [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + echo "dry-run=true" >> $GITHUB_OUTPUT + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + elif [ "$GITHUB_EVENT_NAME" == "schedule" ]; then + echo "dry-run=false" >> $GITHUB_OUTPUT + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + else + echo "Error: unknown event name \"$GITHUB_EVENT_NAME\"" >&2 + exit 1 + fi + + maint-nightly: + runs-on: ubuntu-latest + + # Only run this job if the dispatcher determined to schedule the + # "maint-nightly" or "dry-run" jobs: + needs: dispatcher + if: ${{ needs.dispatcher.outputs.run-maint-nightly == 'true' && needs.dispatcher.outputs.dry-run != 'true' }} + + permissions: + # Give GITHUB_TOKEN write permissions to modify PRs and issues: + pull-requests: write + issues: write + + steps: + # Requires a tock checkout to run from: + - uses: actions/checkout@v4 + + # Setup Python and install dependencies: + - uses: actions/setup-python@v5 + - name: Install Python Dependencies + run: pip install -r tools/tockbot/requirements.txt + + # Run nightly tockbot maintenance: + - name: Nightly Tockbot Maintenance + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRY_RUN: ${{ needs.dispatcher.outputs.dry-run == 'true' && '-n' || '' }} + run: | + cd tools/tockbot/ + ./tockbot.py -v $DRY_RUN maint-nightly -c ./maint_nightly.yaml + + # We'd like to avoid duplicating this, either by using conditionals in the + # permissions key, or by using YAML anchors, neither of which are supported by + # GH Actions... + maint-nightly-dry-run: + runs-on: ubuntu-latest + + # Only run this job if the dispatcher determined to schedule the + # "maint-nightly" or "dry-run" jobs: + needs: dispatcher + if: ${{ needs.dispatcher.outputs.run-maint-nightly == 'true' && needs.dispatcher.outputs.dry-run == 'true' }} + + permissions: + # Dry-run, read-only access: + pull-requests: read + issues: read + + steps: + # Requires a tock checkout to run from: + - uses: actions/checkout@v4 + + # Setup Python and install dependencies: + - uses: actions/setup-python@v5 + - name: Install Python Dependencies + run: pip install -r tools/tockbot/requirements.txt + + # Run nightly tockbot maintenance: + - name: Nightly Tockbot Maintenance + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRY_RUN: ${{ needs.dispatcher.outputs.dry-run == 'true' && '-n' || '' }} + run: | + cd tools/tockbot/ + ./tockbot.py -v $DRY_RUN maint-nightly -c ./maint_nightly.yaml diff --git a/Cargo.toml b/Cargo.toml index e4be134394..0f4ca54e85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "boards/msp_exp432p401r", "boards/microbit_v2", "boards/wm1110dev", + "boards/makepython-nrf52840", "boards/nordic/nrf52840dk", "boards/nordic/nrf52840_dongle", "boards/nordic/nrf52dk", diff --git a/arch/cortex-m/src/lib.rs b/arch/cortex-m/src/lib.rs index 858523ed29..6fae6e533e 100644 --- a/arch/cortex-m/src/lib.rs +++ b/arch/cortex-m/src/lib.rs @@ -384,12 +384,12 @@ pub unsafe fn print_cortexm_state(writer: &mut dyn Write) { // ARM assembly since it will not compile. /////////////////////////////////////////////////////////////////// -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn unhandled_interrupt() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn initialize_ram_jump_to_main() { unimplemented!() } diff --git a/arch/cortex-m/src/scb.rs b/arch/cortex-m/src/scb.rs index 2b254506f4..b5727ab123 100644 --- a/arch/cortex-m/src/scb.rs +++ b/arch/cortex-m/src/scb.rs @@ -316,7 +316,7 @@ pub unsafe fn disable_fpca() { } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe fn disable_fpca() { // Dummy read register, to satisfy the `Readable` trait import on // non-ARM platforms. diff --git a/arch/cortex-m/src/support.rs b/arch/cortex-m/src/support.rs index 581c141509..7e748bde38 100644 --- a/arch/cortex-m/src/support.rs +++ b/arch/cortex-m/src/support.rs @@ -39,19 +39,19 @@ where } // Mock implementations for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] /// NOP instruction (mock) pub fn nop() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] /// WFI instruction (mock) pub unsafe fn wfi() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe fn atomic(_f: F) -> R where F: FnOnce() -> R, diff --git a/arch/cortex-m0/src/lib.rs b/arch/cortex-m0/src/lib.rs index 3ea42e6852..9a9d6f1517 100644 --- a/arch/cortex-m0/src/lib.rs +++ b/arch/cortex-m0/src/lib.rs @@ -72,7 +72,7 @@ unsafe extern "C" fn hard_fault_handler_kernel(faulting_stack: *mut u32) -> ! { } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe extern "C" fn generic_isr() { unimplemented!() } @@ -167,7 +167,7 @@ global_asm!( ); // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe extern "C" fn systick_handler_m0() { unimplemented!() } @@ -210,7 +210,7 @@ global_asm!( ); // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe extern "C" fn svc_handler() { unimplemented!() } @@ -249,7 +249,7 @@ svc_handler: ); // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe extern "C" fn hard_fault_handler() { unimplemented!() } @@ -365,7 +365,7 @@ impl cortexm::CortexMVariant for CortexM0 { const HARD_FAULT_HANDLER: unsafe extern "C" fn() = hard_fault_handler; // Mock implementation for tests on Travis-CI. - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe fn switch_to_user( _user_stack: *const usize, _process_regs: &mut [usize; 8], diff --git a/arch/cortex-m0p/src/lib.rs b/arch/cortex-m0p/src/lib.rs index 65601f3450..30c9958860 100644 --- a/arch/cortex-m0p/src/lib.rs +++ b/arch/cortex-m0p/src/lib.rs @@ -31,7 +31,7 @@ pub use cortexm::CortexMVariant; use cortexm0::CortexM0; // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn svc_handler_m0p() { unimplemented!() } @@ -96,7 +96,7 @@ impl cortexm::CortexMVariant for CortexM0P { CortexM0::switch_to_user(user_stack, process_regs) } - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe fn switch_to_user( _user_stack: *const usize, _process_regs: &mut [usize; 8], diff --git a/arch/cortex-m3/src/lib.rs b/arch/cortex-m3/src/lib.rs index 33c481c679..360c326cb9 100644 --- a/arch/cortex-m3/src/lib.rs +++ b/arch/cortex-m3/src/lib.rs @@ -39,7 +39,7 @@ impl cortexm::CortexMVariant for CortexM3 { cortexv7m::switch_to_user_arm_v7m(user_stack, process_regs) } - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe fn switch_to_user( _user_stack: *const usize, _process_regs: &mut [usize; 8], diff --git a/arch/cortex-m4/src/lib.rs b/arch/cortex-m4/src/lib.rs index 08fe9dcd86..45aabff6f4 100644 --- a/arch/cortex-m4/src/lib.rs +++ b/arch/cortex-m4/src/lib.rs @@ -41,7 +41,7 @@ impl cortexm::CortexMVariant for CortexM4 { cortexv7m::switch_to_user_arm_v7m(user_stack, process_regs) } - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe fn switch_to_user( _user_stack: *const usize, _process_regs: &mut [usize; 8], diff --git a/arch/cortex-m7/src/lib.rs b/arch/cortex-m7/src/lib.rs index 2a58763b3d..2a9fcbb39f 100644 --- a/arch/cortex-m7/src/lib.rs +++ b/arch/cortex-m7/src/lib.rs @@ -41,7 +41,7 @@ impl cortexm::CortexMVariant for CortexM7 { cortexv7m::switch_to_user_arm_v7m(user_stack, process_regs) } - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] unsafe fn switch_to_user( _user_stack: *const usize, _process_regs: &mut [usize; 8], diff --git a/arch/cortex-v7m/src/lib.rs b/arch/cortex-v7m/src/lib.rs index 5fdf094a72..31683a9cbe 100644 --- a/arch/cortex-v7m/src/lib.rs +++ b/arch/cortex-v7m/src/lib.rs @@ -561,22 +561,22 @@ pub fn ipsr_isr_number_to_str(isr_number: usize) -> &'static str { // ARM assembly since it will not compile. /////////////////////////////////////////////////////////////////// -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn systick_handler_arm_v7m() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn svc_handler_arm_v7m() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn generic_isr_arm_v7m() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn switch_to_user_arm_v7m( _user_stack: *const u8, _process_regs: &mut [usize; 8], @@ -584,7 +584,7 @@ pub unsafe extern "C" fn switch_to_user_arm_v7m( unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn hard_fault_handler_arm_v7m() { unimplemented!() } diff --git a/arch/riscv/src/csr/mcycle.rs b/arch/riscv/src/csr/mcycle.rs index 3a2e6f99fd..7384bb33d7 100644 --- a/arch/riscv/src/csr/mcycle.rs +++ b/arch/riscv/src/csr/mcycle.rs @@ -13,7 +13,7 @@ register_bitfields![usize, // `mcycleh` is the higher XLEN bits of the number of elapsed cycles. // It does not exist on riscv64. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub mcycleh [ mcycleh OFFSET(0) NUMBITS(crate::XLEN) [] diff --git a/arch/riscv/src/csr/minstret.rs b/arch/riscv/src/csr/minstret.rs index e94b034136..a930418998 100644 --- a/arch/riscv/src/csr/minstret.rs +++ b/arch/riscv/src/csr/minstret.rs @@ -13,7 +13,7 @@ register_bitfields![usize, // `minstreth` is the higher XLEN bits of the number of elapsed instructions. // It does not exist on riscv64. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub minstreth [ minstreth OFFSET(0) NUMBITS(crate::XLEN) [] diff --git a/arch/riscv/src/csr/mod.rs b/arch/riscv/src/csr/mod.rs index 876b5708b0..ca8b3018f0 100644 --- a/arch/riscv/src/csr/mod.rs +++ b/arch/riscv/src/csr/mod.rs @@ -43,38 +43,38 @@ pub mod utvec; // something (as it would be if compiled for a host OS). pub struct CSR { - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub minstreth: ReadWriteRiscvCsr, pub minstret: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub mcycleh: ReadWriteRiscvCsr, pub mcycle: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg0: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg1: ReadWriteRiscvCsr, pub pmpcfg2: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg3: ReadWriteRiscvCsr, pub pmpcfg4: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg5: ReadWriteRiscvCsr, pub pmpcfg6: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg7: ReadWriteRiscvCsr, pub pmpcfg8: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg9: ReadWriteRiscvCsr, pub pmpcfg10: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg11: ReadWriteRiscvCsr, pub pmpcfg12: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg13: ReadWriteRiscvCsr, pub pmpcfg14: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg15: ReadWriteRiscvCsr, pub pmpaddr0: ReadWriteRiscvCsr, @@ -152,7 +152,7 @@ pub struct CSR { pub mstatus: ReadWriteRiscvCsr, pub mseccfg: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub mseccfgh: ReadWriteRiscvCsr, pub utvec: ReadWriteRiscvCsr, @@ -161,37 +161,37 @@ pub struct CSR { // Define the "addresses" of each CSR register. pub const CSR: &CSR = &CSR { - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] minstreth: ReadWriteRiscvCsr::new(), minstret: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] mcycleh: ReadWriteRiscvCsr::new(), mcycle: ReadWriteRiscvCsr::new(), pmpcfg0: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg1: ReadWriteRiscvCsr::new(), pmpcfg2: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg3: ReadWriteRiscvCsr::new(), pmpcfg4: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg5: ReadWriteRiscvCsr::new(), pmpcfg6: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg7: ReadWriteRiscvCsr::new(), pmpcfg8: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg9: ReadWriteRiscvCsr::new(), pmpcfg10: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg11: ReadWriteRiscvCsr::new(), pmpcfg12: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg13: ReadWriteRiscvCsr::new(), pmpcfg14: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg15: ReadWriteRiscvCsr::new(), pmpaddr0: ReadWriteRiscvCsr::new(), @@ -269,7 +269,7 @@ pub const CSR: &CSR = &CSR { mstatus: ReadWriteRiscvCsr::new(), mseccfg: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] mseccfgh: ReadWriteRiscvCsr::new(), utvec: ReadWriteRiscvCsr::new(), @@ -278,7 +278,7 @@ pub const CSR: &CSR = &CSR { impl CSR { // resets the cycle counter to 0 - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub fn reset_cycle_counter(&self) { // Write lower first so that we don't overflow before writing the upper CSR.mcycle.write(mcycle::mcycle::mcycle.val(0)); @@ -292,7 +292,7 @@ impl CSR { } // reads the cycle counter - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub fn read_cycle_counter(&self) -> u64 { let (mut top, mut bot): (usize, usize); @@ -319,28 +319,28 @@ impl CSR { pub fn pmpconfig_get(&self, index: usize) -> usize { match index { 0 => self.pmpcfg0.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 1 => self.pmpcfg1.get(), 2 => self.pmpcfg2.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 3 => self.pmpcfg3.get(), 4 => self.pmpcfg4.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 5 => self.pmpcfg5.get(), 6 => self.pmpcfg6.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 7 => self.pmpcfg7.get(), 8 => self.pmpcfg8.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 9 => self.pmpcfg9.get(), 10 => self.pmpcfg10.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 11 => self.pmpcfg11.get(), 12 => self.pmpcfg12.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 13 => self.pmpcfg13.get(), 14 => self.pmpcfg14.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 15 => self.pmpcfg15.get(), _ => unreachable!(), } @@ -349,28 +349,28 @@ impl CSR { pub fn pmpconfig_set(&self, index: usize, value: usize) { match index { 0 => self.pmpcfg0.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 1 => self.pmpcfg1.set(value), 2 => self.pmpcfg2.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 3 => self.pmpcfg3.set(value), 4 => self.pmpcfg4.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 5 => self.pmpcfg5.set(value), 6 => self.pmpcfg6.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 7 => self.pmpcfg7.set(value), 8 => self.pmpcfg8.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 9 => self.pmpcfg9.set(value), 10 => self.pmpcfg10.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 11 => self.pmpcfg11.set(value), 12 => self.pmpcfg12.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 13 => self.pmpcfg13.set(value), 14 => self.pmpcfg14.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 15 => self.pmpcfg15.set(value), _ => unreachable!(), } @@ -383,28 +383,28 @@ impl CSR { ) { match index { 0 => self.pmpcfg0.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 1 => self.pmpcfg1.modify(field), 2 => self.pmpcfg2.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 3 => self.pmpcfg3.modify(field), 4 => self.pmpcfg4.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 5 => self.pmpcfg5.modify(field), 6 => self.pmpcfg6.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 7 => self.pmpcfg7.modify(field), 8 => self.pmpcfg8.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 9 => self.pmpcfg9.modify(field), 10 => self.pmpcfg10.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 11 => self.pmpcfg11.modify(field), 12 => self.pmpcfg12.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 13 => self.pmpcfg13.modify(field), 14 => self.pmpcfg14.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 15 => self.pmpcfg15.modify(field), _ => unreachable!(), } diff --git a/arch/riscv/src/csr/mseccfg.rs b/arch/riscv/src/csr/mseccfg.rs index 6100e9c789..7f6b02bc4e 100644 --- a/arch/riscv/src/csr/mseccfg.rs +++ b/arch/riscv/src/csr/mseccfg.rs @@ -5,7 +5,7 @@ use kernel::utilities::registers::register_bitfields; // Default to 32 bit if compiling for debug/testing. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub mseccfg [ mml OFFSET(0) NUMBITS(1) [], @@ -14,7 +14,7 @@ register_bitfields![usize, ] ]; -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub mseccfgh [ // This isn't a real entry, it just avoids compilation errors diff --git a/arch/riscv/src/csr/pmpaddr.rs b/arch/riscv/src/csr/pmpaddr.rs index bd57b53caf..ac62830cf0 100644 --- a/arch/riscv/src/csr/pmpaddr.rs +++ b/arch/riscv/src/csr/pmpaddr.rs @@ -5,7 +5,7 @@ use kernel::utilities::registers::register_bitfields; // Default to 32 bit if compiling for debug/testing. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub pmpaddr [ addr OFFSET(0) NUMBITS(crate::XLEN) [] diff --git a/arch/riscv/src/csr/pmpconfig.rs b/arch/riscv/src/csr/pmpconfig.rs index eedf0cf5bc..a341be5f13 100644 --- a/arch/riscv/src/csr/pmpconfig.rs +++ b/arch/riscv/src/csr/pmpconfig.rs @@ -5,7 +5,10 @@ use kernel::utilities::registers::register_bitfields; // Default to 32 bit if compiling for debug/testing. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(any( + target_arch = "riscv32", + all(not(target_arch = "riscv32"), not(target_arch = "riscv64")) +))] register_bitfields![usize, pub pmpcfg [ r0 OFFSET(0) NUMBITS(1) [], diff --git a/arch/riscv/src/lib.rs b/arch/riscv/src/lib.rs index 6f5239225f..dedfb1ad0d 100644 --- a/arch/riscv/src/lib.rs +++ b/arch/riscv/src/lib.rs @@ -17,5 +17,8 @@ pub const XLEN: usize = 64; // Default to 32 bit if no architecture is specified of if this is being // compiled for testing on a different architecture. -#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] +#[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" +)))] pub const XLEN: usize = 32; diff --git a/arch/rv32i/src/lib.rs b/arch/rv32i/src/lib.rs index 3ea32065d8..8a8d3d1ab4 100644 --- a/arch/rv32i/src/lib.rs +++ b/arch/rv32i/src/lib.rs @@ -181,7 +181,7 @@ pub unsafe fn configure_trap_handler(mode: PermissionMode) { } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub extern "C" fn _start_trap() { unimplemented!() } @@ -475,7 +475,7 @@ pub unsafe fn semihost_command(command: usize, arg0: usize, arg1: usize) -> usiz } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn semihost_command(_command: usize, _arg0: usize, _arg1: usize) -> usize { unimplemented!() } diff --git a/arch/rv32i/src/support.rs b/arch/rv32i/src/support.rs index 44258f6ffc..ad148da50a 100644 --- a/arch/rv32i/src/support.rs +++ b/arch/rv32i/src/support.rs @@ -50,13 +50,13 @@ where } // Mock implementations for tests on Travis-CI. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] /// NOP instruction (mock) pub fn nop() { unimplemented!() } -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] /// WFI instruction (mock) pub unsafe fn wfi() { unimplemented!() diff --git a/arch/rv32i/src/syscall.rs b/arch/rv32i/src/syscall.rs index 09770779a2..ba13ae4875 100644 --- a/arch/rv32i/src/syscall.rs +++ b/arch/rv32i/src/syscall.rs @@ -212,7 +212,7 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { } // Mock implementation for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] unsafe fn switch_to_process( &self, _accessible_memory_start: *const u8, diff --git a/boards/clue_nrf52840/src/main.rs b/boards/clue_nrf52840/src/main.rs index 2c7484c390..be054aa2ce 100644 --- a/boards/clue_nrf52840/src/main.rs +++ b/boards/clue_nrf52840/src/main.rs @@ -144,6 +144,11 @@ type SHT3xSensor = components::sht3x::SHT3xComponentType< type TemperatureDriver = components::temperature::TemperatureComponentType; type HumidityDriver = components::humidity::HumidityComponentType; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + /// Supported drivers by the platform pub struct Platform { ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< @@ -154,7 +159,7 @@ pub struct Platform { nrf52::rtc::Rtc<'static>, >, >, - ieee802154_radio: &'static capsules_extra::ieee802154::RadioDriver<'static>, + ieee802154_radio: &'static Ieee802154Driver, console: &'static capsules_core::console::Console<'static>, proximity: &'static capsules_extra::proximity::ProximitySensor<'static>, gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, diff --git a/boards/components/src/ieee802154.rs b/boards/components/src/ieee802154.rs index c34e2ec7c5..6fa0048fd1 100644 --- a/boards/components/src/ieee802154.rs +++ b/boards/components/src/ieee802154.rs @@ -54,6 +54,8 @@ macro_rules! mux_aes128ccm_component_static { };}; } +pub type MuxAes128ccmComponentType = MuxAES128CCM<'static, A>; + pub struct MuxAes128ccmComponent + AES128Ctr + AES128CBC + AES128ECB> { aes: &'static A, } @@ -95,10 +97,39 @@ macro_rules! ieee802154_component_static { > ); - let mux_mac = kernel::static_buf!(capsules_extra::ieee802154::virtual_mac::MuxMac<'static>); - let mac_user = - kernel::static_buf!(capsules_extra::ieee802154::virtual_mac::MacUser<'static>); - let radio_driver = kernel::static_buf!(capsules_extra::ieee802154::RadioDriver<'static>); + let mux_mac = kernel::static_buf!( + capsules_extra::ieee802154::virtual_mac::MuxMac< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, $R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, + >, + > + ); + let mac_user = kernel::static_buf!( + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, $R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, + >, + > + ); + let radio_driver = kernel::static_buf!( + capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, $R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, + >, + >, + > + ); let radio_buf = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); let radio_rx_buf = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); @@ -120,6 +151,24 @@ macro_rules! ieee802154_component_static { };}; } +pub type Ieee802154ComponentType = capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, +>; + +pub type Ieee802154ComponentMacDeviceType = capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, +>; + pub struct Ieee802154Component< R: 'static + kernel::hil::radio::Radio<'static>, A: 'static + AES128<'static> + AES128Ctr + AES128CBC + AES128ECB, @@ -176,17 +225,64 @@ impl< capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, >, >, - &'static mut MaybeUninit>, - &'static mut MaybeUninit>, - &'static mut MaybeUninit>, + &'static mut MaybeUninit< + capsules_extra::ieee802154::virtual_mac::MuxMac< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + &'static mut MaybeUninit< + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + &'static mut MaybeUninit< + capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + >, &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, &'static mut MaybeUninit<[u8; CRYPT_SIZE]>, &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, ); type Output = ( - &'static capsules_extra::ieee802154::RadioDriver<'static>, - &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static>, + &'static capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + &'static capsules_extra::ieee802154::virtual_mac::MuxMac< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, ); fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { diff --git a/boards/components/src/lib.rs b/boards/components/src/lib.rs index 28f27f9585..ea96df6da2 100644 --- a/boards/components/src/lib.rs +++ b/boards/components/src/lib.rs @@ -77,6 +77,7 @@ pub mod si7021; pub mod siphash; pub mod sound_pressure; pub mod spi; +pub mod ssd1306; pub mod st77xx; pub mod temperature; pub mod temperature_rp2040; diff --git a/boards/components/src/screen.rs b/boards/components/src/screen.rs index 7b65be8769..4b9e8ac580 100644 --- a/boards/components/src/screen.rs +++ b/boards/components/src/screen.rs @@ -31,10 +31,12 @@ //! ``` use capsules_extra::screen::Screen; +use capsules_extra::screen_shared::ScreenShared; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; +use kernel::hil; #[macro_export] macro_rules! screen_component_static { @@ -99,3 +101,71 @@ impl Component for ScreenComponent screen } } + +#[macro_export] +macro_rules! screen_shared_component_static { + ($s:literal, $S:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; $s]); + let screen = kernel::static_buf!(capsules_extra::screen_shared::ScreenShared<$S>); + + (buffer, screen) + };}; +} + +pub type ScreenSharedComponentType = capsules_extra::screen_shared::ScreenShared<'static, S>; + +pub struct ScreenSharedComponent< + const SCREEN_BUF_LEN: usize, + S: hil::screen::Screen<'static> + 'static, +> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + screen: &'static S, + apps_regions: &'static [capsules_extra::screen_shared::AppScreenRegion], +} + +impl> + ScreenSharedComponent +{ + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + screen: &'static S, + apps_regions: &'static [capsules_extra::screen_shared::AppScreenRegion], + ) -> ScreenSharedComponent { + ScreenSharedComponent { + board_kernel, + driver_num, + screen, + apps_regions, + } + } +} + +impl> Component + for ScreenSharedComponent +{ + type StaticInput = ( + &'static mut MaybeUninit<[u8; SCREEN_BUF_LEN]>, + &'static mut MaybeUninit>, + ); + type Output = &'static ScreenShared<'static, S>; + + fn finalize(self, static_input: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant_screen = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let buffer = static_input.0.write([0; SCREEN_BUF_LEN]); + + let screen = static_input.1.write(ScreenShared::new( + self.screen, + grant_screen, + buffer, + self.apps_regions, + )); + + kernel::hil::screen::Screen::set_client(self.screen, screen); + + screen + } +} diff --git a/boards/components/src/ssd1306.rs b/boards/components/src/ssd1306.rs new file mode 100644 index 0000000000..1669c385c0 --- /dev/null +++ b/boards/components/src/ssd1306.rs @@ -0,0 +1,89 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Components for the SSD1306 OLED screen. +//! +//! Usage +//! ----- +//! ```rust +//! +//! let ssd1306_i2c = components::i2c::I2CComponent::new(i2c_bus, 0x3c) +//! .finalize(components::i2c_component_static!(nrf52840::i2c::TWI)); +//! +//! let ssd1306 = components::ssd1306::Ssd1306Component::new(ssd1306_i2c, true) +//! .finalize(components::ssd1306_component_static!(nrf52840::i2c::TWI)); +//! ``` + +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil; + +// Setup static space for the objects. +#[macro_export] +macro_rules! ssd1306_component_static { + ($I: ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; capsules_extra::ssd1306::BUFFER_SIZE]); + let ssd1306 = kernel::static_buf!( + capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + + (buffer, ssd1306) + };}; +} + +pub type Ssd1306ComponentType = capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, +>; + +pub struct Ssd1306Component + 'static> { + i2c_device: &'static capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + use_charge_pump: bool, +} + +impl + 'static> Ssd1306Component { + pub fn new( + i2c_device: &'static capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + use_charge_pump: bool, + ) -> Ssd1306Component { + Ssd1306Component { + i2c_device, + use_charge_pump, + } + } +} + +impl + 'static> Component for Ssd1306Component { + type StaticInput = ( + &'static mut MaybeUninit<[u8; capsules_extra::ssd1306::BUFFER_SIZE]>, + &'static mut MaybeUninit< + capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + >, + >, + ); + type Output = &'static capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + >; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let buffer = static_buffer + .0 + .write([0; capsules_extra::ssd1306::BUFFER_SIZE]); + + let ssd1306 = static_buffer.1.write(capsules_extra::ssd1306::Ssd1306::new( + self.i2c_device, + buffer, + self.use_charge_pump, + )); + self.i2c_device.set_client(ssd1306); + + ssd1306 + } +} diff --git a/boards/components/src/udp_mux.rs b/boards/components/src/udp_mux.rs index b20e88c794..021c97264a 100644 --- a/boards/components/src/udp_mux.rs +++ b/boards/components/src/udp_mux.rs @@ -65,7 +65,7 @@ pub const MAX_PAYLOAD_LEN: usize = 200; //The max size UDP message that can be s // Setup static space for the objects. #[macro_export] macro_rules! udp_mux_component_static { - ($A:ty $(,)?) => {{ + ($A:ty, $M:ty $(,)?) => {{ use capsules_core; use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use capsules_extra::net::sixlowpan::{sixlowpan_compression, sixlowpan_state}; @@ -75,7 +75,7 @@ macro_rules! udp_mux_component_static { let alarm = kernel::static_buf!(VirtualMuxAlarm<'static, $A>); let mac_user = - kernel::static_buf!(capsules_extra::ieee802154::virtual_mac::MacUser<'static>); + kernel::static_buf!(capsules_extra::ieee802154::virtual_mac::MacUser<'static, $M>); let sixlowpan = kernel::static_buf!( sixlowpan_state::Sixlowpan< 'static, @@ -154,8 +154,8 @@ macro_rules! udp_mux_component_static { };}; } -pub struct UDPMuxComponent + 'static> { - mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static>, +pub struct UDPMuxComponent + 'static, M: MacDevice<'static> + 'static> { + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, M>, ctx_pfix_len: u8, ctx_pfix: [u8; 16], dst_mac_addr: MacAddress, @@ -164,9 +164,9 @@ pub struct UDPMuxComponent + 'static> { alarm_mux: &'static MuxAlarm<'static, A>, } -impl + 'static> UDPMuxComponent { +impl + 'static, M: MacDevice<'static>> UDPMuxComponent { pub fn new( - mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static>, + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, M>, ctx_pfix_len: u8, ctx_pfix: [u8; 16], dst_mac_addr: MacAddress, @@ -186,10 +186,10 @@ impl + 'static> UDPMuxComponent { } } -impl + 'static> Component for UDPMuxComponent { +impl + 'static, M: MacDevice<'static>> Component for UDPMuxComponent { type StaticInput = ( &'static mut MaybeUninit>, - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, &'static mut MaybeUninit< sixlowpan_state::Sixlowpan< 'static, diff --git a/boards/imix/src/main.rs b/boards/imix/src/main.rs index 87b7a7c3ff..6763e91d26 100644 --- a/boards/imix/src/main.rs +++ b/boards/imix/src/main.rs @@ -115,6 +115,13 @@ type SI7021Sensor = components::si7021::SI7021ComponentType< type TemperatureDriver = components::temperature::TemperatureComponentType; type HumidityDriver = components::humidity::HumidityComponentType; +type Rf233 = capsules_extra::rf233::RF233< + 'static, + VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw<'static>>, +>; +type Ieee802154MacDevice = + components::ieee802154::Ieee802154ComponentMacDeviceType>; + struct Imix { pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, @@ -705,7 +712,10 @@ pub unsafe fn main() { local_ip_ifaces, mux_alarm, ) - .finalize(components::udp_mux_component_static!(sam4l::ast::Ast)); + .finalize(components::udp_mux_component_static!( + sam4l::ast::Ast, + Ieee802154MacDevice + )); // UDP driver initialization happens here let udp_driver = components::udp_driver::UDPDriverComponent::new( diff --git a/boards/imix/src/test/aes_test.rs b/boards/imix/src/test/aes_test.rs index eb53747d2d..13411daaf6 100644 --- a/boards/imix/src/test/aes_test.rs +++ b/boards/imix/src/test/aes_test.rs @@ -53,7 +53,7 @@ unsafe fn static_init_test_ctr(aes: &'static Aes) -> &'static TestAes128Ctr<'sta static_init!( TestAes128Ctr<'static, Aes>, - TestAes128Ctr::new(aes, key, iv, source, data) + TestAes128Ctr::new(aes, key, iv, source, data, true) ) } @@ -65,6 +65,6 @@ unsafe fn static_init_test_cbc(aes: &'static Aes) -> &'static TestAes128Cbc<'sta static_init!( TestAes128Cbc<'static, Aes>, - TestAes128Cbc::new(aes, key, iv, source, data) + TestAes128Cbc::new(aes, key, iv, source, data, true) ) } diff --git a/boards/imix/src/test/icmp_lowpan_test.rs b/boards/imix/src/test/icmp_lowpan_test.rs index 72953e448c..be4b1cf57e 100644 --- a/boards/imix/src/test/icmp_lowpan_test.rs +++ b/boards/imix/src/test/icmp_lowpan_test.rs @@ -67,8 +67,18 @@ pub struct LowpanICMPTest<'a, A: time::Alarm<'a>> { net_cap: &'static NetworkCapability, } +type Rf233 = capsules_extra::rf233::RF233< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + sam4l::spi::SpiHw<'static>, + >, +>; +type Ieee802154MacDevice = + components::ieee802154::Ieee802154ComponentMacDeviceType>; + pub unsafe fn run( - mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static>, + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, Ieee802154MacDevice>, mux_alarm: &'static MuxAlarm<'static, sam4l::ast::Ast>, ) { let create_cap = create_capability!(NetworkCapabilityCreationCapability); @@ -81,7 +91,7 @@ pub unsafe fn run( IpVisibilityCapability::new(&create_cap) ); let radio_mac = static_init!( - capsules_extra::ieee802154::virtual_mac::MacUser<'static>, + capsules_extra::ieee802154::virtual_mac::MacUser<'static, Ieee802154MacDevice>, capsules_extra::ieee802154::virtual_mac::MacUser::new(mux_mac) ); mux_mac.add_user(radio_mac); diff --git a/boards/imix/src/test/ipv6_lowpan_test.rs b/boards/imix/src/test/ipv6_lowpan_test.rs index 444401221f..eb1d252802 100644 --- a/boards/imix/src/test/ipv6_lowpan_test.rs +++ b/boards/imix/src/test/ipv6_lowpan_test.rs @@ -119,6 +119,16 @@ static mut UDP_DGRAM: [u8; PAYLOAD_LEN - UDP_HDR_SIZE] = [0; PAYLOAD_LEN - UDP_H static mut IP6_DG_OPT: Option = None; //END changes +type Rf233 = capsules_extra::rf233::RF233< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + sam4l::spi::SpiHw<'static>, + >, +>; +type Ieee802154MacDevice = + components::ieee802154::Ieee802154ComponentMacDeviceType>; + pub struct LowpanTest<'a, A: time::Alarm<'a>> { alarm: &'a A, sixlowpan_tx: TxState<'a>, @@ -127,14 +137,14 @@ pub struct LowpanTest<'a, A: time::Alarm<'a>> { } pub unsafe fn initialize_all( - mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static>, + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, Ieee802154MacDevice>, mux_alarm: &'static MuxAlarm<'static, sam4l::ast::Ast>, ) -> &'static LowpanTest< 'static, capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, > { let radio_mac = static_init!( - capsules_extra::ieee802154::virtual_mac::MacUser<'static>, + capsules_extra::ieee802154::virtual_mac::MacUser<'static, Ieee802154MacDevice>, capsules_extra::ieee802154::virtual_mac::MacUser::new(mux_mac) ); mux_mac.add_user(radio_mac); diff --git a/boards/makepython-nrf52840/Cargo.toml b/boards/makepython-nrf52840/Cargo.toml new file mode 100644 index 0000000000..bc820b59b6 --- /dev/null +++ b/boards/makepython-nrf52840/Cargo.toml @@ -0,0 +1,21 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "makepython-nrf52840" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true + +[dependencies] +cortexm4 = { path = "../../arch/cortex-m4" } +kernel = { path = "../../kernel" } +nrf52 = { path = "../../chips/nrf52" } +nrf52840 = { path = "../../chips/nrf52840" } +components = { path = "../components" } +nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } diff --git a/boards/makepython-nrf52840/Makefile b/boards/makepython-nrf52840/Makefile new file mode 100644 index 0000000000..7f1258392c --- /dev/null +++ b/boards/makepython-nrf52840/Makefile @@ -0,0 +1,30 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the Arduino Nano 33 BLE board. + +TOCK_ARCH=cortex-m4 +TARGET=thumbv7em-none-eabi +PLATFORM=makepython-nrf52840 + +include ../Makefile.common + +ifdef PORT + FLAGS += --port $(PORT) +endif + +# Default target for installing the kernel. +.PHONY: install +install: program + +# Upload the kernel using tockloader and the tock bootloader +.PHONY: program +program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + tockloader $(FLAGS) flash --address 0x10000 $< + +.PHONY: flash-bootloader +flash-bootloader: + curl -L --output /tmp/makepython-nrf52840-bootloader_v1.1.3.bin https://github.com/tock/tock-bootloader/releases/download/v1.1.3/makepython-nrf52840-bootloader_v1.1.3.bin + tockloader flash --address 0 /tmp/makepython-nrf52840-bootloader_v1.1.3.bin + rm /tmp/makepython-nrf52840-bootloader_v1.1.3.bin diff --git a/boards/makepython-nrf52840/README.md b/boards/makepython-nrf52840/README.md new file mode 100644 index 0000000000..d38b2f8746 --- /dev/null +++ b/boards/makepython-nrf52840/README.md @@ -0,0 +1,52 @@ +MakePython nRF52840 +=================== + + + +The [MakePython nRF52840](https://www.makerfabs.com/makepython-nrf52840.html) is +a development board with the Nordic nRF52840 SoC and a 128 x 64 pixel OLED +display. + + +## Getting Started + +First, follow the [Tock Getting Started guide](../../doc/Getting_Started.md). + +The MakePython nRF52840 is designed to be programmed using an external JLink +programmer. We would like to avoid this requirement, so we use the [Tock +Bootloader](https://github.com/tock/tock-bootloader) which allows us to program +the board over the UART connection. However, we still require the programmer one +time to flash the bootloader. + +To flash the bootloader we must connect a JLink programmer. The easiest way is +to use an nRF52840dk board. + +### Connect the nRF52840dk to the MakePython-nRF52840 + +First we jumper the board as shown with the following pin mappings + +| nRF52840dk | MakePython-nRF52840 | +|------------|---------------------| +| GND | GND | +| SWD SEL | +3V3 | +| SWD CLK | SWDCLK | +| SWD IO | SWDDIO | + +Make sure _both_ the nRF52840dk board and the MakePython-nRF52840 board are +attached to your computer via two USB connections. + +Then: + +``` +make flash-bootloader +``` + +This will use JLinkExe to flash the bootloader using the nRF52840dk's onboard +jtag hardware. + +### Using the Bootloader + +The bootloader activates when the reset button is pressed twice in quick +succession. The green LED will stay on when the bootloader is active. + +Once the bootloader is installed tockloader will work as expected. diff --git a/boards/makepython-nrf52840/layout.ld b/boards/makepython-nrf52840/layout.ld new file mode 100644 index 0000000000..d5950e39c4 --- /dev/null +++ b/boards/makepython-nrf52840/layout.ld @@ -0,0 +1,14 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x00010000, LENGTH = 256K + prog (rx) : ORIGIN = 0x00050000, LENGTH = 704K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +PAGE_SIZE = 4K; + +INCLUDE ../kernel_layout.ld diff --git a/boards/makepython-nrf52840/src/io.rs b/boards/makepython-nrf52840/src/io.rs new file mode 100644 index 0000000000..fefaab2a82 --- /dev/null +++ b/boards/makepython-nrf52840/src/io.rs @@ -0,0 +1,142 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; + +use cortexm4; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart::{self}; +use kernel::ErrorCode; +use nrf52840::gpio::Pin; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; +use kernel::hil::uart::Transmit; +use kernel::utilities::cells::VolatileCell; + +struct Writer { + initialized: bool, +} + +static mut WRITER: Writer = Writer { initialized: false }; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +const BUF_LEN: usize = 512; +static mut STATIC_PANIC_BUF: [u8; BUF_LEN] = [0; BUF_LEN]; + +static mut DUMMY: DummyUsbClient = DummyUsbClient { + fired: VolatileCell::new(false), +}; + +struct DummyUsbClient { + fired: VolatileCell, +} + +impl uart::TransmitClient for DummyUsbClient { + fn transmitted_buffer(&self, _: &'static mut [u8], _: usize, _: Result<(), ErrorCode>) { + self.fired.set(true); + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + if !self.initialized { + self.initialized = true; + } + // Here we mimic a synchronous UART output by calling transmit_buffer + // on the CDC stack and then spinning on USB interrupts until the transaction + // is complete. If the USB or CDC stack panicked, this may fail. It will also + // fail if the panic occurred prior to the USB connection being initialized. + // In the latter case, the LEDs should still blink in the panic pattern. + + // spin so that if any USB DMA is ongoing it will finish + // we should only need this on the first call to write() + let mut i = 0; + loop { + i += 1; + cortexm4::support::nop(); + if i > 10000 { + break; + } + } + + // copy_from_slice() requires equal length slices + // This will truncate any writes longer than BUF_LEN, but simplifies the + // code. In practice, BUF_LEN=512 always seems sufficient for the size of + // individual calls to write made by the panic handler. + let mut max = BUF_LEN; + if buf.len() < BUF_LEN { + max = buf.len(); + } + + unsafe { + // If CDC_REF_FOR_PANIC is not yet set we panicked very early, + // and not much we can do. Don't want to double fault, + // so just return. + super::CDC_REF_FOR_PANIC.map(|cdc| { + // Lots of unsafe dereferencing of global static mut objects here. + // However, this should be okay, because it all happens within + // a single thread, and: + // - This is the only place the global CDC_REF_FOR_PANIC is used, the logic is the same + // as applies for the global CHIP variable used in the panic handler. + // - We do create multiple mutable references to the STATIC_PANIC_BUF, but we never + // access the STATIC_PANIC_BUF after a slice of it is passed to transmit_buffer + // until the slice has been returned in the uart callback. + // - Similarly, only this function uses the global DUMMY variable, and we do not + // mutate it. + let usb = &mut cdc.controller(); + STATIC_PANIC_BUF[..max].copy_from_slice(&buf[..max]); + let static_buf = &mut STATIC_PANIC_BUF; + cdc.set_transmit_client(&DUMMY); + let _ = cdc.transmit_buffer(static_buf, max); + loop { + if let Some(interrupt) = cortexm4::nvic::next_pending() { + if interrupt == 39 { + usb.handle_interrupt(); + } + let n = cortexm4::nvic::Nvic::new(interrupt); + n.clear_pending(); + n.enable(); + } + if DUMMY.fired.get() { + // buffer finished transmitting, return so we can output additional + // messages when requested by the panic handler. + break; + } + } + DUMMY.fired.set(false); + }); + } + buf.len() + } +} + +/// We just use the standard default provided by the debug module in the kernel. +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P1_10); + let led = &mut led::LedLow::new(led_kernel_pin); + let writer = &mut WRITER; + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &PROCESSES, + &CHIP, + &PROCESS_PRINTER, + ) +} diff --git a/boards/makepython-nrf52840/src/main.rs b/boards/makepython-nrf52840/src/main.rs new file mode 100644 index 0000000000..b8fd94744b --- /dev/null +++ b/boards/makepython-nrf52840/src/main.rs @@ -0,0 +1,751 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the MakePython nRF52840. +//! +//! It is based on nRF52840 SoC. + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::led::LedLow; +use kernel::hil::time::Counter; +use kernel::hil::usb::Client; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +#[allow(unused_imports)] +use kernel::{create_capability, debug, debug_gpio, debug_verbose, static_init}; + +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; + +// The datasheet and website and everything say this is connected to P1.10, but +// actually looking at the hardware files (and what actually works) is that the +// LED is connected to P1.11 (as of a board I received in September 2023). +// +// https://github.com/Makerfabs/NRF52840/issues/1 +const LED_PIN: Pin = Pin::P1_11; + +const BUTTON_RST_PIN: Pin = Pin::P0_18; +const BUTTON_PIN: Pin = Pin::P1_15; + +const GPIO_D0: Pin = Pin::P0_23; +const GPIO_D1: Pin = Pin::P0_12; +const GPIO_D2: Pin = Pin::P0_09; +const GPIO_D3: Pin = Pin::P0_07; + +const _UART_TX_PIN: Pin = Pin::P0_06; +const _UART_RX_PIN: Pin = Pin::P0_08; + +/// I2C pins for all of the sensors. +const I2C_SDA_PIN: Pin = Pin::P0_26; +const I2C_SCL_PIN: Pin = Pin::P0_27; + +// Constants related to the configuration of the 15.4 network stack +/// Personal Area Network ID for the IEEE 802.15.4 radio +const PAN_ID: u16 = 0xABCD; +/// Gateway (or next hop) MAC Address +const DST_MAC_ADDR: capsules_extra::net::ieee802154::MacAddress = + capsules_extra::net::ieee802154::MacAddress::Short(49138); +const DEFAULT_CTX_PREFIX_LEN: u8 = 8; //Length of context for 6LoWPAN compression +const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; //Context for 6LoWPAN Compression + +/// UART Writer for panic!()s. +pub mod io; + +// How should the kernel respond when a process faults. For this board we choose +// to stop the app and print a notice, but not immediately panic. This allows +// users to debug their apps, but avoids issues with using the USB/CDC stack +// synchronously for panic! too early after the board boots. +const FAULT_RESPONSE: kernel::process::StopWithDebugFaultPolicy = + kernel::process::StopWithDebugFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +// State for loading and holding applications. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut CDC_REF_FOR_PANIC: Option< + &'static capsules_extra::usb::cdc::CdcAcm< + 'static, + nrf52::usbd::Usbd, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, + >, +> = None; +static mut NRF52_POWER: Option<&'static nrf52840::power::Power> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +// Function for the CDC/USB stack to use to enter the bootloader. +fn baud_rate_reset_bootloader_enter() { + unsafe { + // 0x90 is the magic value the bootloader expects + NRF52_POWER.unwrap().set_gpregret(0x90); + cortexm4::scb::reset(); + } +} + +fn crc(s: &'static str) -> u32 { + kernel::utilities::helpers::crc32_posix(s.as_bytes()) +} + +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +type AlarmDriver = components::alarm::AlarmDriverComponentType>; + +type Screen = components::ssd1306::Ssd1306ComponentType>; +type ScreenDriver = components::screen::ScreenSharedComponentType; + +type Checker = kernel::process_checker::basic::AppCheckerNames<'static, fn(&'static str) -> u32>; + +type Ieee802154MacDevice = components::ieee802154::Ieee802154ComponentMacDeviceType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + +/// Supported drivers by the platform +pub struct Platform { + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + nrf52::ble_radio::Radio<'static>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + >, + ieee802154_radio: &'static Ieee802154Driver, + console: &'static capsules_core::console::Console<'static>, + pconsole: &'static capsules_core::process_console::ProcessConsole< + 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + components::process_console::Capability, + >, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< + 'static, + LedLow<'static, nrf52::gpio::GPIOPin<'static>>, + 1, + >, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + rng: &'static capsules_core::rng::RngDriver<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + alarm: &'static AlarmDriver, + button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, + screen: &'static ScreenDriver, + udp_driver: &'static capsules_extra::net::udp::UDPDriver<'static>, + scheduler: &'static RoundRobinSched<'static>, + checker: &'static Checker, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::screen::DRIVER_NUM => f(Some(self.screen)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + _ => f(None), + } + } +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type CredentialsCheckingPolicy = Checker; + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn credentials_checking_policy(&self) -> &'static Self::CredentialsCheckingPolicy { + self.checker + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + // set up circular peripheral dependencies + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Save a reference to the power module for resetting the board into the + // bootloader. + NRF52_POWER = Some(&base_peripherals.pwr_clk); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + + // Do nRF configuration and setup. This is shared code with other nRF-based + // platforms. + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + + //-------------------------------------------------------------------------- + // DEBUG GPIO + //-------------------------------------------------------------------------- + + // Configure kernel debug GPIOs as early as possible. These are used by the + // `debug_gpio!(0, toggle)` macro. We configure these early so that the + // macro is available during most of the setup code and kernel execution. + kernel::debug::assign_gpios(Some(&nrf52840_peripherals.gpio_port[LED_PIN]), None, None); + + //-------------------------------------------------------------------------- + // GPIO + //-------------------------------------------------------------------------- + + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + 0 => &nrf52840_peripherals.gpio_port[GPIO_D0], + 1 => &nrf52840_peripherals.gpio_port[GPIO_D1], + 2 => &nrf52840_peripherals.gpio_port[GPIO_D2], + 3 => &nrf52840_peripherals.gpio_port[GPIO_D3], + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, nrf52840::gpio::GPIOPin>, + LedLow::new(&nrf52840_peripherals.gpio_port[LED_PIN]), + )); + + //-------------------------------------------------------------------------- + // BUTTONS + //-------------------------------------------------------------------------- + + let button = components::button::ButtonComponent::new( + board_kernel, + capsules_core::button::DRIVER_NUM, + components::button_component_helper!( + nrf52840::gpio::GPIOPin, + ( + &nrf52840_peripherals.gpio_port[BUTTON_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ) + ), + ) + .finalize(components::button_component_static!( + nrf52840::gpio::GPIOPin + )); + + //-------------------------------------------------------------------------- + // ALARM & TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + // Setup the CDC-ACM over USB driver that we will use for UART. + // We use the Arduino Vendor ID and Product ID since the device is the same. + + // Create the strings we include in the USB descriptor. We use the hardcoded + // DEVICEADDR register on the nRF52 to set the serial number. + let serial_number_buf = static_init!([u8; 17], [0; 17]); + let serial_number_string: &'static str = + nrf52::ficr::FICR_INSTANCE.address_str(serial_number_buf); + let strings = static_init!( + [&str; 3], + [ + "MakePython", // Manufacturer + "NRF52840 - TockOS", // Product + serial_number_string, // Serial number + ] + ); + + let cdc = components::cdc::CdcAcmComponent::new( + &nrf52840_peripherals.usbd, + capsules_extra::usb::cdc::MAX_CTRL_PACKET_SIZE_NRF52840, + 0x2341, + 0x005a, + strings, + mux_alarm, + Some(&baud_rate_reset_bootloader_enter), + ) + .finalize(components::cdc_acm_component_static!( + nrf52::usbd::Usbd, + nrf52::rtc::Rtc + )); + CDC_REF_FOR_PANIC = Some(cdc); //for use by panic handler + + // Process Printer for displaying process information. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); + + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + nrf52::rtc::Rtc<'static> + )); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // RANDOM NUMBERS + //-------------------------------------------------------------------------- + + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!()); + + //-------------------------------------------------------------------------- + // ADC + //-------------------------------------------------------------------------- + base_peripherals.adc.calibrate(); + + let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc) + .finalize(components::adc_mux_component_static!(nrf52840::adc::Adc)); + + let adc_syscall = + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) + .finalize(components::adc_syscall_component_helper!( + // A0 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput2) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A1 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput3) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A2 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput6) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A3 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput5) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A4 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput7) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A5 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput0) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A6 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput4) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A7 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput1) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + )); + + //-------------------------------------------------------------------------- + // SCREEN + //-------------------------------------------------------------------------- + + let i2c_bus = components::i2c::I2CMuxComponent::new(&base_peripherals.twi0, None) + .finalize(components::i2c_mux_component_static!(nrf52840::i2c::TWI)); + base_peripherals.twi0.configure( + nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), + ); + + // I2C address is b011110X, and on this board D/C̅ is GND. + let ssd1306_i2c = components::i2c::I2CComponent::new(i2c_bus, 0x3c) + .finalize(components::i2c_component_static!(nrf52840::i2c::TWI)); + + // Create the ssd1306 object for the actual screen driver. + let ssd1306 = components::ssd1306::Ssd1306Component::new(ssd1306_i2c, true) + .finalize(components::ssd1306_component_static!(nrf52840::i2c::TWI)); + + // Create a Driver for userspace access to the screen. + // let screen = components::screen::ScreenComponent::new( + // board_kernel, + // capsules_extra::screen::DRIVER_NUM, + // ssd1306, + // Some(ssd1306), + // ) + // .finalize(components::screen_component_static!(1032)); + + let apps_regions = static_init!( + [capsules_extra::screen_shared::AppScreenRegion; 3], + [ + capsules_extra::screen_shared::AppScreenRegion::new( + kernel::process::ShortID::Fixed(core::num::NonZeroU32::new(crc("circle")).unwrap()), + 0, // x + 0, // y + 8 * 8, // width + 8 * 8 // height + ), + capsules_extra::screen_shared::AppScreenRegion::new( + kernel::process::ShortID::Fixed(core::num::NonZeroU32::new(crc("count")).unwrap()), + 8 * 8, // x + 0, // y + 8 * 8, // width + 4 * 8 // height + ), + capsules_extra::screen_shared::AppScreenRegion::new( + kernel::process::ShortID::Fixed( + core::num::NonZeroU32::new(crc("tock-scroll")).unwrap() + ), + 8 * 8, // x + 4 * 8, // y + 8 * 8, // width + 4 * 8 // height + ) + ] + ); + + let screen = components::screen::ScreenSharedComponent::new( + board_kernel, + capsules_extra::screen::DRIVER_NUM, + ssd1306, + apps_regions, + ) + .finalize(components::screen_shared_component_static!(1032, Screen)); + + //-------------------------------------------------------------------------- + // WIRELESS + //-------------------------------------------------------------------------- + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &base_peripherals.ble_radio, + mux_alarm, + ) + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); + + use capsules_extra::net::ieee802154::MacAddress; + + let aes_mux = components::ieee802154::MuxAes128ccmComponent::new(&base_peripherals.ecb) + .finalize(components::mux_aes128ccm_component_static!( + nrf52840::aes::AesECB + )); + + let device_id = nrf52840::ficr::FICR_INSTANCE.id(); + let device_id_bottom_16 = u16::from_le_bytes([device_id[0], device_id[1]]); + let (ieee802154_radio, mux_mac) = components::ieee802154::Ieee802154Component::new( + board_kernel, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, + aes_mux, + PAN_ID, + device_id_bottom_16, + device_id, + ) + .finalize(components::ieee802154_component_static!( + nrf52840::ieee802154_radio::Radio, + nrf52840::aes::AesECB<'static> + )); + use capsules_extra::net::ipv6::ip_utils::IPAddr; + + let local_ip_ifaces = static_init!( + [IPAddr; 3], + [ + IPAddr([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ]), + IPAddr([ + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]), + IPAddr::generate_from_mac(capsules_extra::net::ieee802154::MacAddress::Short( + device_id_bottom_16 + )), + ] + ); + + let (udp_send_mux, udp_recv_mux, udp_port_table) = components::udp_mux::UDPMuxComponent::new( + mux_mac, + DEFAULT_CTX_PREFIX_LEN, + DEFAULT_CTX_PREFIX, + DST_MAC_ADDR, + MacAddress::Short(device_id_bottom_16), + local_ip_ifaces, + mux_alarm, + ) + .finalize(components::udp_mux_component_static!( + nrf52840::rtc::Rtc, + Ieee802154MacDevice + )); + + // UDP driver initialization happens here + let udp_driver = components::udp_driver::UDPDriverComponent::new( + board_kernel, + capsules_extra::net::udp::DRIVER_NUM, + udp_send_mux, + udp_recv_mux, + udp_port_table, + local_ip_ifaces, + ) + .finalize(components::udp_driver_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // APP ID CHECKING + //-------------------------------------------------------------------------- + + let checker = static_init!( + kernel::process_checker::basic::AppCheckerNames u32>, + kernel::process_checker::basic::AppCheckerNames::new(&(crc as fn(&'static str) -> u32)) + ); + kernel::deferred_call::DeferredCallClient::register(checker); + + //-------------------------------------------------------------------------- + // FINAL SETUP AND BOARD BOOT + //-------------------------------------------------------------------------- + + // Start all of the clocks. Low power operation will require a better + // approach than this. + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + ble_radio, + ieee802154_radio, + console, + pconsole, + adc: adc_syscall, + led, + button, + gpio, + rng, + screen, + alarm, + udp_driver, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + scheduler, + checker, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + // Configure the USB stack to enable a serial port over CDC-ACM. + cdc.enable(); + cdc.attach(); + + //-------------------------------------------------------------------------- + // TESTS + //-------------------------------------------------------------------------- + // test::linear_log_test::run( + // mux_alarm, + // &nrf52840_peripherals.nrf52.nvmc, + // ); + // test::log_test::run( + // mux_alarm, + // &nrf52840_peripherals.nrf52.nvmc, + // ); + + debug!("Initialization complete. Entering main loop."); + let _ = platform.pconsole.start(); + + ssd1306.init_screen(); + + //-------------------------------------------------------------------------- + // PROCESSES AND MAIN LOOP + //-------------------------------------------------------------------------- + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + kernel::process::load_and_check_processes( + board_kernel, + &platform, + chip, + core::slice::from_raw_parts( + &_sapps as *const u8, + &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + ), + core::slice::from_raw_parts_mut( + &mut _sappmem as *mut u8, + &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + ), + &mut PROCESSES, + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/boards/nano33ble/src/main.rs b/boards/nano33ble/src/main.rs index 8be06f4cbf..c301cbf784 100644 --- a/boards/nano33ble/src/main.rs +++ b/boards/nano33ble/src/main.rs @@ -119,6 +119,14 @@ type HTS221Sensor = components::hts221::Hts221ComponentType< >; type TemperatureDriver = components::temperature::TemperatureComponentType; type HumidityDriver = components::humidity::HumidityComponentType; +type Ieee802154MacDevice = components::ieee802154::Ieee802154ComponentMacDeviceType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; /// Supported drivers by the platform pub struct Platform { @@ -130,7 +138,7 @@ pub struct Platform { nrf52::rtc::Rtc<'static>, >, >, - ieee802154_radio: &'static capsules_extra::ieee802154::RadioDriver<'static>, + ieee802154_radio: &'static Ieee802154Driver, console: &'static capsules_core::console::Console<'static>, pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, @@ -577,7 +585,10 @@ pub unsafe fn start() -> ( local_ip_ifaces, mux_alarm, ) - .finalize(components::udp_mux_component_static!(nrf52840::rtc::Rtc)); + .finalize(components::udp_mux_component_static!( + nrf52840::rtc::Rtc, + Ieee802154MacDevice + )); // UDP driver initialization happens here let udp_driver = components::udp_driver::UDPDriverComponent::new( diff --git a/boards/nano_rp2040_connect/src/main.rs b/boards/nano_rp2040_connect/src/main.rs index 484836dccc..573b58ce4e 100644 --- a/boards/nano_rp2040_connect/src/main.rs +++ b/boards/nano_rp2040_connect/src/main.rs @@ -12,8 +12,6 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use core::arch::global_asm; - use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use components::led::LedsComponent; @@ -166,7 +164,8 @@ extern "C" { fn jump_to_bootloader(); } -global_asm!( +#[cfg(all(target_arch = "arm", target_os = "none"))] +core::arch::global_asm!( " .section .jump_to_bootloader, \"ax\" .global jump_to_bootloader diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs index 6ae5a9cb24..609f3131b2 100644 --- a/boards/nordic/nrf52840_dongle/src/main.rs +++ b/boards/nordic/nrf52840_dongle/src/main.rs @@ -80,6 +80,11 @@ pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; type TemperatureDriver = components::temperature::TemperatureComponentType>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + /// Supported drivers by the platform pub struct Platform { ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< @@ -87,7 +92,7 @@ pub struct Platform { nrf52840::ble_radio::Radio<'static>, VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, - ieee802154_radio: &'static capsules_extra::ieee802154::RadioDriver<'static>, + ieee802154_radio: &'static Ieee802154Driver, button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs index 494aa7b33e..787ae15076 100644 --- a/boards/nordic/nrf52840dk/src/main.rs +++ b/boards/nordic/nrf52840dk/src/main.rs @@ -185,6 +185,15 @@ type KVDriver = components::kv::KVDriverComponentType; type TemperatureDriver = components::temperature::TemperatureComponentType>; +type Ieee802154MacDevice = components::ieee802154::Ieee802154ComponentMacDeviceType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + /// Supported drivers by the platform pub struct Platform { ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< @@ -192,7 +201,7 @@ pub struct Platform { nrf52840::ble_radio::Radio<'static>, VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, - ieee802154_radio: &'static capsules_extra::ieee802154::RadioDriver<'static>, + ieee802154_radio: &'static Ieee802154Driver, button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, @@ -598,7 +607,10 @@ pub unsafe fn start() -> ( local_ip_ifaces, mux_alarm, ) - .finalize(components::udp_mux_component_static!(nrf52840::rtc::Rtc)); + .finalize(components::udp_mux_component_static!( + nrf52840::rtc::Rtc, + Ieee802154MacDevice + )); // UDP driver initialization happens here let udp_driver = components::udp_driver::UDPDriverComponent::new( diff --git a/boards/nordic/nrf52840dk/src/test/aes_test.rs b/boards/nordic/nrf52840dk/src/test/aes_test.rs index bcb03bde9a..4a7b215f4f 100644 --- a/boards/nordic/nrf52840dk/src/test/aes_test.rs +++ b/boards/nordic/nrf52840dk/src/test/aes_test.rs @@ -63,7 +63,7 @@ unsafe fn static_init_test_ctr( static_init!( TestAes128Ctr<'static, AesECB>, - TestAes128Ctr::new(aes, key, iv, source, data) + TestAes128Ctr::new(aes, key, iv, source, data, true) ) } @@ -77,7 +77,7 @@ unsafe fn static_init_test_cbc( static_init!( TestAes128Cbc<'static, AesECB>, - TestAes128Cbc::new(aes, key, iv, source, data) + TestAes128Cbc::new(aes, key, iv, source, data, false) ) } @@ -90,6 +90,6 @@ unsafe fn static_init_test_ecb( static_init!( TestAes128Ecb<'static, AesECB>, - TestAes128Ecb::new(aes, key, source, data) + TestAes128Ecb::new(aes, key, source, data, false) ) } diff --git a/boards/nordic/nrf52840dk/src/test/hmac_sha256_test.rs b/boards/nordic/nrf52840dk/src/test/hmac_sha256_test.rs index 7a05110256..308f1b0b01 100644 --- a/boards/nordic/nrf52840dk/src/test/hmac_sha256_test.rs +++ b/boards/nordic/nrf52840dk/src/test/hmac_sha256_test.rs @@ -13,7 +13,6 @@ use capsules_extra::hmac_sha256::HmacSha256Software; use capsules_extra::sha256::Sha256Software; use capsules_extra::test::hmac_sha256::TestHmacSha256; use kernel::deferred_call::DeferredCallClient; -use kernel::hil::digest::Digest; use kernel::static_init; pub unsafe fn run_hmacsha256() { @@ -43,7 +42,7 @@ unsafe fn static_init_test_hmacsha256() -> &'static TestHmacSha256 { HmacSha256Software<'static, Sha256Software<'static>>, HmacSha256Software::new(sha256, sha256_hash_buf, hmacsha256_verify_buf) ); - sha256.set_client(hmacsha256); + kernel::hil::digest::Digest::set_client(sha256, hmacsha256); let test = static_init!( TestHmacSha256, diff --git a/boards/nordic/nrf52dk/src/tests/aes.rs b/boards/nordic/nrf52dk/src/tests/aes.rs index 2b7ee2bfb7..3a1b7b78d1 100644 --- a/boards/nordic/nrf52dk/src/tests/aes.rs +++ b/boards/nordic/nrf52dk/src/tests/aes.rs @@ -30,6 +30,6 @@ unsafe fn static_init_test( static_init!( TestAes128Ctr<'static, AesECB>, - TestAes128Ctr::new(aesecb, key, iv, source, data) + TestAes128Ctr::new(aesecb, key, iv, source, data, true) ) } diff --git a/boards/nucleo_f429zi/src/main.rs b/boards/nucleo_f429zi/src/main.rs index a22e9c1958..299983c010 100644 --- a/boards/nucleo_f429zi/src/main.rs +++ b/boards/nucleo_f429zi/src/main.rs @@ -306,17 +306,17 @@ unsafe fn setup_peripherals( rtc.enable_clock(); } -/// Statically initialize the core peripherals for the chip. -/// /// This is in a separate, inline(never) function so that its stack frame is /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn create_peripherals() -> ( - &'static mut Stm32f429ziDefaultPeripherals<'static>, - &'static stm32f429zi::syscfg::Syscfg<'static>, - &'static stm32f429zi::dma::Dma1<'static>, +unsafe fn start() -> ( + &'static kernel::Kernel, + NucleoF429ZI, + &'static stm32f429zi::chip::Stm32f4xx<'static, Stm32f429ziDefaultPeripherals<'static>>, ) { + stm32f429zi::init(); + // We use the default HSI 16Mhz clock let rcc = static_init!(stm32f429zi::rcc::Rcc, stm32f429zi::rcc::Rcc::new()); @@ -335,17 +335,6 @@ unsafe fn create_peripherals() -> ( Stm32f429ziDefaultPeripherals, Stm32f429ziDefaultPeripherals::new(rcc, exti, dma1, dma2) ); - (peripherals, syscfg, dma1) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - stm32f429zi::init(); - - let (peripherals, syscfg, dma1) = create_peripherals(); peripherals.init(); let base_peripherals = &peripherals.stm32f4; @@ -384,7 +373,6 @@ pub unsafe fn main() { // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); @@ -718,10 +706,14 @@ pub unsafe fn main() { .finalize(components::multi_alarm_test_component_buf!(stm32f429zi::tim2::Tim2)) .run();*/ - board_kernel.kernel_loop( - &nucleo_f429zi, - chip, - Some(&nucleo_f429zi.ipc), - &main_loop_capability, - ); + (board_kernel, nucleo_f429zi, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/nucleo_f446re/src/main.rs b/boards/nucleo_f446re/src/main.rs index 58703c2c76..2f636e8ea7 100644 --- a/boards/nucleo_f446re/src/main.rs +++ b/boards/nucleo_f446re/src/main.rs @@ -257,17 +257,17 @@ unsafe fn setup_peripherals(tim2: &stm32f446re::tim2::Tim2) { cortexm4::nvic::Nvic::new(stm32f446re::nvic::TIM2).enable(); } -/// Statically initialize the core peripherals for the chip. -/// /// This is in a separate, inline(never) function so that its stack frame is /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn create_peripherals() -> ( - &'static mut Stm32f446reDefaultPeripherals<'static>, - &'static stm32f446re::syscfg::Syscfg<'static>, - &'static stm32f446re::dma::Dma1<'static>, +unsafe fn start() -> ( + &'static kernel::Kernel, + NucleoF446RE, + &'static stm32f446re::chip::Stm32f4xx<'static, Stm32f446reDefaultPeripherals<'static>>, ) { + stm32f446re::init(); + // We use the default HSI 16Mhz clock let rcc = static_init!(stm32f446re::rcc::Rcc, stm32f446re::rcc::Rcc::new()); let syscfg = static_init!( @@ -285,17 +285,6 @@ unsafe fn create_peripherals() -> ( Stm32f446reDefaultPeripherals, Stm32f446reDefaultPeripherals::new(rcc, exti, dma1, dma2) ); - (peripherals, syscfg, dma1) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - stm32f446re::init(); - - let (peripherals, syscfg, dma1) = create_peripherals(); peripherals.init(); let base_peripherals = &peripherals.stm32f4; @@ -331,7 +320,6 @@ pub unsafe fn main() { // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); @@ -559,10 +547,15 @@ pub unsafe fn main() { /*components::test::multi_alarm_test::MultiAlarmTestComponent::new(mux_alarm) .finalize(components::multi_alarm_test_component_buf!(stm32f446re::tim2::Tim2)) .run();*/ - board_kernel.kernel_loop( - &nucleo_f446re, - chip, - Some(&nucleo_f446re.ipc), - &main_loop_capability, - ); + + (board_kernel, nucleo_f446re, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/opentitan/src/tests/aes_test.rs b/boards/opentitan/src/tests/aes_test.rs index 036242c96a..5f3d80b62a 100644 --- a/boards/opentitan/src/tests/aes_test.rs +++ b/boards/opentitan/src/tests/aes_test.rs @@ -126,7 +126,7 @@ unsafe fn static_init_test_ecb(aes: &'static Aes) -> &'static TestAes128Ecb<'sta static_init!( TestAes128Ecb<'static, Aes>, - TestAes128Ecb::new(aes, key, source, data) + TestAes128Ecb::new(aes, key, source, data, true) ) } @@ -160,7 +160,7 @@ unsafe fn static_init_test_cbc(aes: &'static Aes) -> &'static TestAes128Cbc<'sta static_init!( TestAes128Cbc<'static, Aes>, - TestAes128Cbc::new(aes, key, iv, source, data) + TestAes128Cbc::new(aes, key, iv, source, data, true) ) } @@ -194,6 +194,6 @@ unsafe fn static_init_test_ctr(aes: &'static Aes) -> &'static TestAes128Ctr<'sta static_init!( TestAes128Ctr<'static, Aes>, - TestAes128Ctr::new(aes, key, iv, source, data) + TestAes128Ctr::new(aes, key, iv, source, data, true) ) } diff --git a/boards/particle_boron/src/main.rs b/boards/particle_boron/src/main.rs index 0f0075e55e..5ec3caf873 100644 --- a/boards/particle_boron/src/main.rs +++ b/boards/particle_boron/src/main.rs @@ -91,6 +91,11 @@ pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; type TemperatureDriver = components::temperature::TemperatureComponentType>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + /// Supported drivers by the platform pub struct Platform { ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< @@ -98,7 +103,7 @@ pub struct Platform { nrf52840::ble_radio::Radio<'static>, VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, - ieee802154_radio: &'static capsules_extra::ieee802154::RadioDriver<'static>, + ieee802154_radio: &'static Ieee802154Driver, button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, console: &'static capsules_core::console::Console<'static>, gpio: &'static capsules_core::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, diff --git a/boards/pico_explorer_base/src/main.rs b/boards/pico_explorer_base/src/main.rs index 09eb7442ff..319856bc06 100644 --- a/boards/pico_explorer_base/src/main.rs +++ b/boards/pico_explorer_base/src/main.rs @@ -12,8 +12,6 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use core::arch::global_asm; - use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use components::led::LedsComponent; @@ -176,7 +174,8 @@ extern "C" { fn jump_to_bootloader(); } -global_asm!( +#[cfg(all(target_arch = "arm", target_os = "none"))] +core::arch::global_asm!( " .section .jump_to_bootloader, \"ax\" .global jump_to_bootloader diff --git a/boards/raspberry_pi_pico/src/main.rs b/boards/raspberry_pi_pico/src/main.rs index d578cd1253..f80c6b4adc 100644 --- a/boards/raspberry_pi_pico/src/main.rs +++ b/boards/raspberry_pi_pico/src/main.rs @@ -12,8 +12,6 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use core::arch::global_asm; - use capsules_core::i2c_master::I2CMasterDriver; use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::date_time_component_static; @@ -167,7 +165,8 @@ extern "C" { fn jump_to_bootloader(); } -global_asm!( +#[cfg(all(target_arch = "arm", target_os = "none"))] +core::arch::global_asm!( " .section .jump_to_bootloader, \"ax\" .global jump_to_bootloader diff --git a/boards/sma_q3/src/main.rs b/boards/sma_q3/src/main.rs index 90c18d31df..c7d2787358 100644 --- a/boards/sma_q3/src/main.rs +++ b/boards/sma_q3/src/main.rs @@ -79,6 +79,11 @@ type Bmp280Sensor = components::bmp280::Bmp280ComponentType< >; type TemperatureDriver = components::temperature::TemperatureComponentType; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + /// Supported drivers by the platform pub struct Platform { temperature: &'static TemperatureDriver, @@ -87,7 +92,7 @@ pub struct Platform { nrf52840::ble_radio::Radio<'static>, VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, - ieee802154_radio: &'static capsules_extra::ieee802154::RadioDriver<'static>, + ieee802154_radio: &'static Ieee802154Driver, button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, diff --git a/capsules/core/src/process_console.rs b/capsules/core/src/process_console.rs index dac03bf233..26b4a6ad7f 100644 --- a/capsules/core/src/process_console.rs +++ b/capsules/core/src/process_console.rs @@ -344,7 +344,6 @@ struct CommandHistory<'a, const COMMAND_HISTORY_LEN: usize> { cmds: &'a mut [Command; COMMAND_HISTORY_LEN], cmd_idx: usize, cmd_is_modified: bool, - modified_byte: u8, } impl<'a, const COMMAND_HISTORY_LEN: usize> CommandHistory<'a, COMMAND_HISTORY_LEN> { @@ -353,7 +352,6 @@ impl<'a, const COMMAND_HISTORY_LEN: usize> CommandHistory<'a, COMMAND_HISTORY_LE cmds: cmds_buffer, cmd_idx: 0, cmd_is_modified: false, - modified_byte: EOL, } } @@ -369,22 +367,6 @@ impl<'a, const COMMAND_HISTORY_LEN: usize> CommandHistory<'a, COMMAND_HISTORY_LE } } - /// Checks if the command line was modified - /// before pressing Up or Down keys - /// and saves the current modified command - /// into the history - fn change_cmd_from(&mut self, cmd: &[u8]) { - match self.modified_byte { - BS | DEL => { - self.cmds[0].clear(); - self.write_to_first(cmd); - } - _ => { - self.modified_byte = EOL; - } - } - } - fn write_to_first(&mut self, cmd: &[u8]) { let mut cmd_arr = [0; COMMAND_BUF_LEN]; cmd_arr.copy_from_slice(cmd); @@ -1227,8 +1209,6 @@ impl<'a, const COMMAND_HISTORY_LEN: usize, A: Alarm<'a>, C: ProcessManagementCap } else { ht.prev_cmd_idx() } { - ht.change_cmd_from(command); - let next_command_len = ht.cmds[next_index].len; for _ in cursor..index { @@ -1304,7 +1284,15 @@ impl<'a, const COMMAND_HISTORY_LEN: usize, A: Alarm<'a>, C: ProcessManagementCap // not to permit accumulation of the text if COMMAND_HISTORY_LEN > 1 { self.command_history.map(|ht| { - ht.cmds[0].delete_byte(cursor - 1); + if ht.cmd_is_modified { + // Copy the last command into the unfinished command + + ht.cmds[0].clear(); + ht.write_to_first(command); + ht.cmd_is_modified = false; + } else { + ht.cmds[0].delete_byte(cursor); + } }); } } @@ -1366,15 +1354,21 @@ impl<'a, const COMMAND_HISTORY_LEN: usize, A: Alarm<'a>, C: ProcessManagementCap // not to permit accumulation of the text if COMMAND_HISTORY_LEN > 1 { self.command_history.map(|ht| { - ht.cmds[0].delete_byte(cursor - 1); + if ht.cmd_is_modified { + // Copy the last command into the unfinished command + + ht.cmds[0].clear(); + ht.write_to_first(command); + ht.cmd_is_modified = false; + } else { + ht.cmds[0].delete_byte(cursor - 1); + } }); } } - } else if (COMMAND_HISTORY_LEN > 1) && (esc_state.has_started()) { - self.command_history - .map(|ht| ht.modified_byte = previous_byte); } else if index < (command.len() - 1) && read_buf[0] < ASCII_LIMIT + && !esc_state.has_started() && !esc_state.in_progress() { // For some reason, sometimes reads return > 127 but no error, diff --git a/capsules/core/src/test/capsule_test.rs b/capsules/core/src/test/capsule_test.rs new file mode 100644 index 0000000000..25d2ead5de --- /dev/null +++ b/capsules/core/src/test/capsule_test.rs @@ -0,0 +1,70 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Interface for running capsule tests. +//! +//! As Tock capsules are asynchronous, it is difficult for a test runner to +//! determine when a test has finished. This interface provides a `done()` +//! callback used by the test implementation to notify when the test has +//! completed. +//! +//! A simple example of a test capsule using this interface: +//! +//! ```rust +//! pub struct TestSensorX { +//! client: OptionalCell<&'static dyn CapsuleTestClient>, +//! } +//! +//! impl TestSensorX { +//! pub fn new() -> Self { +//! TestHmacSha256 { +//! client: OptionalCell::empty(), +//! } +//! } +//! } +//! +//! impl CapsuleTest for TestSensorX { +//! fn set_client(&self, client: &'static dyn CapsuleTestClient) { +//! self.client.set(client); +//! } +//! } +//! +//! impl AsyncClient for TestSensorX { +//! fn operation_complete(&self) { +//! // Test has finished at this point. +//! self.client.map(|client| { +//! client.done(Ok(())); +//! }); +//! } +//! } +//! ``` + +use kernel::ErrorCode; + +/// Errors for the result of a failed test. +pub enum CapsuleTestError { + /// The test computed some result (e.g., a checksum or hash) and the result + /// is not correct (e.g., it doesn't match the intended value, say the + /// correct checksum or hash). + IncorrectResult, + + /// An error occurred while running the test, and the resulting `ErrorCode` + /// is provided. + ErrorCode(ErrorCode), +} + +/// Client for receiving test done events. +pub trait CapsuleTestClient { + /// Called when the test is finished. If the test was successful, `result` + /// is `Ok(())`. If the test failed, `result` is `Err()` with a suitable + /// error. + fn done(&'static self, result: Result<(), CapsuleTestError>); +} + +/// Identify a test as a capsule test. This is only used for setting the client +/// for test complete callbacks. +pub trait CapsuleTest { + /// Set the client for the done callback. + fn set_client(&self, client: &'static dyn CapsuleTestClient); +} diff --git a/capsules/core/src/test/mod.rs b/capsules/core/src/test/mod.rs index 056325c801..ab9f77be99 100644 --- a/capsules/core/src/test/mod.rs +++ b/capsules/core/src/test/mod.rs @@ -4,6 +4,7 @@ pub mod alarm; pub mod alarm_edge_cases; +pub mod capsule_test; pub mod double_grant_entry; pub mod random_alarm; pub mod random_timer; diff --git a/capsules/extra/src/ieee802154/device.rs b/capsules/extra/src/ieee802154/device.rs index 03ec7dda50..b4cfc3557c 100644 --- a/capsules/extra/src/ieee802154/device.rs +++ b/capsules/extra/src/ieee802154/device.rs @@ -73,6 +73,25 @@ pub trait MacDevice<'a> { security_needed: Option<(SecurityLevel, KeyId)>, ) -> Result; + /// Creates an IEEE 802.15.4 Frame object that is compatible with the + /// MAC transmit and append payload methods. This serves to provide + /// functionality for sending packets fully formed by the userprocess + /// and that the 15.4 capsule does not modify. The len field may be less + /// than the length of the buffer as the len field is the length of + /// the current frame while the buffer is the maximum 15.4 frame size. + /// + /// - `buf`: The buffer to be used for the frame + /// - `len`: The length of the frame + /// + /// Returns a Result: + /// - on success a Frame object. + /// - on failure an error returning the buffer. + fn buf_to_frame( + &self, + buf: &'static mut [u8], + len: usize, + ) -> Result; + /// Transmits a frame that has been prepared by the above process. If the /// transmission process fails, the buffer inside the frame is returned so /// that it can be re-used. diff --git a/capsules/extra/src/ieee802154/driver.rs b/capsules/extra/src/ieee802154/driver.rs index 06e07efe6e..bc72a77645 100644 --- a/capsules/extra/src/ieee802154/driver.rs +++ b/capsules/extra/src/ieee802154/driver.rs @@ -177,14 +177,46 @@ impl KeyDescriptor { } } +/// Denotes the type of pending transmission. A `Parse(..)` PendingTX +/// indicates that the 15.4 framer will need to form the packet header +/// from the provided address and security level. A `Raw` PendingTX +/// is formed by the userprocess and passes through the Framer unchanged. +enum PendingTX { + Parse(u16, Option<(SecurityLevel, KeyId)>), + Raw, + Empty, +} + +impl Default for PendingTX { + /// The default PendingTX is `Empty` + fn default() -> Self { + PendingTX::Empty + } +} + +impl PendingTX { + /// Returns true if the PendingTX state is `Empty` + fn is_empty(&self) -> bool { + match self { + PendingTX::Empty => true, + _ => false, + } + } + + /// Take the pending transmission, replacing it with `Empty` and return the current PendingTx + fn take(&mut self) -> PendingTX { + core::mem::replace(self, PendingTX::Empty) + } +} + #[derive(Default)] pub struct App { - pending_tx: Option<(u16, Option<(SecurityLevel, KeyId)>)>, + pending_tx: PendingTX, } -pub struct RadioDriver<'a> { +pub struct RadioDriver<'a, M: device::MacDevice<'a>> { /// Underlying MAC device, possibly multiplexed - mac: &'a dyn device::MacDevice<'a>, + mac: &'a M, /// List of (short address, long address) pairs representing IEEE 802.15.4 /// neighbors. @@ -227,9 +259,9 @@ pub struct RadioDriver<'a> { backup_device_procedure: OptionalCell<&'a dyn framer::DeviceProcedure>, } -impl<'a> RadioDriver<'a> { +impl<'a, M: device::MacDevice<'a>> RadioDriver<'a, M> { pub fn new( - mac: &'a dyn device::MacDevice<'a>, + mac: &'a M, grant: Grant< App, UpcallCount<{ upcall::COUNT }>, @@ -383,7 +415,7 @@ impl<'a> RadioDriver<'a> { for app in self.apps.iter() { let processid = app.processid(); app.enter(|app, _| { - if app.pending_tx.is_some() { + if !app.pending_tx.is_empty() { pending_app = Some(processid); } }); @@ -413,56 +445,77 @@ impl<'a> RadioDriver<'a> { /// idle and the app has a pending transmission. #[inline] fn perform_tx_sync(&self, processid: ProcessId) -> Result<(), ErrorCode> { - self.apps.enter(processid, |app, kerel_data| { - let (dst_addr, security_needed) = match app.pending_tx.take() { - Some(pending_tx) => pending_tx, - None => { - return Ok(()); - } - }; - let result = self.kernel_tx.take().map_or(Err(ErrorCode::NOMEM), |kbuf| { - // Prepare the frame headers - let pan = self.mac.get_pan(); - let dst_addr = MacAddress::Short(dst_addr); - let src_addr = MacAddress::Short(self.mac.get_address()); - let mut frame = match self.mac.prepare_data_frame( - kbuf, - pan, - dst_addr, - pan, - src_addr, - security_needed, - ) { - Ok(frame) => frame, - Err(kbuf) => { - self.kernel_tx.replace(kbuf); - return Err(ErrorCode::FAIL); + self.apps.enter(processid, |app, kernel_data| { + // The use of this take method is somewhat overkill, but serves to ensure that + // this, or future, implementations do not forget to "remove" the pending_tx + // from the app after processing. + let curr_tx = app.pending_tx.take(); + + // Before beginning the transmission process, confirm that the PendingTX + // is not Empty. In the Empty case, there is nothing to transmit and we + // can return Ok(()) immediately as there is nothing to transmit. + if let PendingTX::Empty = curr_tx { return Ok(()) } + + // At a high level, we must form a Frame from the provided userproceess data, + // place this frame into a static buffer, and then transmit the frame. This is + // somewhat complicated by the need to error handle each of these steps and also + // provide Raw and Parse sending modes (i.e. Raw mode userprocess fully forms + // 15.4 packet and Parse mode the 15.4 framer forms the packet from the userprocess + // parameters and payload). Because we first take this kernel buffer, we must + // replace the `kernel_tx` buffer upon handling any error. + self.kernel_tx.take().map_or(Err(ErrorCode::NOMEM), |kbuf| { + match curr_tx { + PendingTX::Empty => { + unreachable!("PendingTX::Empty should have been handled earlier with guard statement.") + } + PendingTX::Raw => { + // Here we form an empty frame from the buffer to later be filled by the specified + // userprocess frame data. Note, we must allocate the needed buffer for the frame, + // but set the `len` field to 0 as the frame is empty. + self.mac.buf_to_frame(kbuf, 0).map_err(|(err, buf)| { + self.kernel_tx.replace(buf); + err + })}, + PendingTX::Parse(dst_addr, security_needed) => { + // Prepare the frame headers + let pan = self.mac.get_pan(); + let dst_addr = MacAddress::Short(dst_addr); + let src_addr = MacAddress::Short(self.mac.get_address()); + self.mac.prepare_data_frame( + kbuf, + pan, + dst_addr, + pan, + src_addr, + security_needed, + ).map_or_else(|err_buf| { + self.kernel_tx.replace(err_buf); + Err(ErrorCode::FAIL) + }, | frame| + Ok(frame) + ) } - }; - - // Append the payload: there must be one - let result = kerel_data - .get_readonly_processbuffer(ro_allow::WRITE) - .and_then(|write| write.enter(|payload| frame.append_payload_process(payload))) - .unwrap_or(Err(ErrorCode::INVAL)); - if result != Ok(()) { - return result; } + }).map(|mut frame| { - // Finally, transmit the frame - match self.mac.transmit(frame) { - Ok(()) => Ok(()), - Err((ecode, buf)) => { - self.kernel_tx.put(Some(buf)); - Err(ecode) - } + // Obtain the payload from the userprocess, append the "payload" to the previously formed frame + // and pass the frame to be transmitted. Note, the term "payload" is somewhat misleading in the + // case of Raw transmission as the payload is the entire 15.4 frame. + kernel_data + .get_readonly_processbuffer(ro_allow::WRITE) + .and_then(|write| write.enter(|payload| + frame.append_payload_process(payload) + ))?.map( |()| + { + self.mac.transmit(frame).map_or_else(|(errorcode, error_buf)| { + self.kernel_tx.replace(error_buf); + Err(errorcode) + }, |()| {self.current_app.set(processid); Ok(()) } + ) } - }); - if result == Ok(()) { - self.current_app.set(processid); - } - result + )? })? + })? } /// Schedule the next transmission if there is one pending. Performs the @@ -491,7 +544,7 @@ impl<'a> RadioDriver<'a> { } } -impl DeferredCallClient for RadioDriver<'static> { +impl<'a, M: device::MacDevice<'a>> DeferredCallClient for RadioDriver<'a, M> { fn handle_deferred_call(&self) { let _ = self .apps @@ -517,7 +570,7 @@ impl DeferredCallClient for RadioDriver<'static> { } } -impl framer::DeviceProcedure for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> framer::DeviceProcedure for RadioDriver<'a, M> { /// Gets the long address corresponding to the neighbor that matches the given /// MAC address. If no such neighbor exists, returns `None`. fn lookup_addr_long(&self, addr: MacAddress) -> Option<[u8; 8]> { @@ -544,7 +597,7 @@ impl framer::DeviceProcedure for RadioDriver<'_> { } } -impl framer::KeyProcedure for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> framer::KeyProcedure for RadioDriver<'a, M> { /// Gets the key corresponding to the key that matches the given security /// level `level` and key ID `key_id`. If no such key matches, returns /// `None`. @@ -573,7 +626,7 @@ impl framer::KeyProcedure for RadioDriver<'_> { } } -impl SyscallDriver for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> SyscallDriver for RadioDriver<'a, M> { /// IEEE 802.15.4 MAC device control. /// /// For some of the below commands, one 32-bit argument is not enough to @@ -623,6 +676,10 @@ impl SyscallDriver for RadioDriver<'_> { /// 9 bytes: the key ID (might not use all bytes) + /// 16 bytes: the key. /// - `25`: Remove the key at an index. + /// - `26`: Transmit a frame (parse required). Take the provided payload and + /// parameters to encrypt, form headers, and transmit the frame. + /// - `27`: Transmit a frame (raw). Transmit preformed 15.4 frame (i.e. + /// headers and security etc completed by userprocess). fn command( &self, command_number: usize, @@ -862,7 +919,7 @@ impl SyscallDriver for RadioDriver<'_> { 26 => { self.apps .enter(processid, |app, kernel_data| { - if app.pending_tx.is_some() { + if !app.pending_tx.is_empty() { // Cannot support more than one pending tx per process. return Err(ErrorCode::BUSY); } @@ -900,7 +957,13 @@ impl SyscallDriver for RadioDriver<'_> { if next_tx.is_none() { return Err(ErrorCode::INVAL); } - app.pending_tx = next_tx; + + match next_tx { + Some((dst_addr, sec)) => { + app.pending_tx = PendingTX::Parse(dst_addr, sec) + } + None => app.pending_tx = PendingTX::Empty, + } Ok(()) }) .map_or_else( @@ -911,6 +974,21 @@ impl SyscallDriver for RadioDriver<'_> { }, ) } + 27 => { + self.apps + .enter(processid, |app, _| { + if !app.pending_tx.is_empty() { + // Cannot support more than one pending tx per process. + return Err(ErrorCode::BUSY); + } + app.pending_tx = PendingTX::Raw; + Ok(()) + }) + .map_or_else( + |err| CommandReturn::failure(err.into()), + |_| self.do_next_tx_sync(processid).into(), + ) + } _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } @@ -920,7 +998,7 @@ impl SyscallDriver for RadioDriver<'_> { } } -impl device::TxClient for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> device::TxClient for RadioDriver<'a, M> { fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.kernel_tx.replace(spi_buf); self.current_app.take().map(|processid| { @@ -957,7 +1035,7 @@ fn encode_address(addr: &Option) -> usize { ((AddressMode::from(addr) as usize) << 16) | short_addr_only } -impl device::RxClient for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> device::RxClient for RadioDriver<'a, M> { fn receive<'b>(&self, buf: &'b [u8], header: Header<'b>, data_offset: usize, data_len: usize) { self.apps.each(|_, _, kernel_data| { let read_present = kernel_data diff --git a/capsules/extra/src/ieee802154/framer.rs b/capsules/extra/src/ieee802154/framer.rs index a173a3419d..5cf5a4f733 100644 --- a/capsules/extra/src/ieee802154/framer.rs +++ b/capsules/extra/src/ieee802154/framer.rs @@ -99,7 +99,52 @@ use kernel::ErrorCode; #[derive(Eq, PartialEq, Debug)] pub struct Frame { buf: &'static mut [u8], - info: FrameInfo, + info: FrameInfoWrap, +} + +/// This enum wraps the `FrameInfo` struct and allows each sending type +/// (Parse or Raw) to only store the relevant information. In the +/// case of a Raw send, the `FrameInfo` struct is irrelevant as the +/// packet has been fully formed by the userprocess. For a Raw send, +/// we only require knowledge on the frame length. In the case of a +/// Parse send, the wrapper provides all required frame header information. +#[derive(Eq, PartialEq, Debug)] +enum FrameInfoWrap { + Raw(usize), + Parse(FrameInfo), +} + +impl FrameInfoWrap { + /// Obtain secured_length of the Frame + pub fn secured_length(&self) -> usize { + match self { + FrameInfoWrap::Parse(info) => info.secured_length(), + FrameInfoWrap::Raw(len) => *len, + } + } + + /// Obtain unsecured_length of the Frame + pub fn unsecured_length(&self) -> usize { + match self { + FrameInfoWrap::Parse(info) => info.unsecured_length(), + FrameInfoWrap::Raw(len) => *len, + } + } + + /// Fetcher of the FrameInfo struct for Parse sending. Panics if + /// called for Raw sending. + pub fn get_info(&self) -> FrameInfo { + match self { + FrameInfoWrap::Raw(_) => { + // This should never be called for a Raw send. The Framer should never + // require information other than the Frame length for a Raw send. This + // warrants a panic condition as fetching the `FrameInfo` struct for a + // Raw send is undefined behavior. + panic!("FrameInfoWrap::Raw called when expecting FrameInfoWrap::Parse") + } + FrameInfoWrap::Parse(info) => *info, + } + } } /// This contains just enough information about a frame to determine @@ -144,8 +189,13 @@ impl Frame { } let begin = radio::PSDU_OFFSET + self.info.unsecured_length(); self.buf[begin..begin + payload.len()].copy_from_slice(payload); - self.info.data_len += payload.len(); - + match self.info { + FrameInfoWrap::Raw(len) => self.info = FrameInfoWrap::Raw(len + payload.len()), + FrameInfoWrap::Parse(mut info) => { + info.data_len += payload.len(); + self.info = FrameInfoWrap::Parse(info); + } + } Ok(()) } @@ -160,8 +210,13 @@ impl Frame { } let begin = radio::PSDU_OFFSET + self.info.unsecured_length(); payload_buf.copy_to_slice(&mut self.buf[begin..begin + payload_buf.len()]); - self.info.data_len += payload_buf.len(); - + match self.info { + FrameInfoWrap::Raw(len) => self.info = FrameInfoWrap::Raw(len + payload_buf.len()), + FrameInfoWrap::Parse(mut info) => { + info.data_len += payload_buf.len(); + self.info = FrameInfoWrap::Parse(info); + } + } Ok(()) } } @@ -287,13 +342,13 @@ enum TxState { /// There is no frame to be transmitted. Idle, /// There is a valid frame that needs to be secured before transmission. - ReadyToEncrypt(FrameInfo, &'static mut [u8]), + ReadyToEncrypt(FrameInfoWrap, &'static mut [u8]), /// There is currently a frame being encrypted by the encryption facility. #[allow(dead_code)] - Encrypting(FrameInfo), + Encrypting(FrameInfoWrap), /// There is a frame that is completely secured or does not require /// security, and is waiting to be passed to the radio. - ReadyToTransmit(FrameInfo, &'static mut [u8]), + ReadyToTransmit(FrameInfoWrap, &'static mut [u8]), } #[derive(Eq, PartialEq, Debug)] @@ -301,14 +356,14 @@ enum RxState { /// There is no frame that has been received. Idle, /// There is a secured frame that needs to be decrypted. - ReadyToDecrypt(FrameInfo, &'static mut [u8]), + ReadyToDecrypt(FrameInfoWrap, &'static mut [u8]), /// A secured frame is currently being decrypted by the decryption facility. #[allow(dead_code)] - Decrypting(FrameInfo), + Decrypting(FrameInfoWrap), /// There is an unsecured frame that needs to be re-parsed and exposed to /// the client. #[allow(dead_code)] - ReadyToYield(FrameInfo, &'static mut [u8]), + ReadyToYield(FrameInfoWrap, &'static mut [u8]), } /// This struct wraps an IEEE 802.15.4 radio device `kernel::hil::radio::Radio` @@ -383,7 +438,16 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { /// Performs the first checks in the security procedure. The rest of the /// steps are performed as part of the transmission pipeline. /// Returns the next `TxState` to enter. - fn outgoing_frame_security(&self, buf: &'static mut [u8], frame_info: FrameInfo) -> TxState { + fn outgoing_frame_security( + &self, + buf: &'static mut [u8], + frame_info_wrap: FrameInfoWrap, + ) -> TxState { + let frame_info = match frame_info_wrap { + FrameInfoWrap::Parse(info) => info, + FrameInfoWrap::Raw(_) => return TxState::ReadyToTransmit(frame_info_wrap, buf), + }; + // IEEE 802.15.4-2015: 9.2.1, outgoing frame security // Steps a-e have already been performed in the frame preparation step, // so we only need to dispatch on the security parameters in the frame info @@ -392,12 +456,12 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { if level == SecurityLevel::None { // This case should never occur if the FrameInfo was // prepared by prepare_data_frame - TxState::ReadyToTransmit(frame_info, buf) + TxState::ReadyToTransmit(frame_info_wrap, buf) } else { - TxState::ReadyToEncrypt(frame_info, buf) + TxState::ReadyToEncrypt(frame_info_wrap, buf) } } - None => TxState::ReadyToTransmit(frame_info, buf), + None => TxState::ReadyToTransmit(frame_info_wrap, buf), } } @@ -500,7 +564,7 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { self.mac.set_receive_buffer(buf); RxState::Idle } - Some(frame_info) => RxState::ReadyToDecrypt(frame_info, buf), + Some(frame_info) => RxState::ReadyToDecrypt(FrameInfoWrap::Parse(frame_info), buf), } } @@ -514,14 +578,14 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { let (next_state, result) = match state { TxState::Idle => (TxState::Idle, Ok(())), TxState::ReadyToEncrypt(info, buf) => { - match info.security_params { + match info.get_info().security_params { None => { // `ReadyToEncrypt` should only be entered when // `security_params` is not `None`. (TxState::Idle, Err((ErrorCode::FAIL, buf))) } Some((level, key, nonce)) => { - let (m_off, m_len) = info.ccm_encrypt_ranges(); + let (m_off, m_len) = info.get_info().ccm_encrypt_ranges(); let (a_off, m_off) = (radio::PSDU_OFFSET, radio::PSDU_OFFSET + m_off); @@ -536,7 +600,7 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { a_off, m_off, m_len, - info.mic_len, + info.get_info().mic_len, level.encryption_needed(), true, ); @@ -582,14 +646,14 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { let next_state = match state { RxState::Idle => RxState::Idle, RxState::ReadyToDecrypt(info, buf) => { - match info.security_params { + match info.get_info().security_params { None => { // `ReadyToDecrypt` should only be entered when // `security_params` is not `None`. RxState::Idle } Some((level, key, nonce)) => { - let (m_off, m_len) = info.ccm_encrypt_ranges(); + let (m_off, m_len) = info.get_info().ccm_encrypt_ranges(); let (a_off, m_off) = (radio::PSDU_OFFSET, radio::PSDU_OFFSET + m_off); // Crypto setup failed; fail receiving packet and return to idle @@ -620,7 +684,7 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { a_off, m_off, m_len, - info.mic_len, + info.get_info().mic_len, level.encryption_needed(), true, ) @@ -816,19 +880,36 @@ impl<'a, M: Mac<'a>, A: AES128CCM<'a>> MacDevice<'a> for Framer<'a, M, A> { match header.encode(&mut buf[radio::PSDU_OFFSET..], true).done() { Some((data_offset, mac_payload_offset)) => Ok(Frame { buf: buf, - info: FrameInfo { + info: FrameInfoWrap::Parse(FrameInfo { frame_type: FrameType::Data, mac_payload_offset: mac_payload_offset, data_offset: data_offset, data_len: 0, mic_len: mic_len, security_params: security_desc.map(|(sec, key, nonce)| (sec.level, key, nonce)), - }, + }), }), None => Err(buf), } } + fn buf_to_frame( + &self, + buf: &'static mut [u8], + len: usize, + ) -> Result { + // Error check input for compliance with max 15.4 buffer size and + // that the provided len is compatibile with the provided buffer. + if buf.len() < radio::MAX_BUF_SIZE || len > buf.len() { + return Err((ErrorCode::INVAL, buf)); + } + + Ok(Frame { + buf: buf, + info: FrameInfoWrap::Raw(len), + }) + } + fn transmit(&self, frame: Frame) -> Result<(), (ErrorCode, &'static mut [u8])> { let Frame { buf, info } = frame; let state = match self.tx_state.take() { diff --git a/capsules/extra/src/ieee802154/virtual_mac.rs b/capsules/extra/src/ieee802154/virtual_mac.rs index efc499d375..3d669892f2 100644 --- a/capsules/extra/src/ieee802154/virtual_mac.rs +++ b/capsules/extra/src/ieee802154/virtual_mac.rs @@ -44,13 +44,13 @@ use kernel::ErrorCode; /// IEE 802.15.4 MAC device muxer that keeps a list of MAC users and sequences /// any pending transmission requests. Any received frames from the underlying /// MAC device are sent to all users. -pub struct MuxMac<'a> { - mac: &'a dyn device::MacDevice<'a>, - users: List<'a, MacUser<'a>>, - inflight: OptionalCell<&'a MacUser<'a>>, +pub struct MuxMac<'a, M: device::MacDevice<'a>> { + mac: &'a M, + users: List<'a, MacUser<'a, M>>, + inflight: OptionalCell<&'a MacUser<'a, M>>, } -impl device::TxClient for MuxMac<'_> { +impl<'a, M: device::MacDevice<'a>> device::TxClient for MuxMac<'a, M> { fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.inflight.take().map(move |user| { user.send_done(spi_buf, acked, result); @@ -59,7 +59,7 @@ impl device::TxClient for MuxMac<'_> { } } -impl device::RxClient for MuxMac<'_> { +impl<'a, M: device::MacDevice<'a>> device::RxClient for MuxMac<'a, M> { fn receive<'b>(&self, buf: &'b [u8], header: Header<'b>, data_offset: usize, data_len: usize) { for user in self.users.iter() { user.receive(buf, header, data_offset, data_len); @@ -67,8 +67,8 @@ impl device::RxClient for MuxMac<'_> { } } -impl<'a> MuxMac<'a> { - pub const fn new(mac: &'a dyn device::MacDevice<'a>) -> MuxMac<'a> { +impl<'a, M: device::MacDevice<'a>> MuxMac<'a, M> { + pub const fn new(mac: &'a M) -> MuxMac<'a, M> { MuxMac { mac: mac, users: List::new(), @@ -78,13 +78,13 @@ impl<'a> MuxMac<'a> { /// Registers a MAC user with this MAC mux device. Each MAC user should only /// be registered once. - pub fn add_user(&self, user: &'a MacUser<'a>) { + pub fn add_user(&self, user: &'a MacUser<'a, M>) { self.users.push_head(user); } /// Gets the next `MacUser` and operation to perform if an operation is not /// already underway. - fn get_next_op_if_idle(&self) -> Option<(&'a MacUser<'a>, Op)> { + fn get_next_op_if_idle(&self) -> Option<(&'a MacUser<'a, M>, Op)> { if self.inflight.is_some() { return None; } @@ -107,7 +107,7 @@ impl<'a> MuxMac<'a> { /// Performs a non-idle operation on a `MacUser` asynchronously: that is, if the /// transmission operation results in immediate failure, then return the /// buffer to the `MacUser` via its transmit client. - fn perform_op_async(&self, node: &'a MacUser<'a>, op: Op) { + fn perform_op_async(&self, node: &'a MacUser<'a, M>, op: Op) { if let Op::Transmit(frame) = op { match self.mac.transmit(frame) { // If Err, the transmission failed, @@ -126,7 +126,7 @@ impl<'a> MuxMac<'a> { /// the error code and the buffer immediately. fn perform_op_sync( &self, - node: &'a MacUser<'a>, + node: &'a MacUser<'a, M>, op: Op, ) -> Option> { if let Op::Transmit(frame) = op { @@ -162,7 +162,7 @@ impl<'a> MuxMac<'a> { /// device but fails immediately, return the buffer synchronously. fn do_next_op_sync( &self, - new_node: &MacUser<'a>, + new_node: &MacUser<'a, M>, ) -> Option> { self.get_next_op_if_idle().and_then(|(node, op)| { if core::ptr::eq(node, new_node) { @@ -192,17 +192,17 @@ enum Op { /// all MacUsers because there is only one MAC device. For example, the MAC /// device address is shared, so calling `set_address` on one `MacUser` sets the /// MAC address for all `MacUser`s. -pub struct MacUser<'a> { - mux: &'a MuxMac<'a>, +pub struct MacUser<'a, M: device::MacDevice<'a>> { + mux: &'a MuxMac<'a, M>, operation: MapCell, - next: ListLink<'a, MacUser<'a>>, + next: ListLink<'a, MacUser<'a, M>>, tx_client: Cell>, rx_client: Cell>, } -impl<'a> MacUser<'a> { - pub const fn new(mux: &'a MuxMac<'a>) -> MacUser<'a> { - MacUser { +impl<'a, M: device::MacDevice<'a>> MacUser<'a, M> { + pub const fn new(mux: &'a MuxMac<'a, M>) -> Self { + Self { mux: mux, operation: MapCell::new(Op::Idle), next: ListLink::empty(), @@ -212,7 +212,7 @@ impl<'a> MacUser<'a> { } } -impl MacUser<'_> { +impl<'a, M: device::MacDevice<'a>> MacUser<'a, M> { fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.tx_client .get() @@ -226,13 +226,13 @@ impl MacUser<'_> { } } -impl<'a> ListNode<'a, MacUser<'a>> for MacUser<'a> { - fn next(&'a self) -> &'a ListLink<'a, MacUser<'a>> { +impl<'a, M: device::MacDevice<'a>> ListNode<'a, MacUser<'a, M>> for MacUser<'a, M> { + fn next(&'a self) -> &'a ListLink<'a, MacUser<'a, M>> { &self.next } } -impl<'a> device::MacDevice<'a> for MacUser<'a> { +impl<'a, M: device::MacDevice<'a>> device::MacDevice<'a> for MacUser<'a, M> { fn set_transmit_client(&self, client: &'a dyn device::TxClient) { self.tx_client.set(Some(client)); } @@ -287,6 +287,14 @@ impl<'a> device::MacDevice<'a> for MacUser<'a> { .prepare_data_frame(buf, dst_pan, dst_addr, src_pan, src_addr, security_needed) } + fn buf_to_frame( + &self, + buf: &'static mut [u8], + len: usize, + ) -> Result { + self.mux.mac.buf_to_frame(buf, len) + } + fn transmit(&self, frame: framer::Frame) -> Result<(), (ErrorCode, &'static mut [u8])> { // If the muxer is idle, immediately transmit the frame, otherwise // attempt to queue the transmission request. However, each MAC user can diff --git a/capsules/extra/src/lib.rs b/capsules/extra/src/lib.rs index 240db15598..102347e44f 100644 --- a/capsules/extra/src/lib.rs +++ b/capsules/extra/src/lib.rs @@ -75,6 +75,7 @@ pub mod read_only_state; pub mod rf233; pub mod rf233_const; pub mod screen; +pub mod screen_shared; pub mod sdcard; pub mod segger_rtt; pub mod seven_segment; @@ -85,6 +86,7 @@ pub mod sht4x; pub mod si7021; pub mod sip_hash; pub mod sound_pressure; +pub mod ssd1306; pub mod st77xx; pub mod symmetric_encryption; pub mod temperature; diff --git a/capsules/extra/src/screen_shared.rs b/capsules/extra/src/screen_shared.rs new file mode 100644 index 0000000000..cb98b30884 --- /dev/null +++ b/capsules/extra/src/screen_shared.rs @@ -0,0 +1,459 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Shares a screen among multiple userspace processes. +//! +//! The screen can be split into multiple regions, and regions are assigned to +//! processes by AppID. +//! +//! Boards should create an array of `AppScreenRegion` objects that assign apps +//! to specific regions (frames) within the screen. +//! +//! ``` +//! AppScreenRegion { +//! app_id: kernel::process:ShortID::new(id), +//! frame: Frame { +//! x: 0, +//! y: 0, +//! width: 8, +//! height: 16, +//! } +//! } +//! ``` +//! +//! This driver uses a subset of the API from `Screen`. It does not support any +//! screen config settings (brightness, invert) as those operations affect the +//! entire screen. + +use core::convert::From; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::processbuffer::ReadableProcessBuffer; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Screen as usize; + +/// Ids for read-only allow buffers +mod ro_allow { + pub const SHARED: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +#[derive(Clone, Copy, PartialEq)] +enum ScreenCommand { + WriteSetFrame, + WriteBuffer, +} + +fn pixels_in_bytes(pixels: usize, bits_per_pixel: usize) -> usize { + let bytes = pixels * bits_per_pixel / 8; + if pixels * bits_per_pixel % 8 != 0 { + bytes + 1 + } else { + bytes + } +} + +/// Rectangular region of a screen. +#[derive(Default, Clone, Copy, PartialEq)] +pub struct Frame { + /// X coordinate of the upper left corner of the frame. + x: usize, + /// Y coordinate of the upper left corner of the frame. + y: usize, + /// Width of the frame. + width: usize, + /// Height of the frame. + height: usize, +} + +pub struct AppScreenRegion { + app_id: kernel::process::ShortID, + frame: Frame, +} + +impl AppScreenRegion { + pub fn new( + app_id: kernel::process::ShortID, + x: usize, + y: usize, + width: usize, + height: usize, + ) -> Self { + Self { + app_id, + frame: Frame { + x, + y, + width, + height, + }, + } + } +} + +#[derive(Default)] +pub struct App { + /// The app has requested some screen operation, or `None()` if idle. + command: Option, + /// The current frame the app is using. + frame: Frame, +} + +/// A userspace driver that allows multiple apps to use the same screen. +/// +/// Each app is given a pre-set rectangular region of the screen to use. +pub struct ScreenShared<'a, S: hil::screen::Screen<'a>> { + /// Underlying screen driver to use. + screen: &'a S, + + /// Grant region for apps using the screen. + apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + + /// Static allocations of screen regions for each app. + apps_regions: &'a [AppScreenRegion], + + /// The process currently executing a command on the screen. + current_process: OptionalCell, + + /// Internal buffer for write commands. + buffer: TakeCell<'static, [u8]>, +} + +impl<'a, S: hil::screen::Screen<'a>> ScreenShared<'a, S> { + pub fn new( + screen: &'a S, + grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + buffer: &'static mut [u8], + apps_regions: &'a [AppScreenRegion], + ) -> ScreenShared<'a, S> { + ScreenShared { + screen: screen, + apps: grant, + current_process: OptionalCell::empty(), + buffer: TakeCell::new(buffer), + apps_regions, + } + } + + // Enqueue a command for the given app. + fn enqueue_command(&self, command: ScreenCommand, process_id: ProcessId) -> CommandReturn { + let ret = self + .apps + .enter(process_id, |app, _| { + if app.command.is_some() { + Err(ErrorCode::BUSY) + } else { + app.command = Some(command); + Ok(()) + } + }) + .map_err(ErrorCode::from) + .and_then(|r| r) + .into(); + + if self.current_process.is_none() { + self.run_next_command(); + } + + ret + } + + /// Calculate the frame within the entire screen that the app is currently + /// trying to use. This is the `app_frame` within the app's allocated + /// `app_screen_region`. + fn calculate_absolute_frame(&self, app_screen_region_frame: Frame, app_frame: Frame) -> Frame { + // x and y are sums + let mut absolute_x = app_screen_region_frame.x + app_frame.x; + let mut absolute_y = app_screen_region_frame.y + app_frame.y; + // width and height are simply the app_frame width and height. + let mut absolute_w = app_frame.width; + let mut absolute_h = app_frame.height; + + // Make sure that the calculate frame is within the allocated region. + absolute_x = core::cmp::min( + app_screen_region_frame.x + app_screen_region_frame.width, + absolute_x, + ); + absolute_y = core::cmp::min( + app_screen_region_frame.y + app_screen_region_frame.height, + absolute_y, + ); + absolute_w = core::cmp::min( + app_screen_region_frame.x + app_screen_region_frame.width - absolute_x, + absolute_w, + ); + absolute_h = core::cmp::min( + app_screen_region_frame.y + app_screen_region_frame.height - absolute_y, + absolute_h, + ); + + Frame { + x: absolute_x, + y: absolute_y, + width: absolute_w, + height: absolute_h, + } + } + + fn call_screen( + &self, + process_id: ProcessId, + app_screen_region_frame: Frame, + ) -> Result<(), ErrorCode> { + self.apps + .enter(process_id, |app, kernel_data| { + match app.command { + Some(ScreenCommand::WriteSetFrame) => { + let absolute_frame = + self.calculate_absolute_frame(app_screen_region_frame, app.frame); + + app.command = Some(ScreenCommand::WriteBuffer); + self.screen + .set_write_frame( + absolute_frame.x, + absolute_frame.y, + absolute_frame.width, + absolute_frame.height, + ) + .map_err(|e| { + app.command = None; + e + }) + } + Some(ScreenCommand::WriteBuffer) => { + app.command = None; + kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .map(|allow_buf| { + let len = allow_buf.len(); + + if len == 0 { + Err(ErrorCode::NOMEM) + } else if !self.is_len_multiple_color_depth(len) { + Err(ErrorCode::INVAL) + } else { + // All good, copy buffer. + + self.buffer.take().map_or(Err(ErrorCode::FAIL), |buffer| { + let copy_len = + core::cmp::min(buffer.len(), allow_buf.len()); + allow_buf.enter(|ab| { + // buffer[..copy_len].copy_from_slice(ab[..copy_len]); + ab[..copy_len].copy_to_slice(&mut buffer[..copy_len]) + })?; + + // Send to screen. + let mut data = SubSliceMut::new(buffer); + data.slice(..copy_len); + self.screen.write(data, false) + }) + } + }) + .map_err(ErrorCode::from) + .and_then(|r| r) + } + _ => Err(ErrorCode::NOSUPPORT), + } + }) + .map_err(ErrorCode::from) + .and_then(|r| r) + } + + fn schedule_callback(&self, process_id: ProcessId, data1: usize, data2: usize, data3: usize) { + let _ = self.apps.enter(process_id, |_app, kernel_data| { + kernel_data.schedule_upcall(0, (data1, data2, data3)).ok(); + }); + } + + fn get_app_screen_region_frame(&self, process_id: ProcessId) -> Option { + let short_id = process_id.short_app_id(); + + for app_screen_region in self.apps_regions { + if short_id == app_screen_region.app_id { + return Some(app_screen_region.frame); + } + } + None + } + + fn run_next_command(&self) { + let ran_cmd = self.current_process.map_or(false, |process_id| { + let app_region_frame = self.get_app_screen_region_frame(process_id); + + app_region_frame.map_or(false, |frame| { + let r = self.call_screen(process_id, frame); + if r.is_err() { + // We were unable to run the screen operation meaning we + // will not get a callback and we need to report the error. + self.current_process.take().map(|process_id| { + self.schedule_callback( + process_id, + kernel::errorcode::into_statuscode(r), + 0, + 0, + ); + }); + false + } else { + true + } + }) + }); + + if !ran_cmd { + // Check if there are any pending events. + for app in self.apps.iter() { + let process_id = app.processid(); + + // Check if this process has both a pending command and is + // allocated a region on the screen. + let frame_maybe = app.enter(|app, _| { + if app.command.is_some() { + self.get_app_screen_region_frame(process_id) + } else { + None + } + }); + + // If we have a candidate, try to execute the screen operation. + if frame_maybe.is_some() { + match frame_maybe { + Some(frame) => { + // Reserve the screen for this process and execute + // the operation. + self.current_process.set(process_id); + match self.call_screen(process_id, frame) { + Ok(()) => { + // Everything is good, stop looking for apps + // to execute. + break; + } + Err(err) => { + // Could not run the screen command. + // Un-reserve the screen and do an upcall + // with the bad news. + self.current_process.clear(); + self.schedule_callback( + process_id, + kernel::errorcode::into_statuscode(Err(err)), + 0, + 0, + ); + } + } + } + None => {} + } + } + } + } + } + + fn is_len_multiple_color_depth(&self, len: usize) -> bool { + let depth = pixels_in_bytes(1, self.screen.get_pixel_format().get_bits_per_pixel()); + (len % depth) == 0 + } +} + +impl<'a, S: hil::screen::Screen<'a>> hil::screen::ScreenClient for ScreenShared<'a, S> { + fn command_complete(&self, r: Result<(), ErrorCode>) { + if r.is_err() { + self.current_process.take().map(|process_id| { + self.schedule_callback(process_id, kernel::errorcode::into_statuscode(r), 0, 0); + }); + } + + self.run_next_command(); + } + + fn write_complete(&self, data: SubSliceMut<'static, u8>, r: Result<(), ErrorCode>) { + self.buffer.replace(data.take()); + + // Notify that the write is finished. + self.current_process.take().map(|process_id| { + self.schedule_callback(process_id, kernel::errorcode::into_statuscode(r), 0, 0); + }); + + self.run_next_command(); + } + + fn screen_is_ready(&self) { + self.run_next_command(); + } +} + +impl<'a, S: hil::screen::Screen<'a>> SyscallDriver for ScreenShared<'a, S> { + fn command( + &self, + command_num: usize, + data1: usize, + data2: usize, + process_id: ProcessId, + ) -> CommandReturn { + match command_num { + // Driver existence check + 0 => CommandReturn::success(), + + // Get Rotation + 21 => CommandReturn::success_u32(self.screen.get_rotation() as u32), + + // Get Resolution + 23 => match self.get_app_screen_region_frame(process_id) { + Some(frame) => { + CommandReturn::success_u32_u32(frame.width as u32, frame.height as u32) + } + None => CommandReturn::failure(ErrorCode::NOSUPPORT), + }, + + // Get pixel format + 25 => CommandReturn::success_u32(self.screen.get_pixel_format() as u32), + + // Set Write Frame + 100 => { + let frame = Frame { + x: (data1 >> 16) & 0xFFFF, + y: data1 & 0xFFFF, + width: (data2 >> 16) & 0xFFFF, + height: data2 & 0xFFFF, + }; + + self.apps + .enter(process_id, |app, kernel_data| { + app.frame = frame; + + // Just issue upcall. + let _ = kernel_data + .schedule_upcall(0, (kernel::errorcode::into_statuscode(Ok(())), 0, 0)); + }) + .map_err(ErrorCode::from) + .into() + } + + // Write + 200 => { + // First check if this app has any screen real estate allocated. + // If not, return error. + if self.get_app_screen_region_frame(process_id).is_none() { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } else { + self.enqueue_command(ScreenCommand::WriteSetFrame, process_id) + } + } + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/extra/src/ssd1306.rs b/capsules/extra/src/ssd1306.rs new file mode 100644 index 0000000000..ea6f0b81ee --- /dev/null +++ b/capsules/extra/src/ssd1306.rs @@ -0,0 +1,564 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! SSD1306/SSD1315 OLED Screen + +use core::cell::Cell; +use kernel::hil; +use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +pub const BUFFER_SIZE: usize = 1032; + +const WIDTH: usize = 128; +const HEIGHT: usize = 64; + +#[derive(Copy, Clone, PartialEq)] +#[repr(usize)] +pub enum Command { + // Charge Pump Commands + /// Charge Pump Setting. + SetChargePump { enable: bool }, + + // Fundamental Commands + /// SetContrastControl. Double byte command to select 1 out of 256 contrast + /// steps. Contrast increases as the value increases. + SetContrast { contrast: u8 }, + /// Entire Display On. + EntireDisplayOn { ignore_ram: bool }, + /// Set Normal Display. + SetDisplayInvert { inverse: bool }, + /// Set Display Off. + SetDisplayOnOff { on: bool }, + + // Scrolling Commands + /// Continuous Horizontal Scroll. Right or Left Horizontal Scroll. + ContinuousHorizontalScroll { + left: bool, + page_start: u8, + interval: u8, + page_end: u8, + }, + /// Continuous Vertical and Horizontal Scroll. Vertical and Right Horizontal + /// Scroll. + ContinuousVerticalHorizontalScroll { + left: bool, + page_start: u8, + interval: u8, + page_end: u8, + vertical_offset: u8, + }, + /// Deactivate Scroll. Stop scrolling that is configured by scroll commands. + DeactivateScroll = 0x2e, + /// Activate Scroll. Start scrolling that is configured by scroll commands. + ActivateScroll = 0x2f, + /// Set Vertical Scroll Area. Set number of rows in top fixed area. The + /// number of rows in top fixed area is referenced to the top of the GDDRAM + /// (i.e. row 0). + SetVerticalScrollArea { rows_fixed: u8, rows_scroll: u8 }, + + // Addressing Setting Commands + /// Set Lower Column Start Address for Page Addressing Mode. + /// + /// Set the lower nibble of the column start address register for Page + /// Addressing Mode using `X[3:0]` as data bits. The initial display line + /// register is reset to 0000b after RESET. + SetLowerColumnStartAddress { address: u8 }, + /// Set Higher Column Start Address for Page Addressing Mode. + /// + /// Set the higher nibble of the column start address register for Page + /// Addressing Mode using `X[3:0]` as data bits. The initial display line + /// register is reset to 0000b after RESET. + SetHigherColumnStartAddress { address: u8 }, + /// Set Memory Addressing Mode. + SetMemoryAddressingMode { mode: u8 }, + /// Set Column Address. Setup column start and end address. + SetColumnAddress { column_start: u8, column_end: u8 }, + /// Set Page Address. Setup page start and end address. + SetPageAddress { page_start: u8, page_end: u8 }, + /// Set Page Start Address for Page Addressing Mode. Set GDDRAM Page Start + /// Address (PAGE0~PAGE7) for Page Addressing Mode using `X[2:0]`. + SetPageStartAddress { address: u8 }, + + // Hardware Configuration Commands + /// Set Display Start Line. Set display RAM display start line register from + /// 0-63 using `X[5:0]`. + SetDisplayStartLine { line: u8 }, + /// Set Segment Remap. + SetSegmentRemap { reverse: bool }, + /// Set Multiplex Ratio. + SetMultiplexRatio { ratio: u8 }, + /// Set COM Output Scan Direction. + SetComScanDirection { decrement: bool }, + /// Set Display Offset. Set vertical shift by COM from 0-63. + SetDisplayOffset { vertical_shift: u8 } = 0xd3, + /// Set COM Pins Hardware Configuration + SetComPins { alternative: bool, enable_com: bool }, + + // Timing & Driving Scheme Setting Commands. + /// Set Display Clock Divide Ratio/Oscillator Frequency. + SetDisplayClockDivide { + divide_ratio: u8, + oscillator_frequency: u8, + }, + /// Set Pre-charge Period. + SetPrechargePeriod { phase1: u8, phase2: u8 }, + /// Set VCOMH Deselect Level. + SetVcomDeselect { level: u8 }, +} + +impl Command { + fn encode(self, buffer: &mut SubSliceMut<'static, u8>) { + let take = match self { + Self::SetChargePump { enable } => { + buffer[0] = 0x8D; + buffer[1] = 0x10 | ((enable as u8) << 2); + 2 + } + Self::SetContrast { contrast } => { + buffer[0] = 0x81; + buffer[1] = contrast; + 2 + } + Self::EntireDisplayOn { ignore_ram } => { + buffer[0] = 0xa4 | (ignore_ram as u8); + 1 + } + Self::SetDisplayInvert { inverse } => { + buffer[0] = 0xa6 | (inverse as u8); + 1 + } + Self::SetDisplayOnOff { on } => { + buffer[0] = 0xae | (on as u8); + 1 + } + Self::ContinuousHorizontalScroll { + left, + page_start, + interval, + page_end, + } => { + buffer[0] = 0x26 | (left as u8); + buffer[1] = 0; + buffer[2] = page_start; + buffer[3] = interval; + buffer[4] = page_end; + buffer[5] = 0; + buffer[6] = 0xff; + 7 + } + Self::ContinuousVerticalHorizontalScroll { + left, + page_start, + interval, + page_end, + vertical_offset, + } => { + buffer[0] = 0x29 | (left as u8); + buffer[1] = 0; + buffer[2] = page_start; + buffer[3] = interval; + buffer[4] = page_end; + buffer[5] = vertical_offset; + 6 + } + Self::DeactivateScroll => { + buffer[0] = 0x2e; + 1 + } + Self::ActivateScroll => { + buffer[0] = 0x2f; + 1 + } + Self::SetVerticalScrollArea { + rows_fixed, + rows_scroll, + } => { + buffer[0] = 0xa3; + buffer[1] = rows_fixed; + buffer[2] = rows_scroll; + 3 + } + Self::SetLowerColumnStartAddress { address } => { + buffer[0] = 0x00 | (address & 0xF); + 1 + } + Self::SetHigherColumnStartAddress { address } => { + buffer[0] = 0x10 | (address & 0xF); + 1 + } + Self::SetMemoryAddressingMode { mode } => { + buffer[0] = 0x20; + buffer[1] = mode; + 2 + } + Self::SetColumnAddress { + column_start, + column_end, + } => { + buffer[0] = 0x21; + buffer[1] = column_start; + buffer[2] = column_end; + 3 + } + Self::SetPageAddress { + page_start, + page_end, + } => { + buffer[0] = 0x22; + buffer[1] = page_start; + buffer[2] = page_end; + 3 + } + Self::SetPageStartAddress { address } => { + buffer[0] = 0xb0 | (address & 0x7); + 1 + } + Self::SetDisplayStartLine { line } => { + buffer[0] = 0x40 | (line & 0x3F); + 1 + } + Self::SetSegmentRemap { reverse } => { + buffer[0] = 0xa0 | (reverse as u8); + 1 + } + Self::SetMultiplexRatio { ratio } => { + buffer[0] = 0xa8; + buffer[1] = ratio; + 2 + } + Self::SetComScanDirection { decrement } => { + buffer[0] = 0xc0 | ((decrement as u8) << 3); + 1 + } + Self::SetDisplayOffset { vertical_shift } => { + buffer[0] = 0xd3; + buffer[1] = vertical_shift; + 2 + } + Self::SetComPins { + alternative, + enable_com, + } => { + buffer[0] = 0xda; + buffer[1] = ((alternative as u8) << 4) | ((enable_com as u8) << 5) | 0x2; + 2 + } + Self::SetDisplayClockDivide { + divide_ratio, + oscillator_frequency, + } => { + buffer[0] = 0xd5; + buffer[1] = ((oscillator_frequency & 0xF) << 4) | (divide_ratio & 0xf); + 2 + } + Self::SetPrechargePeriod { phase1, phase2 } => { + buffer[0] = 0xd9; + buffer[1] = ((phase2 & 0xF) << 4) | (phase1 & 0xf); + 2 + } + Self::SetVcomDeselect { level } => { + buffer[0] = 0xdb; + buffer[1] = (level & 0xF) << 4; + 2 + } + }; + + // Move the available region of the buffer to what is remaining after + // this command was encoded. + buffer.slice(take..); + } +} + +// #[derive(Copy, Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +enum State { + Idle, + Init, + SimpleCommand, + Write, +} + +pub struct Ssd1306<'a, I: hil::i2c::I2CDevice> { + i2c: &'a I, + state: Cell, + client: OptionalCell<&'a dyn hil::screen::ScreenClient>, + setup_client: OptionalCell<&'a dyn hil::screen::ScreenSetupClient>, + buffer: TakeCell<'static, [u8]>, + write_buffer: MapCell>, + enable_charge_pump: bool, +} + +impl<'a, I: hil::i2c::I2CDevice> Ssd1306<'a, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8], enable_charge_pump: bool) -> Ssd1306<'a, I> { + Ssd1306 { + i2c, + state: Cell::new(State::Idle), + client: OptionalCell::empty(), + setup_client: OptionalCell::empty(), + buffer: TakeCell::new(buffer), + write_buffer: MapCell::empty(), + enable_charge_pump, + } + } + + pub fn init_screen(&self) { + let commands = [ + Command::SetDisplayOnOff { on: false }, + Command::SetDisplayClockDivide { + divide_ratio: 0, + oscillator_frequency: 0x8, + }, + Command::SetMultiplexRatio { + ratio: HEIGHT as u8 - 1, + }, + Command::SetDisplayOffset { vertical_shift: 0 }, + Command::SetDisplayStartLine { line: 0 }, + Command::SetChargePump { + enable: self.enable_charge_pump, + }, + Command::SetMemoryAddressingMode { mode: 0 }, //horizontal + Command::SetSegmentRemap { reverse: true }, + Command::SetComScanDirection { decrement: true }, + Command::SetComPins { + alternative: true, + enable_com: false, + }, + Command::SetContrast { contrast: 0xcf }, + Command::SetPrechargePeriod { + phase1: 0x1, + phase2: 0xf, + }, + Command::SetVcomDeselect { level: 2 }, + Command::EntireDisplayOn { ignore_ram: false }, + Command::SetDisplayInvert { inverse: false }, + Command::DeactivateScroll, + Command::SetDisplayOnOff { on: true }, + ]; + + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::Init); + } + Err(_e) => {} + } + } + + fn send_sequence(&self, sequence: &[Command]) -> Result<(), ErrorCode> { + if self.state.get() == State::Idle { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + let mut buf_slice = SubSliceMut::new(buffer); + + // Specify this is a series of command bytes. + buf_slice[0] = 0; // Co = 0, D/C̅ = 0 + + // Move the window of the subslice after the command byte header. + buf_slice.slice(1..); + + for cmd in sequence.iter() { + cmd.encode(&mut buf_slice); + } + + // We need the amount of data that has been sliced away + // at the start of the subslice. + let remaining_len = buf_slice.len(); + buf_slice.reset(); + let tx_len = buf_slice.len() - remaining_len; + + self.i2c.enable(); + match self.i2c.write(buf_slice.take(), tx_len) { + Ok(()) => Ok(()), + Err((_e, buf)) => { + self.buffer.replace(buf); + self.i2c.disable(); + Err(ErrorCode::INVAL) + } + } + }) + } else { + Err(ErrorCode::BUSY) + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::screen::ScreenSetup<'a> for Ssd1306<'a, I> { + fn set_client(&self, client: &'a dyn hil::screen::ScreenSetupClient) { + self.setup_client.set(client); + } + + fn set_resolution(&self, _resolution: (usize, usize)) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn set_pixel_format(&self, _depth: hil::screen::ScreenPixelFormat) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn set_rotation(&self, _rotation: hil::screen::ScreenRotation) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn get_num_supported_resolutions(&self) -> usize { + 1 + } + + fn get_supported_resolution(&self, index: usize) -> Option<(usize, usize)> { + match index { + 0 => Some((WIDTH, HEIGHT)), + _ => None, + } + } + + fn get_num_supported_pixel_formats(&self) -> usize { + 1 + } + + fn get_supported_pixel_format(&self, index: usize) -> Option { + match index { + 0 => Some(hil::screen::ScreenPixelFormat::Mono), + _ => None, + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::screen::Screen<'a> for Ssd1306<'a, I> { + fn set_client(&self, client: &'a dyn hil::screen::ScreenClient) { + self.client.set(client); + } + + fn get_resolution(&self) -> (usize, usize) { + (WIDTH, HEIGHT) + } + + fn get_pixel_format(&self) -> hil::screen::ScreenPixelFormat { + hil::screen::ScreenPixelFormat::Mono + } + + fn get_rotation(&self) -> hil::screen::ScreenRotation { + hil::screen::ScreenRotation::Normal + } + + fn set_write_frame( + &self, + x: usize, + y: usize, + width: usize, + height: usize, + ) -> Result<(), ErrorCode> { + let commands = [ + Command::SetPageAddress { + page_start: (y / 8) as u8, + page_end: ((y / 8) + (height / 8) - 1) as u8, + }, + Command::SetColumnAddress { + column_start: x as u8, + column_end: (x + width - 1) as u8, + }, + ]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn write(&self, data: SubSliceMut<'static, u8>, _continue: bool) -> Result<(), ErrorCode> { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + let mut buf_slice = SubSliceMut::new(buffer); + + // Specify this is data. + buf_slice[0] = 0x40; // Co = 0, D/C̅ = 1 + + // Move the window of the subslice after the command byte header. + buf_slice.slice(1..); + + // Figure out how much we can send. + let copy_len = core::cmp::min(buf_slice.len(), data.len()); + + for i in 0..copy_len { + buf_slice[i] = data[i]; + } + + let tx_len = copy_len + 1; + + self.i2c.enable(); + match self.i2c.write(buf_slice.take(), tx_len) { + Ok(()) => { + self.state.set(State::Write); + self.write_buffer.replace(data); + Ok(()) + } + Err((_e, buf)) => { + self.buffer.replace(buf); + Err(ErrorCode::INVAL) + } + } + }) + } + + fn set_brightness(&self, brightness: u16) -> Result<(), ErrorCode> { + let commands = [Command::SetContrast { + contrast: (brightness >> 8) as u8, + }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn set_power(&self, enabled: bool) -> Result<(), ErrorCode> { + let commands = [Command::SetDisplayOnOff { on: enabled }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn set_invert(&self, enabled: bool) -> Result<(), ErrorCode> { + let commands = [Command::SetDisplayInvert { inverse: enabled }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::i2c::I2CClient for Ssd1306<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), hil::i2c::Error>) { + self.buffer.replace(buffer); + self.i2c.disable(); + + match self.state.get() { + State::Init => { + self.state.set(State::Idle); + self.client.map(|client| client.screen_is_ready()); + } + + State::SimpleCommand => { + self.state.set(State::Idle); + self.client.map(|client| client.command_complete(Ok(()))); + } + + State::Write => { + self.state.set(State::Idle); + self.write_buffer.take().map(|buf| { + self.client.map(|client| client.write_complete(buf, Ok(()))); + }); + } + _ => {} + } + } +} diff --git a/capsules/extra/src/test/aes.rs b/capsules/extra/src/test/aes.rs index 33f86ba02b..0a3220416f 100644 --- a/capsules/extra/src/test/aes.rs +++ b/capsules/extra/src/test/aes.rs @@ -4,12 +4,14 @@ //! Test the AES hardware. +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient}; use core::cell::Cell; use kernel::debug; use kernel::hil; use kernel::hil::symmetric_encryption::{ AES128Ctr, AES128, AES128CBC, AES128ECB, AES128_BLOCK_SIZE, AES128_KEY_SIZE, }; +use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; pub struct TestAes128Ctr<'a, A: 'a> { @@ -19,9 +21,12 @@ pub struct TestAes128Ctr<'a, A: 'a> { iv: TakeCell<'a, [u8]>, source: TakeCell<'static, [u8]>, data: TakeCell<'static, [u8]>, + test_decrypt: bool, encrypting: Cell, use_source: Cell, + + client: OptionalCell<&'static dyn CapsuleTestClient>, } pub struct TestAes128Cbc<'a, A: 'a> { @@ -31,9 +36,12 @@ pub struct TestAes128Cbc<'a, A: 'a> { iv: TakeCell<'a, [u8]>, source: TakeCell<'static, [u8]>, data: TakeCell<'static, [u8]>, + test_decrypt: bool, encrypting: Cell, use_source: Cell, + + client: OptionalCell<&'static dyn CapsuleTestClient>, } pub struct TestAes128Ecb<'a, A: 'a> { @@ -42,9 +50,12 @@ pub struct TestAes128Ecb<'a, A: 'a> { key: TakeCell<'a, [u8]>, source: TakeCell<'static, [u8]>, data: TakeCell<'static, [u8]>, + test_decrypt: bool, encrypting: Cell, use_source: Cell, + + client: OptionalCell<&'static dyn CapsuleTestClient>, } const DATA_OFFSET: usize = AES128_BLOCK_SIZE; @@ -56,6 +67,7 @@ impl<'a, A: AES128<'a> + AES128ECB> TestAes128Ecb<'a, A> { key: &'a mut [u8], source: &'static mut [u8], data: &'static mut [u8], + test_decrypt: bool, ) -> Self { TestAes128Ecb { aes: aes, @@ -63,9 +75,12 @@ impl<'a, A: AES128<'a> + AES128ECB> TestAes128Ecb<'a, A> { key: TakeCell::new(key), source: TakeCell::new(source), data: TakeCell::new(data), + test_decrypt, encrypting: Cell::new(true), use_source: Cell::new(true), + + client: OptionalCell::empty(), } } @@ -139,6 +154,12 @@ impl<'a, A: AES128<'a> + AES128ECB> TestAes128Ecb<'a, A> { } } +impl<'a, A: AES128<'a> + AES128ECB> CapsuleTest for TestAes128Ecb<'a, A> { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} + impl<'a, A: AES128<'a> + AES128Ctr> TestAes128Ctr<'a, A> { pub fn new( aes: &'a A, @@ -146,6 +167,7 @@ impl<'a, A: AES128<'a> + AES128Ctr> TestAes128Ctr<'a, A> { iv: &'a mut [u8], source: &'static mut [u8], data: &'static mut [u8], + test_decrypt: bool, ) -> Self { TestAes128Ctr { aes: aes, @@ -154,9 +176,12 @@ impl<'a, A: AES128<'a> + AES128Ctr> TestAes128Ctr<'a, A> { iv: TakeCell::new(iv), source: TakeCell::new(source), data: TakeCell::new(data), + test_decrypt, encrypting: Cell::new(true), use_source: Cell::new(true), + + client: OptionalCell::empty(), } } @@ -288,15 +313,25 @@ impl<'a, A: AES128<'a> + AES128Ctr> hil::symmetric_encryption::Client<'a> for Te self.use_source.set(false); self.run(); } else { - if self.encrypting.get() { + if self.encrypting.get() && self.test_decrypt { self.encrypting.set(false); self.use_source.set(true); self.run(); + } else { + self.client.map(|client| { + client.done(Ok(())); + }); } } } } +impl<'a, A: AES128<'a> + AES128Ctr> CapsuleTest for TestAes128Ctr<'a, A> { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} + impl<'a, A: AES128<'a> + AES128CBC> TestAes128Cbc<'a, A> { pub fn new( aes: &'a A, @@ -304,6 +339,7 @@ impl<'a, A: AES128<'a> + AES128CBC> TestAes128Cbc<'a, A> { iv: &'a mut [u8], source: &'static mut [u8], data: &'static mut [u8], + test_decrypt: bool, ) -> Self { TestAes128Cbc { aes: aes, @@ -312,9 +348,12 @@ impl<'a, A: AES128<'a> + AES128CBC> TestAes128Cbc<'a, A> { iv: TakeCell::new(iv), source: TakeCell::new(source), data: TakeCell::new(data), + test_decrypt, encrypting: Cell::new(true), use_source: Cell::new(true), + + client: OptionalCell::empty(), } } @@ -445,15 +484,25 @@ impl<'a, A: AES128<'a> + AES128CBC> hil::symmetric_encryption::Client<'a> for Te self.use_source.set(false); self.run(); } else { - if self.encrypting.get() { + if self.encrypting.get() && self.test_decrypt { self.encrypting.set(false); self.use_source.set(true); self.run(); + } else { + self.client.map(|client| { + client.done(Ok(())); + }); } } } } +impl<'a, A: AES128<'a> + AES128CBC> CapsuleTest for TestAes128Cbc<'a, A> { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} + impl<'a, A: AES128<'a> + AES128ECB> hil::symmetric_encryption::Client<'a> for TestAes128Ecb<'a, A> { fn crypt_done(&'a self, source: Option<&'static mut [u8]>, dest: &'static mut [u8]) { if self.use_source.get() { @@ -500,10 +549,14 @@ impl<'a, A: AES128<'a> + AES128ECB> hil::symmetric_encryption::Client<'a> for Te self.use_source.set(false); self.run(); } else { - if self.encrypting.get() { + if self.encrypting.get() && self.test_decrypt { self.encrypting.set(false); self.use_source.set(true); self.run(); + } else { + self.client.map(|client| { + client.done(Ok(())); + }); } } } diff --git a/capsules/extra/src/test/hmac_sha256.rs b/capsules/extra/src/test/hmac_sha256.rs index f38d3bdcc7..a9d0ce3dd7 100644 --- a/capsules/extra/src/test/hmac_sha256.rs +++ b/capsules/extra/src/test/hmac_sha256.rs @@ -7,9 +7,11 @@ use crate::hmac_sha256::HmacSha256Software; use crate::sha256::Sha256Software; +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient, CapsuleTestError}; use kernel::hil::digest; use kernel::hil::digest::HmacSha256; -use kernel::hil::digest::{DigestData, DigestDataHash, DigestHash}; +use kernel::hil::digest::{DigestData, DigestHash}; +use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::utilities::leasable_buffer::SubSlice; use kernel::utilities::leasable_buffer::SubSliceMut; @@ -21,6 +23,7 @@ pub struct TestHmacSha256 { data: TakeCell<'static, [u8]>, // The data to hash digest: TakeCell<'static, [u8; 32]>, // The supplied hash correct: &'static [u8; 32], // The supplied hash + client: OptionalCell<&'static dyn CapsuleTestClient>, } impl TestHmacSha256 { @@ -37,11 +40,13 @@ impl TestHmacSha256 { data: TakeCell::new(data), digest: TakeCell::new(digest), correct, + client: OptionalCell::empty(), } } pub fn run(&'static self) { - self.hmac.set_client(self); + kernel::hil::digest::Digest::set_client(self.hmac, self); + let key = self.key.take().unwrap(); let r = self.hmac.set_mode_hmacsha256(key); if r.is_err() { @@ -61,23 +66,64 @@ impl digest::ClientData<32> for TestHmacSha256 { unimplemented!() } - fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { self.data.replace(data.take()); + match result { + Ok(()) => {} + Err(e) => { + kernel::debug!("HmacSha256Test: failed to add data: {:?}", e); + self.client.map(|client| { + client.done(Err(CapsuleTestError::ErrorCode(e))); + }); + return; + } + } + let r = self.hmac.run(self.digest.take().unwrap()); - if r.is_err() { - panic!("HmacSha256Test: failed to run HMAC: {:?}", r); + match r { + Ok(()) => {} + Err((e, d)) => { + kernel::debug!("HmacSha256Test: failed to run HMAC: {:?}", e); + + self.digest.replace(d); + self.client.map(|client| { + client.done(Err(CapsuleTestError::ErrorCode(e))); + }); + } } } } impl digest::ClientHash<32> for TestHmacSha256 { fn hash_done(&self, _result: Result<(), ErrorCode>, digest: &'static mut [u8; 32]) { + let mut error = false; for i in 0..32 { if self.correct[i] != digest[i] { - panic!("HmacSha256Test: incorrect HMAC output!"); + error = true; } } - kernel::debug!("HMAC-SHA256 matches!"); + if !error { + kernel::debug!("HMAC-SHA256 matches!"); + self.client.map(|client| { + client.done(Ok(())); + }); + } else { + kernel::debug!("HmacSha256Test: incorrect HMAC output!"); + self.client.map(|client| { + client.done(Err(CapsuleTestError::IncorrectResult)); + }); + } + } +} + +impl digest::ClientVerify<32> for TestHmacSha256 { + fn verification_done(&self, _result: Result, _compare: &'static mut [u8; 32]) { + } +} + +impl CapsuleTest for TestHmacSha256 { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); } } diff --git a/capsules/extra/src/test/siphash24.rs b/capsules/extra/src/test/siphash24.rs index 117c5d955d..7cc6f3870d 100644 --- a/capsules/extra/src/test/siphash24.rs +++ b/capsules/extra/src/test/siphash24.rs @@ -8,8 +8,9 @@ //! Digest trait. use crate::sip_hash::SipHasher24; -use kernel::debug; +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient, CapsuleTestError}; use kernel::hil::hasher::{Client, Hasher}; +use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::utilities::leasable_buffer::{SubSlice, SubSliceMut}; use kernel::ErrorCode; @@ -19,6 +20,7 @@ pub struct TestSipHash24 { data: TakeCell<'static, [u8]>, // The data to hash hash: TakeCell<'static, [u8; 8]>, // The supplied hash correct_hash: TakeCell<'static, [u8; 8]>, // The correct hash + client: OptionalCell<&'static dyn CapsuleTestClient>, } impl TestSipHash24 { @@ -33,6 +35,7 @@ impl TestSipHash24 { data: TakeCell::new(data), hash: TakeCell::new(hash), correct_hash: TakeCell::new(correct_hash), + client: OptionalCell::empty(), } } @@ -56,10 +59,30 @@ impl Client<8> for TestSipHash24 { fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) {} fn hash_done(&self, _result: Result<(), ErrorCode>, digest: &'static mut [u8; 8]) { - debug!("hashed result: {:?}", digest); - debug!("expected result: {:?}", self.correct_hash.take().unwrap()); + let correct = self.correct_hash.take().unwrap(); + + let matches = correct != digest; + if !matches { + kernel::debug!("TestSipHash24: incorrect hash output!"); + } + kernel::debug!("TestSipHash24 matches!"); self.hash.replace(digest); self.hasher.clear_data(); + + self.client.map(|client| { + let res = if matches { + Ok(()) + } else { + Err(CapsuleTestError::IncorrectResult) + }; + client.done(res); + }); + } +} + +impl CapsuleTest for TestSipHash24 { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); } } diff --git a/chips/apollo3/src/lib.rs b/chips/apollo3/src/lib.rs index ab93461f41..b8744db126 100644 --- a/chips/apollo3/src/lib.rs +++ b/chips/apollo3/src/lib.rs @@ -104,7 +104,7 @@ pub unsafe fn init() { } // Mock implementation for tests -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe fn init() { // Prevent unused code warning. scb::disable_fpca(); diff --git a/chips/arty_e21_chip/src/chip.rs b/chips/arty_e21_chip/src/chip.rs index 9309a74aee..4f8abdefe3 100644 --- a/chips/arty_e21_chip/src/chip.rs +++ b/chips/arty_e21_chip/src/chip.rs @@ -132,7 +132,7 @@ impl<'a, I: InterruptService + 'a> ArtyExx<'a, I> { } // Mock implementation for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn configure_trap_handler(&self) { unimplemented!() } diff --git a/chips/earlgrey/src/chip.rs b/chips/earlgrey/src/chip.rs index 9a32a8546d..7211e243bc 100644 --- a/chips/earlgrey/src/chip.rs +++ b/chips/earlgrey/src/chip.rs @@ -435,7 +435,7 @@ pub unsafe fn configure_trap_handler() { // Mock implementation for crate tests that does not include the section // specifier, as the test will not use our linker script, and the host // compilation environment may not allow the section name. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub extern "C" fn _start_trap_vectored() { use core::hint::unreachable_unchecked; unsafe { diff --git a/chips/esp32-c3/src/chip.rs b/chips/esp32-c3/src/chip.rs index 1624380cb4..c8aea1d0b7 100644 --- a/chips/esp32-c3/src/chip.rs +++ b/chips/esp32-c3/src/chip.rs @@ -287,7 +287,7 @@ pub unsafe fn configure_trap_handler() { // Mock implementation for crate tests that does not include the section // specifier, as the test will not use our linker script, and the host // compilation environment may not allow the section name. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub extern "C" fn _start_trap_vectored() { use core::hint::unreachable_unchecked; unsafe { diff --git a/chips/litex_vexriscv/src/interrupt_controller.rs b/chips/litex_vexriscv/src/interrupt_controller.rs index ecff09f86a..4abc24d50a 100644 --- a/chips/litex_vexriscv/src/interrupt_controller.rs +++ b/chips/litex_vexriscv/src/interrupt_controller.rs @@ -102,7 +102,7 @@ mod vexriscv_irq_raw { /// defined in litex/soc/cores/cpu/vexriscv/csr-defs.h const CSR_IRQ_PENDING: usize = 0xFC0; - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn irq_getmask() -> usize { 0 } @@ -117,7 +117,7 @@ mod vexriscv_irq_raw { mask } - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn irq_setmask(_mask: usize) {} #[cfg(all(target_arch = "riscv32", target_os = "none"))] @@ -128,7 +128,7 @@ mod vexriscv_irq_raw { asm!("csrw 0xBC0, {mask}", mask = in(reg) mask); } - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn irq_pending() -> usize { 0 } diff --git a/chips/nrf5x/src/gpio.rs b/chips/nrf5x/src/gpio.rs index 3488bad690..18a60c25e3 100644 --- a/chips/nrf5x/src/gpio.rs +++ b/chips/nrf5x/src/gpio.rs @@ -24,7 +24,7 @@ const NUM_GPIOTE: usize = 4; const NUM_GPIOTE: usize = 8; // Dummy value for testing on Travis-CI. #[cfg(all( - not(any(target_arch = "arm", target_os = "none")), + not(all(target_arch = "arm", target_os = "none")), not(feature = "nrf51"), not(feature = "nrf52"), ))] diff --git a/chips/rp2040/src/clocks.rs b/chips/rp2040/src/clocks.rs index 1ebc75e01b..6967be34ef 100644 --- a/chips/rp2040/src/clocks.rs +++ b/chips/rp2040/src/clocks.rs @@ -1059,7 +1059,7 @@ impl Clocks { } } - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] fn loop_3_cycles(&self, _clock: Clock) { unimplemented!() } diff --git a/chips/rp2040/src/lib.rs b/chips/rp2040/src/lib.rs index 30e0a3e409..7ed5a0924f 100644 --- a/chips/rp2040/src/lib.rs +++ b/chips/rp2040/src/lib.rs @@ -23,9 +23,7 @@ pub mod usb; pub mod watchdog; pub mod xosc; -use cortexm0p::{ - initialize_ram_jump_to_main, unhandled_interrupt, CortexM0P, CortexMVariant, -}; +use cortexm0p::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM0P, CortexMVariant}; extern "C" { // _estack is not really a function, but it makes the types work diff --git a/chips/stm32f4xx/src/fsmc.rs b/chips/stm32f4xx/src/fsmc.rs index 49ae8f66aa..08d98361a4 100644 --- a/chips/stm32f4xx/src/fsmc.rs +++ b/chips/stm32f4xx/src/fsmc.rs @@ -266,12 +266,12 @@ impl<'a> Fsmc<'a> { } } - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] fn write_reg(&self, _bank: FsmcBanks, _addr: u16) { unimplemented!() } - #[cfg(not(any(target_arch = "arm", target_os = "none")))] + #[cfg(not(all(target_arch = "arm", target_os = "none")))] fn write_data(&self, _bank: FsmcBanks, _data: u16) { unimplemented!() } diff --git a/doc/syscalls/README.md b/doc/syscalls/README.md index 7146d31111..72cd43fe7a 100644 --- a/doc/syscalls/README.md +++ b/doc/syscalls/README.md @@ -15,12 +15,13 @@ provided syscalls, and the driver specific interfaces (using `allow`, * [Base](#base) * [Kernel](#kernel) * [Hardware Access](#hardware-access) - * [Radio](#radio) + * [Networking](#networking) * [Cryptography](#cryptography) * [Storage](#storage) * [Sensors](#sensors) * [Sensor ICs](#sensor-ics) * [Other ICs](#other-ics) + * [Display](#display) * [Miscellaneous](#miscellaneous) @@ -47,17 +48,13 @@ stabilized or not (a "✓" indicates stability) in the Tock 2.0 release. | ✓ | 0x00001 | [Console](00001_console.md) | UART console | | ✓ | 0x00002 | [LED](00002_leds.md) | Control LEDs on board | | ✓ | 0x00003 | [Button](00003_buttons.md) | Get interrupts from buttons on the board | -| ✓ | 0x00005 | [ADC](00005_adc.md) | Sample analog-to-digital converter pins | -| | 0x00006 | DAC | Digital to analog converter | -| | 0x00007 | [AnalogComparator](00007_analog_comparator.md) | Analog Comparator | | | 0x00008 | [Low-Level Debug](00008_low_level_debug.md) | Low-level debugging tools | -| | 0x00009 | [ROS](00009_ros.md) | Read Only State, access system information | -| | 0x00010 | [PWM](00010_pwm.md) | Control PWM pins | ### Kernel |2.0| Driver Number | Driver | Description | |---|---------------|------------------|--------------------------------------------| +| | 0x00009 | [ROS](00009_ros.md) | Read Only State, access system information | | | 0x10000 | IPC | Inter-process communication | ### Hardware Access @@ -65,6 +62,10 @@ stabilized or not (a "✓" indicates stability) in the Tock 2.0 release. |2.0| Driver Number | Driver | Description | |---|---------------|------------------|--------------------------------------------| | | 0x00004 | [GPIO](00004_gpio.md) | Set and read GPIO pins | +| ✓ | 0x00005 | [ADC](00005_adc.md)| Sample analog-to-digital converter pins | +| | 0x00006 | DAC | Digital to analog converter | +| | 0x00007 | [AnalogComparator](00007_analog_comparator.md) | Analog Comparator | +| | 0x00010 | [PWM](00010_pwm.md)| Control PWM pins | | | 0x20000 | UART | UART | | | 0x20001 | SPI | Raw SPI Master interface | | | 0x20002 | SPI Slave | Raw SPI slave interface | @@ -75,7 +76,7 @@ stabilized or not (a "✓" indicates stability) in the Tock 2.0 release. _Note:_ GPIO is slated for re-numbering in Tock 2.0. -### Radio +### Networking |2.0| Driver Number | Driver | Description | |---|---------------|------------------|--------------------------------------------| @@ -107,10 +108,11 @@ _Note:_ GPIO is slated for re-numbering in Tock 2.0. | ✓ | 0x60000 | [Ambient Temp.](60000_ambient_temperature.md) | Ambient temperature (centigrate) | | ✓ | 0x60001 | [Humidity](60001_humidity.md) | Humidity Sensor (percent) | | ✓ | 0x60002 | [Luminance](60002_luminance.md) | Ambient Light Sensor (lumens) | -| | 0x60003 | Pressure | Pressure sensor | -| | 0x60004 | Ninedof | Virtualized accelerometer/magnetometer/gyroscope | -| | 0x60005 | Proximity | Proximity Sensor | -| | 0x60006 | SoundPressure | Sound Pressure Sensor | +| | 0x60003 | Pressure | Pressure sensor | +| | 0x60004 | Ninedof | Virtualized accelerometer/magnetometer/gyroscope | +| | 0x60005 | Proximity | Proximity Sensor | +| | 0x60006 | SoundPressure | Sound Pressure Sensor | +| | 0x90002 | [Touch](90002_touch.md) | Multi Touch Panel | ### Sensor ICs @@ -132,11 +134,15 @@ _Note:_ GPIO is slated for re-numbering in Tock 2.0. | | 0x80003 | GPIO Async | Asynchronous GPIO pins | | | 0x80004 | nRF51822 | nRF serialization link to nRF51822 BLE SoC | -### Miscellaneous +### Display |2.0| Driver Number | Driver | Description | |---|---------------|-----------------------------------------|--------------------------------------------| -| | 0x90000 | Buzzer | Buzzer | | | 0x90001 | [Screen](90001_screen.md) | Graphic Screen | -| | 0x90002 | [Touch](90002_touch.md) | Multi Touch Panel | | | 0x90003 | [Text Screen](90003_text_screen.md) | Text Screen | + +### Miscellaneous + +|2.0| Driver Number | Driver | Description | +|---|---------------|-----------------------------------------|--------------------------------------------| +| | 0x90000 | Buzzer | Buzzer | diff --git a/doc/wg/core/README.md b/doc/wg/core/README.md index 2095a007ce..224c9fc94f 100644 --- a/doc/wg/core/README.md +++ b/doc/wg/core/README.md @@ -26,6 +26,7 @@ are to: - Philip Levis, [phil-levis](https://github.com/phil-levis), Stanford - Amit Levy (chair), [alevy](https://github.com/alevy), Princeton University - Pat Pannuto, [ppannuto](https://github.com/ppannuto), UCSD +- Alexandru Radovici [alexandruradovici](https://github.com/alexandruradovici), Politehnica Bucharest & OxidOS - Leon Schuermann, [lschuermann](https://github.com/lschuermann), Princeton University - Johnathan Van Why, [jrvanwhy](https://github.com/jrvanwhy), Google diff --git a/doc/wg/core/notes/core-notes-2024-01-05.md b/doc/wg/core/notes/core-notes-2024-01-05.md new file mode 100644 index 0000000000..8d563976fb --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-01-05.md @@ -0,0 +1,256 @@ +# Tock Core Notes 2024-01-05 + +Attending: +- Alyssa Haroldsen +- Andrew Imwalle +- Brad Campbell +- Branden Ghena +- Johnathan Van Why +- Leon Schuermann + +# Updates + +- Branden: Two Agenda Items: + + - Amit made a post on Slack on code size in Tock. May want to talk + about it today. Although not dialed in today... + + - If Andrew wants to talk about it -- state of TicKV. + +- Brad: Playing around with my old PR on doing RSA signatures for app + signing. Trying to lay some groundwork for doing that. Things have + gotten slightly better with regards to external library + support. Rust crypto RSA library uses heap-allocated numbers. There + is currently work underway to switch out the bigint library that + this crate is using. I'm told the next iteration will allow for + fixed-sized values. + + - Branden: Timeline uncertain? + + - Brad: Yes, although a lot of progress has been made on the library + side. + +- Leon: Have been able to test compiling libtock-c's with the + precompiled newlib, seems to work. It's great to see this being + portable, even works on NixOS! + + - Brad: Its a little unclear what the order of operations is here? + We should merge and then replicate those precompiled packages to + more places. Shouldn't need to change anything, given the SHA + doesn't change. + + - Branden: Previously we were told to hold off merging. Is this + ready now? + + - Brad: Yes, it's ready! + +# TicKV Update + +- Andrew: Posted an issue a couple of weeks ago concerning + fragmentation. Alistair seems to like one of the solutions I + mentioned. Given that this is blocking for me, we'll be moving + forward to implementing it. This seems to be a good replacement for + garbage collection. Functionally, it fixes the case where you could + be told that the flash is full, when there's actually still space + left (just fragmented). + +- Brad: Good to hear that you don't feel stuck on that. + + In general, Alistair is the original creator of the library, but + it's also under the Tock umbrella. It's not finished, and I'm not + too worried about preserving the original model. If this is strictly + better, we should go ahead with it. + +- Andrew: Alistair is slightly concerned with maintaining useful + properties, such as consistency on power loss, etc. Of course want + to preserve those. + +# Conferences & Tutorials + +- Brad: Had a couple discussions about different tutorials we may want + to do. We're committed to doing one at CPS-IoTWorld. There's another + potentially interesting one more focus on hardware security. Are + there others that people might be interested in? + +- Leon: When we talk about security, I'm curious what the focus of our + tutorial should be on. E.g., I'm still working on a system for + safely executing C code in the kernel, which may be fitting (e.g., + to drive trusted hardware). But without that, what should the focus + be on? + +- Brad: Good question. We as developers are thinking about the most + recent changes. I think we should separate those concerns -- + tutorials don't need to focus on only that. We should try not to + focus on these developments, because it's hard to make them into a + tutorial. For someone who's new to Tock, everything's new -- so we + can present established tutorials like the Security Key example we + presented at TockWorld. + +- Leon: This makes sense to me. It takes a significant amout of time + and effort when using these tutorials as driving force to polish and + present on new subsystems. + +- Brad: Yes, it's hard to develop something, get it working reliably, + and teach people about it! + +- Leon: I'd be happy to see the USB security key tutorial be reused + and continued. I can only speak to the reception of it at the + company I was doing my internship, but at least there is was very + well received. It helped people understand general Tock kernel + concepts. + +- Branden: It is a lot easier to iterate on something, than making + something entirely new. We should be able to improve on our security + key tutorial much more easily. + +- Leon: Regarding a HOST'24 tutorial, I'm perhaps slightly worried + about the target audience. We're not necessarily focused on + considerations around timing side channels, etc. + +- Brad: Yes, it's such a range of topics, so it's hard to know what an + audience is actually interested in. + + Comment on doing an internal variant of the tutorial is good, it'd + be great to do some more trial runs, e.g., at universities? + +- Leon: How should we be moving forward on this? + +- Brad: There's a few of us who are motivated generally. If a specific + opportunity comes up for anyone, we're happy to support it! + + Hard part seems to be finding opportunities in the first place + (interested parties, content). We have people interested in figuring + out logistics. + +# Tock CI Architecture & Demo + +- Leon: Have been working on a prototype for a Tock CI system. This is + motivated by me requiring access to a CW310 OpenTitan devboard, of + which we only have one, and not being able to carry this with me + while traveling. + + Sketched out and implemented a prototype system that we may be able + to use more generally for Tock CI. + +- Brad: Interested to hear about this, and what pieces we could re-use + for this "cloud CI" idea that we're persuing? + +- Leon: I could do a quick demo and screenshare? + + Disclaimer: the system that I hacked together is largely motivated + by me trying out interesting technologies. Would not want to give + the impression that this is an authoritative design in any way. + + [shares screen] + + Current system components: + - Central web interface for management. Allows scheduling jobs on + multiple boards. The web interface is running on a dedicated + server in a datacenter, it is not physically connected to the + boards. + + - The boards are connected to a different computer via USB, which + runs a so-called "runner" software. You can select an + "environment" to launch on a board runner, which governs the + container environment provided to you. E.g., you can boot a given + version of Ubuntu, which has a certain set of packages installed. + + This environment is then started in a container (system-nspawn), + which provides access to only this board. Containers use a fresh, + epehmeral root file system. Other boards are inaccessible. + + The container is then accessible via SSH. + +- Brad: This looks cool. Where is the container running? + +- Leon: This is running on the physical computer (e.g., Raspberry Pi + or any other computer), connected directly via USB. + +- Brad: Basically, you end up with a setup similar to if you just + SSHed into a desktop sitting in your office, except that you can't + see it. + +- Leon: Exactly. Did think about adding a camera live stream, but not + prototyped yet. + +- Leon: Couple more technicalities: + + - This does not require any firewall exceptions / open ports. The + "runner" computer next to the board connects to the central server + and forwards all traffic through outgoing HTTPs connections + (tunneling SSH through WebSockets). + +- Brad: The local CI server basically needs: + + - some physical connection to the board + + - ability to run the containers + + - a little bit of software for management. + + Somehow you want to attach containers to a given board. How would + you configure that? + + - Leon: The runner has a configuration file, where you can specify + the exact set of devices passed through. Using udev rules to + expose devices under well-known paths. + +- Brad: The execution of a container is associated with a given user? + + - Leon: Yes. Containers run from images, which serve as + templates. Two boards can share the same image, one board can have + multiple images available. + + Idea behind this was to use it for both interactive development + and for CI by just switching out the container image. + +- Brad: Right. One model may be that we take this scheduler, and when + a new CI workflow is started assign it to a board. Perhaps none is + available, in which case we can either enqueue it, or return an + error. + + - Leon: The system does support some very primitive queueing. If you + attempt to start another job on a board that is currently in use, + the job will be started when the other one is terminated. + +- Brad: When we have a CI workload, do we want to have tests baked + into the container, or do we load them in after the fact? + +- Brad: Hopefully we can get someone to help with this. One request + that I might have: the hardware platform will be physically + distributed, and thus as pain to micro-manage / debug / etc. So it + seems tricky to get right, or just specify to avoid dealing with a + bunch of heterogeneity. + +- Leon: Agreed! One really important thing to figure out there is how + we integrate more buses and peripherals (i.e., GPIO + connections). Can be a USB GPIO expander, FPGA, or something else. + + Deployment question is an interesting one, and a hard one to get + right, but seems to be somewhat far out for now. + +- Brad: The reason why I think that it is quite important to figure + this out is that the system should really be distributed, and we + want to able to tie in downstream users and contributors with their + own platform. In theory things like OSes should provide the required + abstractions, but in practice they're not sufficient. Trying to get + this right the first time the best we can seems important. + +- Leon: Good point. Have some knowledge to go by here, e.g., + netbooting a bunch of Raspberry Pis at a student ISP distributed + over a large city. We should be able to take some inspiration from + that. + + For tying in downstream users and deployment, there are more + concerns, such as trust: are we going to manage these systems? Will + we have a point of contact with downstream deployments? ... + +- Brad: Hopefully what happens is that the value we get is so high + that we have this built-in incentive for downstream users that may + resolve some of these issues. People would be motivated to keep this + going. + + We want to have a structure in place where we tell downstream users + exactly what our expectations are (general architecture, OS, + software components, availability, ...), that would set us up for + success. diff --git a/doc/wg/core/notes/core-notes-2024-02-09.md b/doc/wg/core/notes/core-notes-2024-02-09.md new file mode 100644 index 0000000000..6acb6ff886 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-02-09.md @@ -0,0 +1,124 @@ +# Tock Meeting Notes 02/09/24 + +## Attendees + +- Branden Ghena +- Amit Levy +- Leon Schuermann +- Brad Campbell +- Alex Radovici +- Jonathan Van Why +- Alyssa Haroldson +- Tyler Potyondy +- Pat Pannuto + + +## Updates +### Display Support +* Brad: OLED screen work is in a good state in libtock-c. Support for the monochrome display is good +* Amit: Where does the display driver live? +* Brad: It's in the kernel +* Amit: And the C library just does like fonts and stuff +* Brad: Yes. The C library layers on top of the syscall interface we already had + +### Hardware CI +* Leon: Update on Tock CI development. System I present a few weeks ago, is now being used for a CW310 OpenTitan board. You can start jobs and get access to a Linux container that has access to the board. So it's a good solution for boards we don't have many of, and in the future for automatically running jobs. +* Amit: Remote, reservation-based build environment. On the path towards building a CI system +* Brad: Would CI be essentially the same as a person using it? +* Leon: The base for this can talk github API, so whenever a github workload is created we get a webhook request to the server along with which type of job. The job is scheduled with a parameter of the workflow ID. So the environment would run a github action runner in "ephemeral" mode. Then that can run the job and reports back. The only difference is that we won't have to pretend to be a user and send SSH commands. We'll start a workflow that actually just runs some tasks + +### Tockworld +* Amit: Tockworld update. Thanks to Pat we're moving forward with securing space at UCSD + + +## Mutable static references +* https://github.com/tock/tock/issues/3841 +* Alex: We have a problem with the new version of Rust. It won't compile mutable static references in the future. It's a warning right now. We get 25 warnings on the Microbit right now, which will become errors soon. +* Brad: CI is checking this warning here: https://github.com/tock/tock/pull/3842 +* Alex: Leon incidentally fixed one of these issues in the boards file, with the `ptr_address_of` change Alyssa proposed +* Leon: Yes, I still need to rebase that, but the change is mostly good. +* Alex: Deferred calls and several other places use static mut references too +* Amit: So that'll be important to address +* Alyssa: We might have to change to unsafe cell with a helper that gives a reference. Would have a .get_mut() function +* Amit: Unsafe cell has a const constructor, so that's probably fine. Just extra wrapping code +* Johnathan: `static mut` is stabilized as part of the 2021 edition. So the stable compiler can't deny that anytime soon at least. I feel like they can't just switch it off like that with Rust's stability promises +* Amit: Yeah. So it might not be urgent. +* Branden: Maybe this is real because we're on a nightly? The warning seems to promise that it will be an error +* Amit: It's an edition thing. The Rust 2024 edition will break it, even if the Rust 2021 edition allows it. +* Alyssa: Dependencies can stay on 2021, even if you're on 2024 +* Amit: We should still fix. Especially if it's as simple as Alyssa suggests +* Alyssa: You could do `&mut* ptr_address_of()` still. That overcomes the lint +* Johnathan: That's fine for now. But it would be good to move towards what they want at some point. They're trying to get us to avoid this for a reason, so we'll likely eventually have a Tock cell that wraps unsafe cell in some way. +* Alyssa: Ti50 has a version like this. It acts differently on chip than on host for testing purposes. Unsafe sell or a checked, sync cell of some type + +## Removing naked functions +* https://github.com/tock/tock/pull/3802 +* Amit: This would get us to stable, I believe +* Brad: Yes. We've handled the last few other things. So that means we can merge this to remove the last nightly feature on ARM, and the Hail board compiles on stable! +* Branden: What's the nightly feature on RISC-V? +* Brad: ASM-const for the CSR library. It's more difficult. +* Jonathan: I see a rewrite coming when we update Tock registers +* Brad: The point is, if we're going to compile 99% of our boards on nightly, because we still like our nightly features, this is just a proof-of-concept that we _can_ compile on stable and will test roughly half of our code on stable. Importantly the capsules and kernel crates +* Alyssa: I see progress towards stable as unambiguously good +* Leon: I do think this is a great idea. What has made me skittish is that while we were making these changes, we had problems with the linker placing assembly code and not being able to branch between them. So I'm worried that the linker won't be able to make some optimizations now. The benchmarks CI run doesn't show any massive memory increases (or decreases) so we're probably okay, but I want awareness +* Amit: How did we fix it? +* Leon: I think we jump to a constant now, instead of a relative jump +* Amit: This is now on the merge queue +* Branden: Why do we want to be on nightly at all? +* Brad: Two makefile-level things that boards can opt into. Compiling our own Core library with optimizations, and a testing framework. +* Leon: I think we can use macro_rules to hack around the RISC-V CSR stuff. We could have an expansion which builds the actual CSR assembly strings from a macro +* Pat: I think we had the macro version before, and left it because it was ugly. So we could certainly go back +* Brad: So overview, we could go back to that. If there's someone who's motivated to see RISC-V on stable we wouldn't preclude that. I don't personally care though, because I think one example on stable is good enough. +* Brad: Our plan for stable, is that once the Tock registers update that will affect the CSR stuff, and then we'll chip away at that feature too. +* Branden: And it is nice to have some things on nightly and some things on stable, so we're testing both +* Johnathan: We also want to test stuff with MIRI, and that'll require nightly. +* Alyssa: Unfortunately MIRI won't be stabilized until the Rust memory model is stabilized, so it's going to be a _while_. More than 3-5 years out +* Amit: Comparatively, naked functions are basically dead though + +## Process Checking +* https://github.com/tock/tock/pull/3772 +* Amit: Phil isn't here to discuss unfortunately +* Brad: I do want to handle this, but I agree we could push it + +## Libtock-C Refresh +* Amit: High-level, Libtock-C could use some love. Tyler brought part of this up +* Brad: Yeah, Tyler brought up how clumsy libtock-c is to use. It's been cobbled together at low-effort for testing kernel features. Then I realized I wasn't sure what it meant to "fix" it or how to start. So, I want to crowdsource ideas on how to improve it +* Branden: A trivial answer is documentation, which is quite lacking +* Tyler: An example, there's an assumption listed in one of the alarm files that may or may not be valid anymore. It would be great to know "what are the assumptions" about how you use APIs. For example, requirements based on the frequencies they're running at. Probably falls under documentation +* Amit: Aside from documentation, two things. First, we could consider the build infrastructure. Even with the best C tools, stuff is hard. But things like CMake are more ergonomic for relying on libraries. That could be a big deal. It's a chicken-and-egg thing where people aren't asking for this, partially because it's not easy to use right now. +* Leon: It would be great to pull libtock-c into an out-of-tree library too +* Leon: Also, we have pretty inconsistent APIs in libtock-c. I'm not sure even all kernel changes ever made it into userspace. It would be great to version individual subsystems. We basically don't have any stability guarantees for libtock-c right now +* Tyler: As part of the CI tests for libtock-c, we build things, but I don't think we actually have unit tests for anything. +* Leon: There's one very limited one. The Tock CI tests an ancient version of libtock-c for the litex board +* Amit: The second one, was that it could be worth separating the libtock-c which we currently have which is a useful proving ground and isn't particularly good for real applications from an alternative userspace. We could have a new one designed from the top down. Although there's a question for who would do that +* Brad: Where do those interfaces come from? +* Amit: There _are_ users that are building C apps. They don't currently contribute upstream. I'm not sure what their libtock-c stuff looks like or how it works. I just know that they are building applications in C for Tock. +* Amit: In general, the interface would come from applications. We might need to talk with groups who do make applications +* Amit: There is this IoT application thing now that Tyler is revisiting. C seems important in that domain. +* Amit: We could replicate some similar API from an existing system. Proton-style for example. It would be much more constrained, but that enables better documentation since there's less stuff +* Brad: So how much would be exposed and where are the pain points is the question. If a more-documented smaller API would be helpful, that's one answer +* Brad: One idea I had was just reorganizing things. Making a folder-structure here for categories of drivers seems useful +* Branden: The examples folder is a mess too. And there's a tests folder in there where it's unclear what goes where +* Pat: Could we match the syscall numbers which are in tables in the kernel by types. We could use those same types for our folders +* Brad: Yes. I'll look at that +* Brad: Does any of this seem like the thing that would move the needle Tyler? +* Tyler: I think it would help. As someone who came into the project recently, and I'm working with two undergrads on the open-thread abstractions, there are function prototypes where we're connecting OpenThread to Libtock-C. The biggest pain-points are the documentation, but specifically and worse the inconsistencies in APIs and places where the kernel has changed and we just get some generic error when making a call. +* Tyler: I'm not sure if a simpler redesign would be good. It would help. But I can't decide on restart versus repair +* Brad: I think the consistency thing is a clear issue. Part of what I would hope to do with a reorganization is separate the testing stuff from interfaces that are more preferable. It's fine to have an interface to a specific IC, but we really want a Temperature interface. So we could guide people to more general, well-made APIs +* Brad: One question, are you saying some things just don't work on Master for kernel and userspace? +* Tyler: There are syscalls that are deprecated and just do nothing in the Thread stuff for example. You do get an appropriate error back at least +* Brad: That's a bug in my mind. Someone should have "fixed" libtock-c +* Tyler: I don't know if there's a way to automate this, but it would be neat to tag things in the kernel with what they associate with in userspace, so making changes in one would flag a required change in the other. The same issue will otherwise occur in a few years even if we fix everything now, as long as nothing is checking that we keep the two in alignment. +* Leon: I don't fully agree with classifying them all as bugs. One of my conclusions is that in the kernel the development of kernel stuff that guides releases goes mostly independently of capsules. We don't use capsules to inform kernel releases often. So they change and get out-of-sync. Having some relation of which capsule API we want and whatever libtock-c currently expects would be good +* Brad: Anyone is free to stabilize a capsule syscall interface, although we do so rarely. Then userspace wouldn't have to change because it's stable +* Leon: I guess. I'm concerned that's not realistic though. For example, was the alarm stuff a part of the release? +* Brad: I think we deprecated the old stuff, but left it there. I am aggressive about not breaking userspace after we agreed on stability +* Brad: Users do expect functions to work. So some way to check that the functions work would be useful. Possibly CI testing which could block PRs for kernel or userspace. I think fixing a lot of entry-level stuff would help a lot. But then we still do need to decide on what our interfaces _should_ be. I'm not sure +* Tyler: Depends on who's using it. They might define usability differently +* Amit: Hopefully we could talk to some of the users and ask questions about their use +* Tyler: One more thought, speaking to the alarm infrastructure, there's a surprising and troubling amount of bugs in the implementation. The time-as-tick PR but also I think the queue of alarms has some issues with sorting. I'm not sure it's actually a real-world issue but it could be an edge case. So I think we should be thinking about unit tests too +* Branden: I think that's a rare case though. Most drivers just rely on the kernel syscalls to do almost all work +* Tyler: The alarm has a lot of logic. Maybe there are others with a lot of logic and we should have testing for them. It is a shame if the kernel does all this hard work, and libtock-c ruins it. +* Amit: I suspect there's not much logic, primarily because libtock-c was for testing the kernel. A redesign could have more logic though +* Tyler: So moving forward, documentation push first, and I'll help there. Then we could kick-off the process of talking to stakeholders. Would be useful + diff --git a/doc/wg/core/notes/core-notes-2024-02-16.md b/doc/wg/core/notes/core-notes-2024-02-16.md new file mode 100644 index 0000000000..3d77030785 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-02-16.md @@ -0,0 +1,96 @@ +# Tock Meeting Notes 02/16/24 + +## Attendees + +- Branden Ghena +- Hudson Ayers +- Leon Schuermann +- Jonathan Van Why +- Andrew Imwalle +- Tyler Potyondy +- Brad Campbell +- Philip Levis +- Pat Pannuto +- Alex Radovici + + +## Updates +### Certification and Unit Tests +* Alex: Started writing unit tests for Tock (in the process of certifying). We're not sure how to do this, and posted an issue looking for help/advice: https://github.com/tock/tock/issues/3854 +### Board with Display +* Brad: PR on makepython-nrf52840 board with screen drivers needs reviews. Board isn't very interesting, but the capsules matter: https://github.com/tock/tock/pull/3817 + + +## Async Process Loading +* https://github.com/tock/tock/pull/3849 +* Brad: This is a refactor on process loading/checking to make it all asynchronous. It's been tricky to handle the synchronous code that exists with the rest of the asynchronous Tock stuff, specifically you don't get errors along the way, just one error for the whole thing. So the PR spells out some details on why this refactor helps +* Brad: The major change is that we currently load anything that looks like a valid TBF into a full process object, then decide if it's valid. So we committed a bunch of resources to something that might not be credentialed. So by splitting the tasks of checking and creating, we can stop it short and skip over things that are never going to be valid. This is particularly helpful on the path towards dynamic process loading, for loading new processes at runtime +* Phil: One point you made there, is the idea that you have a process that's parsed and syntactically valid from a TBF perspective, but we haven't loaded it so we don't know if it has a proper ID. If I want to check a signature, do I just check once, or each time I want to run it? +* Brad: Just once. Between parsing the binary and creating a process standard object in the processes array. +* Phil: What happens if I have two images, 1 and 2, and both of them have TBFs checked. I want to load version 1, then later want to stop it and load version 2 into that slot. Then later want to go back to version 1. Do I have to check the signature every time? Or just once? It's probably not a show-stopper, I'm just trying to understand. +* Brad: The primitive that's in the PR right now is that once a process is in the processes array, it has a valid credential. If you modify the binary, that would not be true. Other than that, the credential should still pass. +* Phil: So, three steps, checking loading and running. The credentials are part of the loading step. So if you have a shortage of process array elements, then re-checking signatures when swapping could be an issue. Although that's very hypothetical. +* Brad: There are two ways to think about that. Yes, you could end up having to recheck. But part of the implementation adds a process binary array, and that holds the object after parsed, but before loading into a full process. +* Phil: So you'd have unloaded, but parsed, binaries. +* Brad: Yes, you could do that +* Phil: So there are 4 levels: exists, parsed, loaded, running. That makes sense +* Brad: I had hoped that we could get away without parsed, but then I don't know how to do version checking. You need to hold everything you might want to run, so you can choose the best one to run. +* Phil: Something to think about for async is what are the operations that are async. It sounds like parsing is now async. Really, what's the granularity of async operations in the new approach? +* Brad: It's really just the credentials checker. Then the entire loading process happens as an async thing. You could a bunch of deferred calls, but you don't have to. There is one though, so we start on an async and can do callbacks. So a drop-in replacement could read from an external chip. +* Phil: That makes sense to me. This is a good idea. It was something I struggled with and was trying to figure out how to make it async, but the PR was so big already +* Brad: Definitely +* Brad: The other change that falls out of this: the core kernel loop treats the processes array like it did before credentials checking. Anything in that array is totally valid to execute. So all of the checking happens before the array is populated. +* Phil: That cleans up the loop. That's very nice +* Brad: Yes. That removes overhead if you don't want to do checking +* Phil: My one comment: now that process loading/checking is complicated with a four-stage state machine. It would be good to write a document describing it, as it will be totally non-obvious. What are the states, how do they transition, etc. +* Brad: Yes, good point +* Brad: Last thing, which will maybe be in this PR. What does happen if you want to dynamically swap processes. The idea that everything in the processes array is valid would no longer be true, and we'd need some way to check for uniqueness at that point. I'm still figuring that out. +* Brad: If you did a process update while the system is running, you have a new binary and would like to stop the old and load the new. But we need to make sure the uniqueness doesn't ever get violated. +* Phil: I thought you checked that it's unique before making a new process? +* Brad: Right. That was easy, but is hard in this PR. Because the kernel no longer has the checking mechanism to do that check. No reference to a checker. +* Phil: It'll probably need a reference. Whatever handles a call to transition a process will need that. +* Brad: Right now, if you only consider the boot case, you can do this once. But if there's a way to add a new process you need to do it again. And how that should work is a bit tricky. +* Phil: It just needs a reference, right? Or something else can mark a process as "has clearance to run". Which must be something that can assert uniqueness. +* Brad: Yup. I got to this stage yesterday or so. Still considering it. +* Phil: If you don't want the main loop to have a reference to the checker, you could add a new process state about whether a process is cleared as unique. And the kernel will only start those that are cleared. +* Phil: I am happy to continue to be a sounding board for this. I'm not good at tracking the github stream though. Send me an email about it please and that'll go faster. + +## Signed Processes +* https://github.com/tock/tock/pull/3772 +* Brad: It makes sense to have a trait per hash so we can keep track? +* Phil: Not per hash. Per signature algorithm. So you know which kind was used. And those types should define the size of the data and the contents +* Brad: Doing that elegantly doesn't seem possible right now. I might have a less elegant way to do it with a rust feature. +* Phil: Is the issue having two types? +* Brad: You can have the trait, and in theory there's just a constant attached to the trait which is the size. That's a nightly feature. +* Phil: Can't you associate a type with it? +* Brad: Yes, but not a constant one + +## Libtock-C Revamp +* https://github.com/tock/libtock-c/pull/370 +* Brad: I wrote a guide about how we could arrange the libtock C library to be usable but more predictable. Looking for comments there +* Hudson: I'll look into this +* Branden: I really strongly like this. I think it's a big step forward for libtock-c +* Leon: So this is mostly the status quo, but with a synchronous namespace? +* Brad: Two other things too. Requires wrappers for low-level syscalls. Second it is very prescriptive on what those names look like. + + +## Unit tests for Tock +* https://github.com/tock/tock/issues/3854 +* Alex: We want a certified version of Tock. Needs unit tests for every single line +* Alex: But it's difficult to test free-standing functions. For example, the TBF library. When doing unit tests, we have to mock up various other functions. The only way we found to do this is configurations for testing/not testing. I'd love some thoughts on how to do this +* Alex: We want, long term, these tests to get back into Tock. I know Tock doesn't like conditional compilation +* Pat: We do have some of this already. Conditional compilation for testing was the one type we really were okay with. I think it's just in arch right now? +* Alex: We'll need it in the kernel too though. And it can't just go in the test suite, it's got to go in the main code because when we compile it for testing we have to pull in a different mocked-up crate. Something I know is giving correct or incorrect answers so I can do unit tests on a function-by-function basis. +* Hudson: Yeah, but every dependency in the kernel having a config for testing or not testing will be really ugly, right? +* Alex: That's the issue. For anything that has a trait or a generic works fine. But everything, like the kernel, that doesn't do this is not fine. We would either need to modify the kernel to take the tock TBF as a trait, which wouldn't be a code size increase at least. Or we need configure +* Hudson: Yeah, that would have it's own problem. It would add generics everywhere and explode a bit. +* Leon: Doesn't Rust support default arguments for generics? That would potentially mean we could add a limited set of generic parameters but other things wouldn't need to care? +* Alex: So we could add it to kernel resources, add a default associated type, and in testing we'd override the kernel resources? +* Leon: Yes? It's still not "nice", but at least there's an interface contract that you explicitly write out in the code. I'm still not sure this is the right solution. But it could be useful. +* Alex: We're willing to try some things and see if it works well +* Alex: Generally, we'll need to do this everywhere we use libraries. Particularly, everywhere we use Cells. +* Johnathan: That seems like a weird line. Cells seems like something that doesn't need to be stubbed. Not sure if it's a requirement. +* Alex: It's a might right now. We'll argue it's a low-level primitive, but certification may still require it +* Alex: Long-term if anyone wants to use Tock in safety-critical environments, or even IoT in EU soon, certification will be a must +* Leon: We'll take a look at Leon's suggestion though, and see how it goes + diff --git a/doc/wg/network/notes/network-notes-2024-02-19.md b/doc/wg/network/notes/network-notes-2024-02-19.md new file mode 100644 index 0000000000..4a1548a7c4 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-02-19.md @@ -0,0 +1,99 @@ +# Tock Network WG Meeting Notes + +- **Date:** February 19, 2024 +- **Participants:** + - Branden Ghena + - Tyler Potyondy + - Leon Schuermann + - Felix Mada + - Alex Radovici +- **Agenda** + 1. Updates + 2. Thread PRs + 3. Tutorial Logistics +- **References:** + - [3851](https://github.com/tock/tock/pull/3851) + - [3859](https://github.com/tock/tock/pull/3859) + - [Tock Book 26](https://github.com/tock/book/pull/26) + + +## Updates +- Tyler: We should talk logistics for CSP-IoT tutorial with Alex +- Leon: I made some pretty substantial progress on the encapsulated process framework. We had to call C functions with the right ABI/signature and translate this though our framework. At this point, I'm reasonably confident that I have a generic solution to that which can be automated elegantly. I'm still missing the last 10% or so, but it's increasingly promising now. Good fallback for Thread if we have libtock-C timing issues. + + +## Thread Status & PRs +- Tyler: State of the world, good progress on the libtock-c front. Everything compiles. Build system is pretty under control at this point. The platform abstraction layer is the last thing to tackle: radio, flash, entropy, and alarm. Alarm is mostly finished and works. Entropy is working. Radio is the most tricky and I've made some PRs for it. Flash has two UCSD undergrads working on it. I have a simple representation of Flash in RAM for now. Turns out that without the Flash working, OpenThread does some weird stuff. +- Branden: Is it weird without the Flash stuff saved in RAM? +- Tyler: It works fine with a RAM representation. The goal is to remove all "asterisk, it works except" disclaimers for now. I think that's a good goal that's achievable. Background: OpenThread needs to save channel and other parameters for it to work, and those really ought to be non-volatile so it stays in the network on reboot. +- Leon: A warning, app-signing plus writing to flash is a bad combo. It turns out writing to your own flash breaks your signature. That's using the AppFlash driver. We could avoid it altogether, or we could try to fix it. But we can't have both right now. +- Tyler: The radio stuff is getting close. From libtock-c sending is working. OpenThread's internal libraries are working too. The function calls roughly set up configuration for the network, then thread start. That starts sending parent requests. All of those are correctly encrypted. They send when they should with correct backoff. So sending is working (once the PRs get merged). Receiving is still in progress. +- Tyler: One reception concern, OpenThread wants a timestamp for when a packet was on the radio. That might require some heavy rework. But I've been looking at how the nRF boards implement this abstractions. They essentially query the time function when the receive callback is called. So we could just do that too. I think that since the ACK happens right away, other stuff is pretty lax on timing concerns. Actually, OpenThread lets you choose how much you're handling timing versus how much the board implementation is. +- Leon: This sounds very similar to the work I needed to do to get IEEE1588 working with timestamps for Ethernet. I did this as a research thing, but one of those versions just had me read the timestamp register of the chip in the radio driver and pass that up to userspace. That gave me a fairly accurate view. That's probably something we could loop through the interfaces for Thread too if necessary. The nice thing about doing it in the Ethernet MAC is that you can read the timer register without going through layers of abstraction. +- Tyler: That's useful. That could be a good abstraction. +- Branden: Or the easy take is to just plop on a timestamp in userspace when you get it. +- Tyler: Yeah, that'll be step one. I think the OpenThread library isn't as time sensitive since it's pushing stuff off to the board. +- Felix: A question, how do you push the timestamp up the stack for Ethernet? +- Leon: In the upcall path, we have an argument, a 128-bit integer that's wrapped in some type about where it was gathered. That's even too complicated for this case, maybe. We might literally just include an integer in the upcall in the interface. +- Felix: Linux or FreeBSD does something similar. But the push the timestamp through the error stack in a weird way. +- Leon: I did look at the Linux implementation. I couldn't figure out what they were doing even after a close look. They have error handlers on file descriptors to push stuff into userspace because of API restrictions, I think. Tock is much simpler, since we can just change interfaces. +- Tyler: Another thing, OpenThread can do encryption in software in userspace (mbedtls), or it can push it down to the board hardware to do. This is a question for everyone, but my gut feeling is that although I've used our crypto implementation for AES128-CCM and I'm pretty confident it's working, I'm even more confident that the mbedtls crypto will be better than ours. So I'm in favor of having OpenThread do all of the packet creation and encryption, and then just sending the fully-formed packet down to userspace. +- Leon: My thoughts, I just spent a lot of time on system call overheads. We do have high overheads for the round-trips for using crypto. Even in the kernel we do pretty expensive dispatch to drivers/hardware. And we have to copy userspace buffers into kernel buffers for DMA. So the question is a tradeoff of the code size for userland apps and the timing overhead of encrypting the payload. Because, I think, Thread only encrypts management traffic which is pretty small. I think it's probably better to just stay in userspace. +- Tyler: Encryption is called as part of sending. So it's just one syscall, but the capsules would connect to encryption mechanisms. So when you send you tell it what encryption you want. +- Leon: I didn't realize that. So encryption is on the path to the radio +- Tyler: The packet will be fully encrypted. Code size is the biggest tradeoff? +- Branden: Probably a timing tradeoff as well. It might not matter in the end. If we just make a decision and the timing works, then great! +- Tyler: The fewer things Tock needs to do, the better (in terms of framing, forming packet, etc.). OpenThread does expect to form and frame the packets. I don't know if there's a way to disable the MLE encryption, I think it always does that itself. It's the link-layer encryption that you could handle yourself or have it do. +- Tyler: https://github.com/tock/tock/pull/3851 +- Tyler: current way you send 15.4 packet is specifying: + - payload + - dest addr + - security configuration + Capsule takes this, kicks off sending. If we want to implement encryption we need to decompose the constructed packet, pull the security config out, encrypting, etc. So what this PR does is provides a way for there to be a "raw/direct" send functionality. So there's another path through the 15.4 capsule that just sends the packet as-is without changing anything. +- Tyler: It would be useful for people to take a look at the PR and give any comments. +- Leon: I think this is great. It's always useful to keep these raw interfaces around, even after better things exist, for testing and whatnot. Ethernet has a raw interface like this. +- Tyler: Other PRs (libtock-c and tock getProcessBuffer API documentation) related to this. +- Branden: Thank you for opening the latter PR. Even if it's not incorrect, there's still a misconception and we should improve on the documentation. +- Tyler: Yeah, I'm always a little hesitant to open things like that to see if I just misunderstand. I think it would make sense to add some comments about the guarantees here. +- Leon: Yeah, you're spot on. We should definitely add this to the documentation. +- Tyler: Seemed weird that you could get a buffer that you have never shared. +- Branden: Are these the only PRs that we need to pay attention to right now? Brad's PR removing `dyn` is open too https://github.com/tock/tock/pull/3859 +- Leon: It really shouldn't change anything about composition of capsules. As long as you don't care about swapping out interfaces at runtime, this is a net improvement. +- Tyler: Other major TODO: switching channels. We don't have this implemented. Wrote a draft in September, still need to finish it. What I'm thinking current is that the current method for setting channel is a constant in the main.rs file for Channel Number. That's passed in as the 15.4 driver is created and can't be changed. Adding the ability to change channels isn't that hard, what's harder is controlling who can change the channel and when. For example, if you're listening for 15.4 packets you sure expect the channel not to change. +- Tyler: My idea: trivial kind of "lock" on the channel (e.g., through TakeCell). You'd say "I now have control over changing channels", and then other apps are prevented from that. +- Branden: Channel control would be a mechanism that you grab in userspace, at init-time. +- Tyler: Not necessarily. I think I would have Thread grab at start and just never release for now. I think there's a way you could grab and release when you want. +- Leon: This is exactly what we do for current non-virtualized capsules. Be careful to make sure this still works if an app has died. The "lock" needs to be freed +- Tyler: Can you share an example of this with me so I can follow it? +- Leon: Yes +- Leon: Example of a non-virtualized capsule that allows a process to take ownership: https://github.com/tock/tock/blob/906bb4fb237531d3eb21b86857bf30e5d5340743/capsules/extra/src/lps25hb.rs#L346 +- Branden: I think your initial design sounds right for now. Long-term a virtualizer would base the decision about channel changing on what the state of the radio is (no changing during reception). And you could have multiple transmitters changing channels, as long as they have some way of specifying which channel, with the radio changing back and forth. +- Tyler: We'll have to think about the actual radio driver implementation. Even with no one requesting it, it stays in the receiving state. +- Branden: You'd need to implement a channel search mechanism to find a network on startup. +- Tyler: OpenTread does that -- just need to give it the ability to set the channel. Been pleasantly surprised with how little it assumes you'd do yourself. For example, the alarm never sets multiple alarms at once and virtualises multiple alarms for you. It really does expect to have bare on-metal functions that set registers and have no functionality. +- Branden: Believe OpenThread to be a fairly high-quality artifact, and this confirms that. + + +## Tutorial Logistics +- Leon: How much time will we have for the tutorial? +- Tyler: Half-day tutorial session. Unclear how long that is (3-5 hours?) +- Leon: Okay, that'll have a big impact on whether we do two things or one +- Tyler: We should definitely consider what our tutorial plan is and make a vague plan. +- Tyler: For Alex, we wanted to touch base on students coming to the tutorial session, and see if things are coming along. +- Alex: Still working through paperwork. TockWorld is easy and has five people coming. The tutorial has more work in progress. +- Leon: One of Amit's other students is starting to use Tock and hacking on kernel things. He may also join the tutorial session in Hong Kong. +- Alex: So the next step is having a meeting of everyone to start working on Thread. We'll need to get some boards on hand to play with it. +- Tyler: Need the nRF52840DK for that. Several of them to make a network +- Alex: Send me information on what to buy +- Tyler: Right now, my energy is just on the libtock-c port of OpenThread. Don't want to do too much of the tutorial until something works. The next step is to come up with a draft of the tutorial plan. I'll try to have a rough sketch of it for the next Network WG call. +- Tyler: In the Tock Book repo, there's an open PR with some discussion https://github.com/tock/book/pull/26 to look through for now +- Tyler: Once we have a plan, we can divide up tasks on writing and testing and whatnot. Ball needs to start rolling on that soon, which requires some starting on my end +- Tyler: It'd be good to get materials together early on, such that participants can download ahead of time. +- Leon: Small update on my side. I heard from a undergrad working on the existing book tutorials and was going to discuss with them some issues they had. They'll be around to test out Thread stuff too +- Branden: I'm also teaching a wireless class this spring and will have people on-hand who know how Thread works but not how Tock works. So they could test too if needed. +- Leon: Will ask Amit to buy some more nRFs. We should make sure that even if something happens, we should be able to support at least most of the people with boards. +- Alex: Any import restrictions in Hong Kong? Concerns about how long that could take if they're concerned. You could be stuck there indefinitely if they get concerned. +- Branden: Should be an action item: talk to people who have done something like this? -> Tyler, Leon(?) +- Alex: We should try to ship them there or buy them there directly. Maybe with the conference organizers. +- Tyler: Reach out to organizers? +- Branden: Yes! If they know of solutions, problem solved! diff --git a/kernel/src/hil/public_key_crypto/mod.rs b/kernel/src/hil/public_key_crypto/mod.rs index 66b074ffc9..f9c86d537a 100644 --- a/kernel/src/hil/public_key_crypto/mod.rs +++ b/kernel/src/hil/public_key_crypto/mod.rs @@ -6,3 +6,4 @@ pub mod keys; pub mod rsa_math; +pub mod signature; diff --git a/kernel/src/hil/public_key_crypto/signature.rs b/kernel/src/hil/public_key_crypto/signature.rs new file mode 100644 index 0000000000..2a2f6c4b79 --- /dev/null +++ b/kernel/src/hil/public_key_crypto/signature.rs @@ -0,0 +1,59 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Interface for verifying signatures. + +use crate::ErrorCode; + +/// This trait provides callbacks for when the verification has completed. +pub trait ClientVerify { + /// Called when the verification is complete. + /// + /// If the verification operation encounters an error, result will be a + /// `Result::Err()` specifying the ErrorCode. Otherwise, result will be a + /// `Result::Ok` set to `Ok(true)` if the signature was correctly verified + /// and `Ok(false)` otherwise. + /// + /// If verification operation did encounter errors `result` will be `Err()` + /// with an appropriate `ErrorCode`. Valid `ErrorCode`s include: + /// + /// - `CANCEL`: the operation was cancelled. + /// - `FAIL`: an internal failure. + fn verification_done( + &self, + result: Result, + hash: &'static mut [u8; HL], + signature: &'static mut [u8; SL], + ); +} + +/// Verify a signature. +/// +/// This is a generic interface, and it is up to the implementation as to the +/// signature verification algorithm being used. +/// +/// - `HL`: The length in bytes of the hash. +/// - `SL`: The length in bytes of the signature. +pub trait SignatureVerify<'a, const HL: usize, const SL: usize> { + /// Set the client instance which will receive the `verification_done()` + /// callback. + fn set_verify_client(&self, client: &'a dyn ClientVerify); + + /// Verify the signature matches the given hash. + /// + /// If this returns `Ok(())`, then the `verification_done()` callback will + /// be called. If this returns `Err()`, no callback will be called. + /// + /// The valid `ErrorCode`s that can occur are: + /// + /// - `OFF`: the underlying digest engine is powered down and cannot be + /// used. + /// - `BUSY`: there is an outstanding operation already in process, and the + /// verification engine cannot accept another request. + fn verify( + &self, + hash: &'static mut [u8; HL], + signature: &'static mut [u8; SL], + ) -> Result<(), (ErrorCode, &'static mut [u8; HL], &'static mut [u8; SL])>; +} diff --git a/kernel/src/process_checker.rs b/kernel/src/process_checker.rs index e52a7aae9a..a3cebdc6e1 100644 --- a/kernel/src/process_checker.rs +++ b/kernel/src/process_checker.rs @@ -8,6 +8,7 @@ //! See the [AppID TRD](../../doc/reference/trd-appid.md). pub mod basic; +pub mod signature; use crate::config; use crate::debug; diff --git a/kernel/src/process_checker/signature.rs b/kernel/src/process_checker/signature.rs new file mode 100644 index 0000000000..55225fae5b --- /dev/null +++ b/kernel/src/process_checker/signature.rs @@ -0,0 +1,302 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Signature credential checker for checking process credentials. + +use crate::hil; +use crate::process::{Process, ShortID}; +use crate::process_checker::{AppCredentialsChecker, AppUniqueness}; +use crate::process_checker::{CheckResult, Client, Compress}; +use crate::utilities::cells::MapCell; +use crate::utilities::cells::OptionalCell; +use crate::utilities::leasable_buffer::{SubSlice, SubSliceMut}; +use crate::ErrorCode; +use tock_tbf::types::TbfFooterV2Credentials; +use tock_tbf::types::TbfFooterV2CredentialsType; + +/// Checker that validates a correct signature credential. +/// +/// This checker provides the scaffolding on top of a hasher (`&H`) and a +/// verifier (`&S`) for a given `TbfFooterV2CredentialsType`. +/// +/// This assumes the `TbfFooterV2CredentialsType` data format only contains the +/// signature (i.e. the data length of the credential in the TBF footer is the +/// same as `SL`). +pub struct AppCheckerSignature< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, +> { + hasher: &'a H, + verifier: &'a S, + hash: MapCell<&'static mut [u8; HL]>, + signature: MapCell<&'static mut [u8; SL]>, + client: OptionalCell<&'static dyn Client<'static>>, + credential_type: TbfFooterV2CredentialsType, + credentials: OptionalCell, + binary: OptionalCell<&'static [u8]>, +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > AppCheckerSignature<'a, S, H, HL, SL> +{ + pub fn new( + hasher: &'a H, + verifier: &'a S, + hash_buffer: &'static mut [u8; HL], + signature_buffer: &'static mut [u8; SL], + credential_type: TbfFooterV2CredentialsType, + ) -> AppCheckerSignature<'a, S, H, HL, SL> { + Self { + hasher, + verifier, + hash: MapCell::new(hash_buffer), + signature: MapCell::new(signature_buffer), + client: OptionalCell::empty(), + credential_type, + credentials: OptionalCell::empty(), + binary: OptionalCell::empty(), + } + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::digest::ClientData for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSliceMut<'static, u8>) {} + + fn add_data_done(&self, result: Result<(), ErrorCode>, data: SubSlice<'static, u8>) { + self.binary.set(data.take()); + + // We added the binary data to the hasher, now we can compute the hash. + match result { + Err(e) => { + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + Ok(()) => { + self.hash.take().map(|h| match self.hasher.run(h) { + Err((e, _)) => { + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + Ok(()) => {} + }); + } + } + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::digest::ClientHash for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; HL]) { + match result { + Err(e) => { + self.hash.replace(digest); + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + Ok(()) => match self.signature.take() { + Some(sig) => match self.verifier.verify(digest, sig) { + Err((e, d, s)) => { + self.hash.replace(d); + self.signature.replace(s); + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + Ok(()) => {} + }, + None => { + self.hash.replace(digest); + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(ErrorCode::FAIL), cred, binary) + }); + } + }, + } + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::digest::ClientVerify for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn verification_done(&self, _result: Result, _compare: &'static mut [u8; HL]) { + // Unused for this checker. + // Needed to make the sha256 client work. + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::public_key_crypto::signature::ClientVerify + for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn verification_done( + &self, + result: Result, + hash: &'static mut [u8; HL], + signature: &'static mut [u8; SL], + ) { + self.hash.replace(hash); + self.signature.replace(signature); + + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + let check_result = if result.unwrap_or(false) { + Ok(CheckResult::Accept) + } else { + Ok(CheckResult::Pass) + }; + + c.check_done(check_result, cred, binary) + }); + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > AppCredentialsChecker<'static> for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn require_credentials(&self) -> bool { + true + } + + fn check_credentials( + &self, + credentials: TbfFooterV2Credentials, + binary: &'static [u8], + ) -> Result<(), (ErrorCode, TbfFooterV2Credentials, &'static [u8])> { + self.credentials.set(credentials); + + if credentials.format() == self.credential_type { + // Save the signature we are trying to compare with. + self.signature.map(|b| { + b.as_mut_slice()[..SL].copy_from_slice(&credentials.data()[..SL]); + }); + + // Add the process binary to compute the hash. + self.hasher.clear_data(); + match self.hasher.add_data(SubSlice::new(binary)) { + Ok(()) => Ok(()), + Err((e, b)) => Err((e, credentials, b.take())), + } + } else { + Err((ErrorCode::NOSUPPORT, credentials, binary)) + } + } + + fn set_client(&self, client: &'static dyn Client<'static>) { + self.client.replace(client); + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > AppUniqueness for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn different_identifier(&self, process_a: &dyn Process, process_b: &dyn Process) -> bool { + let cred_a = process_a.get_credentials(); + let cred_b = process_b.get_credentials(); + + // If it doesn't have credentials, it is by definition + // different. It should not be runnable (this checker requires + // credentials), but if this returned false it could block + // runnable processes from running. + cred_a.map_or(true, |a| { + cred_b.map_or(true, |b| { + // Two IDs are different if they have a different format, + // different length (should not happen, but worth checking for + // the next test), or any byte of them differs. + if a.format() != b.format() { + true + } else if a.data().len() != b.data().len() { + true + } else { + for (aval, bval) in a.data().iter().zip(b.data().iter()) { + if aval != bval { + return true; + } + } + false + } + }) + }) + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > Compress for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn to_short_id(&self, _process: &dyn Process, credentials: &TbfFooterV2Credentials) -> ShortID { + let data = credentials.data(); + if data.len() < 4 { + // Should never trigger, as we only approve signature credentials. + return ShortID::LocallyUnique; + } + let id: u32 = 0x8000000_u32 + | (data[0] as u32) << 24 + | (data[1] as u32) << 16 + | (data[2] as u32) << 8 + | (data[3] as u32); + match core::num::NonZeroU32::new(id) { + Some(nzid) => ShortID::Fixed(nzid), + None => ShortID::LocallyUnique, // Should never be generated + } + } +} diff --git a/kernel/src/process_printer.rs b/kernel/src/process_printer.rs index 6211a5af58..a9478c3612 100644 --- a/kernel/src/process_printer.rs +++ b/kernel/src/process_printer.rs @@ -145,7 +145,7 @@ impl ProcessPrinter for ProcessPrinterText { let _ = match process.get_completion_code() { Some(opt_cc) => match opt_cc { - Some(cc) => bww.write_fmt(format_args!(" Completion Code: {}\r\n", cc)), + Some(cc) => bww.write_fmt(format_args!(" Completion Code: {}\r\n", cc as isize)), None => bww.write_str(" Completion Code: Faulted\r\n"), }, None => bww.write_str(" Completion Code: None\r\n"), diff --git a/libraries/riscv-csr/src/csr.rs b/libraries/riscv-csr/src/csr.rs index b050f817b4..3b583c7f24 100644 --- a/libraries/riscv-csr/src/csr.rs +++ b/libraries/riscv-csr/src/csr.rs @@ -171,7 +171,10 @@ impl ReadWriteRiscvCsr { /// instruction where `rs1 = in(reg) value_to_set` and `rd = /// out(reg) `. // Mock implementations for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] + #[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" + )))] pub fn atomic_replace(&self, _value_to_set: usize) -> usize { unimplemented!("RISC-V CSR {} Atomic Read/Write", V) } @@ -204,7 +207,10 @@ impl ReadWriteRiscvCsr { /// instruction where `rs1 = in(reg) bitmask` and `rd = out(reg) /// `. // Mock implementations for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] + #[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" + )))] pub fn read_and_set_bits(&self, bitmask: usize) -> usize { unimplemented!( "RISC-V CSR {} Atomic Read and Set Bits, bitmask {:04x}", @@ -241,7 +247,10 @@ impl ReadWriteRiscvCsr { /// instruction where `rs1 = in(reg) bitmask` and `rd = out(reg) /// `. // Mock implementations for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] + #[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" + )))] pub fn read_and_clear_bits(&self, bitmask: usize) -> usize { unimplemented!( "RISC-V CSR {} Atomic Read and Clear Bits, bitmask {:04x}", @@ -294,7 +303,10 @@ impl Readable for ReadWriteRiscvCsr usize { unimplemented!("reading RISC-V CSR {}", V) } @@ -316,7 +328,10 @@ impl Writeable for ReadWriteRiscvCsr Parser<'cache> { let syntax = match cache.syntax_set.find_syntax_for_file(path) { Err(error) if error.kind() == ErrorKind::InvalidData => return Err(ParseError::Binary), Err(error) => return Err(error.into()), - Ok(Some(syntax)) => syntax, - Ok(None) => cache.syntax_set.find_syntax_by_name(FALLBACK_NAME).unwrap(), + Ok(Some(syntax)) if syntax.name != cache.plain_text_name => syntax, + Ok(_) => cache.syntax_set.find_syntax_by_name(FALLBACK_NAME).unwrap(), }; Ok(Self { @@ -341,7 +349,7 @@ mod tests { } #[test] - fn plain_text() { + fn plain_text_no_extension() { const EXPECTED: &[LineContents] = &[ Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), @@ -351,7 +359,23 @@ mod tests { Comment("This is a plain text file; the license checker should recognize that it does"), Comment("# not use a common comment syntax."), ]; - let path = Path::new("testdata/plain_text"); + let path = Path::new("testdata/plain_text_no_extension"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + #[test] + fn plain_text_txt() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2024."), + Comment("Copyright Google LLC 2024."), + Comment(""), + Comment("This is a plain text file with the .txt extension. The license checker"), + Comment("should use the fallback parser, even though syntect may recognize it as a"), + Comment("plain text file."), + ]; + let path = Path::new("testdata/plain_text.txt"); assert_produces(Parser::new(&Cache::default(), path), EXPECTED); } diff --git a/tools/license-checker/testdata/plain_text.txt b/tools/license-checker/testdata/plain_text.txt new file mode 100644 index 0000000000..12561e8d46 --- /dev/null +++ b/tools/license-checker/testdata/plain_text.txt @@ -0,0 +1,8 @@ +Licensed under the Apache License, Version 2.0 or the MIT License. +SPDX-License-Identifier: Apache-2.0 OR MIT +Copyright Tock Contributors 2024. +Copyright Google LLC 2024. + +This is a plain text file with the .txt extension. The license checker +should use the fallback parser, even though syntect may recognize it as a +plain text file. diff --git a/tools/license-checker/testdata/plain_text b/tools/license-checker/testdata/plain_text_no_extension similarity index 100% rename from tools/license-checker/testdata/plain_text rename to tools/license-checker/testdata/plain_text_no_extension diff --git a/tools/tockbot/maint_nightly.yaml b/tools/tockbot/maint_nightly.yaml new file mode 100644 index 0000000000..4a7da79b55 --- /dev/null +++ b/tools/tockbot/maint_nightly.yaml @@ -0,0 +1,54 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +_anchors: + core_wg_users: &core_wg_users + - hudson-ayers + - bradjc + - brghena + - phil-levis + - alevy + - ppannuto + - lschuermann + - jrvanwhy + + active_reviewers: &active_reviewers + - alexandruradovici + - hudson-ayers + - bradjc + - brghena + - alevy + - lschuermann + - jrvanwhy + +repo: + owner: tock + name: tock + +# Ignore all PRs and issues that have the tockbot-ignore label: +ignored_labels: + - tockbot-ignore + +tasks: + - type: stale_pr_assign + label: Assign Active Reviewers to Stale PRs + + # For how long the PR must have not received any comments: + staleness_time: 259200 # 60 * 60 * 24 * 3 days + + # Any such PRs must not already have a review by a core team + # member (i.e., have been triaged) + no_reviews_by: *core_wg_users + + # Assign one active reviewer at random: + assignee_cnt: 1 + assignee_candidates: *active_reviewers + + # Ignore PRs that are marked as "blocked" or "blocked-upstream". Those + # should not necessarily be assigned a reviewer, but be staged for + # "check-in" in a core-WG call after being stale for some time (e.g., a + # month). + ignored_labels: + - blocked + - blocked-upstream diff --git a/tools/tockbot/requirements.txt b/tools/tockbot/requirements.txt new file mode 100644 index 0000000000..402cfe8907 --- /dev/null +++ b/tools/tockbot/requirements.txt @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +attrs==23.2.0 +cattrs==23.2.3 +certifi==2023.11.17 +cffi==1.16.0 +charset-normalizer==3.3.2 +cryptography==42.0.4 +Deprecated==1.2.14 +idna==3.6 +platformdirs==4.2.0 +pycparser==2.21 +PyGithub==2.1.1 +PyJWT==2.8.0 +PyNaCl==1.5.0 +python-dateutil==2.8.2 +PyYAML==6.0.1 +requests==2.31.0 +requests-cache==1.2.0 +six==1.16.0 +typing_extensions==4.9.0 +url-normalize==1.4.3 +urllib3==2.1.0 +wrapt==1.16.0 diff --git a/tools/tockbot/shell.nix b/tools/tockbot/shell.nix new file mode 100644 index 0000000000..f4c4cafa8a --- /dev/null +++ b/tools/tockbot/shell.nix @@ -0,0 +1,15 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +with import {}; + +mkShell { + name = "mirrorcheck-shell"; + buildInputs = [ + (python3.withPackages (pypkgs: with pypkgs; [ + requests-cache pygithub pyyaml + ])) + ]; +} + diff --git a/tools/tockbot/tockbot.py b/tools/tockbot/tockbot.py new file mode 100755 index 0000000000..169f2664c4 --- /dev/null +++ b/tools/tockbot/tockbot.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +import os, sys +import random +import argparse +import logging +from datetime import datetime, timedelta, timezone +import yaml +from github import Github, Auth + +# Cache GitHub API requests aggressively: +from requests_cache import NEVER_EXPIRE, DO_NOT_CACHE, get_cache, install_cache +install_cache( + cache_control=True, + urls_expire_after={ + '*.github.com': NEVER_EXPIRE, + '*': DO_NOT_CACHE, + }, +) + +class CallbackFilter: + def __init__(self, function, filtered_cb, sequence): + self.function = function + self.sequence = sequence + self.filtered_cb = filtered_cb + + def __iter__(self): + return self + + def __next__(self): + # Let any StopIteration exception bubble up the call stack + while True: + item = next(self.sequence) + if self.function(item): + return item + else: + self.filtered_cb(item) + +def ignore_prs_filter(config, task_config, prs, logger): + filtered = prs + + # Build a chain of filters over each of the labels: + for ignored_label in ( + config.get("ignored_labels", []) + + task_config.get("ignored_labels", []) + ): + filtered = CallbackFilter( + lambda pr: not any(map( + lambda l: l.name == ignored_label, + pr.get_labels() + )), + lambda ignored: logger.debug( + f"-> Filtered #{filtered.number}, is ignored by label " + + f"\"{ignored_label}\"." + ), + filtered + ) + + return filtered + +def verbose_pr_stream(prs, log): + def verbose_pr_stream_log(pr, log): + log.debug(f"Processing PR #{pr.number} (\"{pr.title}\")") + return pr + return map(lambda pr: verbose_pr_stream_log(pr, log), prs) + +# Assign maintainers to stale PRs when they haven't seen any review / +# reviewer activity after a given amount of time: +def task_stale_pr_assign(config, task_config, gh, repo, rand, log, dry_run): + # Get the list of open PRs: + prs = verbose_pr_stream(repo.get_pulls(state="open"), log) + + # Ignore all draft PRs: + prs = CallbackFilter( + lambda pr: pr.draft == False, + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, is a draft PR."), + prs, + ) + + # Filter out PRs that are marked as ignored by this tool: + prs = ignore_prs_filter(config, task_config, prs, log) + + # Filter out PRs that are assigned to one or more users: + prs = CallbackFilter( + lambda pr: len(pr.assignees) == 0, + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, has assignees."), + prs, + ) + + # Filter out PRs which have received reviews that are not dismissed + # (optionally filted by a designated group of people, if the config is not + # an empty list): + no_reviews_cond = task_config.get("no_reviews_by", None) + if no_reviews_cond is not None: + prs = CallbackFilter( + lambda pr: not any(map( + lambda review: ( + # Only keep PRs that do not have any review where the + # reviewer is in the `no_reviews_cond` list, ... + review.user.login in no_reviews_cond \ + # ... not counting dismissed reviews: + and review.state != "DISMISSED" \ + # ... and not comment reviews (won't be dismissed): + and review.state != "COMMENTED" + ), + pr.get_reviews(), + )), + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, has current reviews."), + prs + ) + + # Filter our PRs that have seen a comment be updated in the last + # task_config["staleness_time"] seconds: + if task_config.get("staleness_time", None) is not None: + comments_since = datetime.now(timezone.utc) \ + - timedelta(seconds=task_config["staleness_time"]) + + prs = CallbackFilter( + lambda pr: ( + ( + # Keep PRs that do _not_ have at least one review comment or + # at least one issue comment since `comments_since`, + pr.get_review_comments(since=comments_since).totalCount == 0 and \ + pr.as_issue().get_comments(since=comments_since).totalCount == 0 + ) and ( + # ... except if the PR is less than `staleness_time` old: + pr.created_at < comments_since + ) + ), + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, not stale."), + prs + ) + + # Now, add an assignee to all remaining PRs randomly: + assignee_cnt = task_config.get("assignee_cnt", 1) + for pr in prs: + assignees = list(map( + lambda login: gh.get_user(login), + rand.sample( + list(filter( + # Avoid assigning the PR creator: + lambda login: pr.user.login != login, + task_config["assignee_candidates"])), + assignee_cnt + ) + )) + + log.info(( + "Would assign user(s) {} to PR #{} (\"{}\")" + if dry_run else + "Assigning user(s) {} to PR #{} (\"{}\")" + ).format( + ", ".join(map(lambda a: a.login, assignees)), + pr.number, + pr.title, + )) + + if not dry_run: + pr.add_to_assignees(*assignees) + + +def cmd_maint_nightly(config, log, dry_run, gh_token = None): + rand = random.SystemRandom() + + # Instantiate the GitHub client library: + if gh_token is not None: + auth_args = { "auth": Auth.Token(gh_token) } + else: + log.warning("Running without GitHub auth token.") + auth_args = {} + + gh = Github(**auth_args) + + repo = gh.get_repo("{}/{}".format( + config["repo"]["owner"], + config["repo"]["name"])) + + # Perform the various maintenance tasks + task_handlers = { + "stale_pr_assign": task_stale_pr_assign, + } + + for task in config["tasks"]: + if task["type"] not in task_handlers: + log.error("Unknown task type \"{}\", skipping!".format(task["type"])) + continue + + log.info("Running task \"{}\" (type \"{}\")...".format( + task.get("label", ""), task["type"])) + log.debug(f"Starting task with rate limits: {str(gh.get_rate_limit())}") + + handler = task_handlers[task["type"]] + handler( + config = config, + task_config = task, + gh = gh, + repo = repo, + rand = rand, + log = log, + dry_run = dry_run, + ) + + log.debug(f"Finished all tasks with rate limits: {str(gh.get_rate_limit())}") + +def main(): + parser = argparse.ArgumentParser(prog = "tockbot") + + # Global options: + parser.add_argument("-n", "--dry-run", action="store_true") + parser.add_argument("-v", "--verbose", action="store_true") + + # Subcommands: + subparsers = parser.add_subparsers(dest="subcommand", required=True) + + # Nightly project maintenance command: + maint_nightly_parser = subparsers.add_parser("maint-nightly") + maint_nightly_parser.add_argument( + "-c", "--config", required=True, + help="YAML configuration for nightly maintenance job") + + args = parser.parse_args() + + # Initialize the logging facility: + ch = logging.StreamHandler() + fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(fmt) + log = logging.getLogger('tockbot') + log.addHandler(ch) + if args.verbose: + log.setLevel(logging.DEBUG) + else: + log.setLevel(logging.INFO) + + # Load the YAML configuration for commands that require it: + if args.subcommand in ["maint-nightly"]: + with open(args.config, "r") as f: + config = yaml.safe_load(f) + + # Check if we're being passed a GitHub access token in an environment var: + gh_token = os.environ.get("GITHUB_TOKEN", None) + gh_token = gh_token if gh_token != "" else None + + if args.subcommand == "maint-nightly": + return cmd_maint_nightly( + config, log, dry_run=args.dry_run, gh_token=gh_token) + else: + log.critical(f"Unhandled subcommand: {args.subcommand}") + return 1 + +if __name__ == "__main__": + sys.exit(main())