tock / tock Goto Github PK
View Code? Open in Web Editor NEWA secure embedded operating system for microcontrollers
Home Page: https://www.tockos.org
License: Other
A secure embedded operating system for microcontrollers
Home Page: https://www.tockos.org
License: Other
In src/platform/storm/lib.rs, on line 195, ESCL is assigned to PB02. According to the ATSAM4L datasheet, this should actually be at PB01 (the peripheral function is TWIMS1 TCLK).
This issue would make the external I2C (ESCL/ESDA) appear to be broken. The I2C bus connected to the Firestorm sensors is unaffected.
Changing line 195 to PB[01] should be the only necessary change.
Need to actually enforce memory protection to allow applications access to their own memory and code, but nothing else (including memory lent to the kernel).
Application memory is already laid out to support protection. The application code section lives in one of the .app.N
sections. The application memory lives in a discreet element of process::MEMORIES
and process::Process#exposed_memory_start
notes the location memory (within the bounds of the application's memory) where exposed memory begins.
The sam4l
supports the ARM MPU extension, allowing up to 8 potentially overlapping regions, and up to 8 sub-regions (depending on the size of a region).
Isolating applications with the MPU should be simply defined in three regions:
The NRF51 MPU works differently. There only two memory regions, and two code regions with pre-defined access control.
Need to investigate how to best use this mechanism to isolate an app. Luckily, we likely only want to fit a single app on the NRF51 anyway.
When a hardware interrupt comes in, switch immediately to the kernel instead of return to user-space, prioritizing all kernel code over user-space.
Add a document explaining the directory structure:
Any thoughts on how we should manage Tock with new drivers and new platforms? Is there a way I can specify my own main.rs
(based on the SAM4L still) for my own platform that has a certain set of sensors and build it against the main Tock repo?
If you pass 1ms to the timer interface, you miss callbacks. This likely has to do with the possibility that the counter is read, then the compare is set to read value +1. If the counter incremented between that read and set, no interrupt will be issued.
Two typical solutions (you generally want both): have the underlying counter at a higher precision (so a timer of 1 is, say, 1000 counter increments), and also check whether the counter has passed the compare value before returning. I.e., set the prescalar in ast.rs so it's higher than 1kHz (e.g., 32kHz), then have the system call interface translate ms to ticks of a 32kHz clock).
Currently it is a internal struct in src/chips/nrf51822/gpio.rs.
It could be used in places where volatile_load/store is required, reducing scope of unsafe blocks for register access.
Original discussion: #17 (diff)
From the original discussion:
"Jinja2 templates are a good way to decouple the model from the "view" (i.e. generated Rust/C code). In fact, that reminded me Yasha (https://github.com/kblomqvist/yasha) already supports SVD models through CMSIS-SVD! There is one example for nRF51 that generates zinc ioreg! macros: https://github.com/kblomqvist/nrf51.rs/blob/master/nrf51.rs.jinja."
It would be great to be able to restart apps if they crash (or upon demand if the kernel detects they aren't working).
An extra fancy option might be to add the ability to configure the board in one of three modes:
stop
: What we do now, where any hardfault prints the registers and stops.print_and_reboot
: Print the hardfault error and then restart the MCU.reboot
: Immediately restart the MCU on any failure.The current build system is pretty cobbled together and is pretty hard to understand (and therefore change). We would like the system to allow at least:
Inspired by https://github.com/helena-project/tock/pull/102/files#diff-6c77447f4906f04d927a4237b1655082L46
Does error as a parameter encourage implementers to simply ignore / not check error? Would we possibly better with separate command_complete
and command_error
traits? Sure, people could implement empty error functions, but that seems harder to mess up than forgetting / not bothering to check the error value.
With the current implementation of wait
and wait_for
, an app that mixes synchronous and asynchronous calls can easily run into trouble.
The high-level problem is that a call to a synchronous function (such as putstr
) might be interleaved with a callback for that calls the same function. Of course, if that function is non-reentrant, this is a problem, but it's actually not possible to make a function reentrant while using wait_for
"correctly".
There is a solution to this. the tl;dr is to change wait_for
to wait for a boolean flag to flip to true rather than using a unique value of the return pointer. But first some background and an example of the problem before a detailed proposal for the solution.
First, a review of how wait
and wait_for
currently work. If you feel like you have a good grasp on this, you can skip down.
There are three related calls:
int __wait(void)
is a system call that blocks execution of the app until a new event is ready. At that point, the kernel signals the event by pushing a frame onto the app's stack and jumping to a callback function specified in a corresponding subscribe
syscall. When the callback returns, execution is resumed at the point where __wait
was called, and the return value of the callback becomes the return for the __wait
call.wait_for(int)
is a library wrapper around __wait
that keeps yielding until a callback of a particular type (specified by the argument) has been invoked. Any event that doesn't match is put onto a queue so that other calls to wait_for
or wait
(see point 3) see it (in case it corresponds to the event they are waiting for). This is meant to correspond to a particluar event, such as putstr
completion, has occured. To do this, all callbacks have to agree to use their return value to signal which event they responded to. To manage this, we currently have a global enum containing each event type in apps/lib/firestorm.h
.wait
is a library wrapper around __wait
that first checks queue populated by wait_for
and only actually calls the system call if the queue was empty.putstr
reentrantFrom a library writer's perspective, making putstr
reentrant should be straight forward. I have a data sink (e.g. the console driver) that can only knows how to deal with one outstanding write request at a time, so I just need to buffer concurrent calls to putstr
. The callback putstr
uses to get notified that the console has finished doing it's thing will process the next buffered string to print et voila.
The problem is that this doesn't let us differentiate in wait_for
between different calls to putstr
. This means that when calls to the same synchronous function are interleaved, we can't be sure that the action has completed when we return. This maybe isn't so terrible with putstr
, but this can definitely be a problem with order dependent operations. For example:
timer_subscribe(my_callback, NULL);
timer_oneshot(1); // a very short timer
spi_write_sync("blahblah", 8);
CB_TYPE my_callback(...) {
// Here, we're not sure if the following command will be buffered or not,
// because "blahblah" may not have completed yet.
spi_write_sync("cmd", 3);
// Maybe "cmd" completed but only maybe "blahblah" completed...
some_call_that_should_only_be_invoked_after_cmd_has_completed();
return TIMER;
}
As a hack explore what was possible for putstr
, I generated a unique return value for each call (just by incrementing a global counter), and waiting on that value instead of PUTSTR
in wait for. With some slight modifications to wait_for
(have to check if the callback is in the queue aftrer each call to __wait
). That functionally works, but is a problem since return values to callbacks should be unique, and it's not clear how to make sure they don't overlap
The core problem is waiting on a something unique for each invocation of a synchronous call. It's hard to generate globally unique values, but it's not hard to generate globally unique pointers.
Instead of waiting on a return value in wait_for
, wait on a particular boolean variable flipping to true. The callback doesn't have to return anything particular, instead it flips the boolean. The boolean can be a global (in the case of a non-reentrant function or if we're only ever processing the head of a globally stored list, e.g.) or can be passed in as the user-data parameter to the callback.
The implementations for wait
and wait_for
are much much simpler (almost not worth implementing):
void wait() {
__wait();
}
void wait_for(bool* cond) {
while(!*cond) {
__wait();
}
}
Writing callbacks that don't correspond to a wait_for
is totally straight forward since you don't have worry about which value you are returning.
These implementations discard the return value completely, even though in principal they could be repurposed (for example to return the length of the written to the console), but that would be a pretty complex implementation and that can always be simulated, again, by modifying another shared variable passed through the callback's user-data.
It requires changes to all functions that currently use wait_for
. So far, i've ported most of them relatively easily. Porting them illuminated that many functions are non-reentrant, and maybe some should be.
Finally, in some cases (like Phil's rf233 code), where synchronous calls are not explicitly broken out, it becomes subtle where to set global boolean flags to false before waiting (it needs to be before the first wait
that might result in a callback, not just the corresponding wait_for
).
Convention in Tock is to use snake_case in register field names.
Original discussion: #17 (diff)
For chip drivers, it is sometimes useful to use a buffer twice (say to handle two reads) before computing a value with the entire buffer. However, if you slice a buffer and pass it to a peripheral (say the I2C driver), then on the callback you get just that slice back and not the entire buffer. If we had a type that wrapped this, then we could re-use a single buffer for a more complicated driver while avoiding this issue.
The BLE stack requires the high frequency clock (HFCLK) for timestamps. It uses the clock to both trigger transmission/reception through the peripheral interconnect (PPI) without going through software as well as capture timestamps on address decoding.
Not sure if this is due to my gcc installation being different, or a common problem.
phoenix:tock pal$ git log | head -1
commit 61d90bb
phoenix:tock pal$ make
Linking build/main.elf
arm-none-eabi-gcc -g -O3 -std=gnu99 -mcpu=cortex-m4 -mthumb -nostdlib -Tsrc/chips/sam4l/loader.ld build/crt1.o build/arch.o build/main.o -o build/main.elf -ffreestanding -lgcc -lc
build/main.o: In function main': main.0.rs:(.text.main+0x426): undefined reference to
__aeabi_memcpy'
collect2: error: ld returned 1 exit status
make: *** [build/main.elf] Error 1
phoenix:tock pal$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 4.8.3
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Some chips need to have the STOP bit omitted in between I2C transactions, and currently the STOP bit is hardcoded.
Add this to the things we need to update in the HIL.
Makefile for libcore is currently broken if you build for more than one platform There are two platforms, let's just say one is cortex-m0 and cortex-m4 (different thumb versions). Triple does not match for core lib. The error output:
./tock/src/process/lib.rs:1:1: 1:1 error: couldn't find crate `core` with expected target triple ./tock/src/chips/nrf51822/target.json [E0461]
./tock/src/process/lib.rs:1 #![crate_name = "process"]
^
./tock/src/process/lib.rs:1:1: 1:1 note: crate `core`, path #1, triple ./tock/src/chips/sam4l/target.json: \\?\C:\Code\github\rust\tock\build\libcore.rlib
error: aborting due to previous error
make: *** [build/libprocess.rlib] Error 101
This is caused by copying libcore to build directory without appending any triple specific value. A quick fix would be to copy libcore to a platform specific build directory or just define libcore.rlib as phony target.
A better one might be to come up with a schema from target.json file. What shall we consider, arch, llvm-target?
When an MPU violation, or other hard fault occurs (e.g. divide-by-zero) from user-space, respawn the offending processes.
Our interrupt handlers are currently emitting function prologues that they should not, e.g:
00016b9e <reset_handler::h0a16a35074fed14diQc>:
16b9e: b580 push {r7, lr}
16ba0: 466f mov r7, sp
The way to fix this is to mark the functions as naked
, but that hasn't quite made it into rust yet: rust-lang/rfcs#1201
We should monitor this though and add it to the list of things worth updating rust versions for whenever it lands.
Use the systick timer to preempt processes after a fixed quantum.
We don't use LTO right now. I just added it and it emptied the whole application. Likely some additional stuff needs to be marked as kept (i.e. non-traditional _start), but not worth digging into now.
All nRF51822 SoC variants seem to have a single port.
Original discussion: #17 (diff)
There is a TODO on the crt1.c but it is easier to track here.
Currently, we implement replacements for several built-in functions that could be provided by some runtime library (GCC or LLVM). It is not clear (at least for me), why they are needed. Are we missing linking to some library that could provide those functions?
A driver for this already exists, but it's not very complete. @brghena is working on improving it significantly. Notably, rely on the TMP006 interrupt pin to learn when new measurements are available, instead of using a timer.
Many Cortex-M's (including the sam4l
) do not support floating point operations in hardware, and Rust doesn't know how to replace those calls with software implementations. Could in principal expose software floating point implementation from libgcc in the kernel. For now, just pushing the floating point computations to user-land.
How does an app unsubscribe to a subscription?
There are currently 2 RTC implementations in the nRF; rtc.rs uses RTC1 for an underlying timer and systick.rs (in the nrf_pc10001) uses RTC0 for systick. We should integrate these two so the same RTC, RTC1, is used both for systick as well as the underlying timer clock. We need to use RTC0 for the BLE stack. Since they share a single NVIC entry this means a capsule that exposes all 3 and demultiplexes their events in software.
I imagine that the plan long-term is to not have the apps
folder in with the kernel, and I think it makes sense for it to stay there for quick testing now, but what would it take to have a separate repo with apps that we can upload to a firestorm that already has the kernel?
I ask because my ble-serialization app will need a bunch of nordic sources in the short-term and I don't want to complicate the kernel build with the external dependency.
Currently VolatileCell assumes every register is read-write. Ideally, we should provide RO/RW/WO variants that enforce correct access permissions at development time.
Proposed by @0xc0170 on #38 (comment)
There are a couple of "bare metal" projects that implement their reset handlers and ISR vectors in Rust as well. The generated code may not be super optimized as C or hand coded assembly, but this code is usually run/loaded once and thus not on the hot path.
See src/platform/storm/lib.rs for inspiration. static_init!() macro (ideally after moved to a common location) should be used for static buffer allocations.
At a minimum, mutable annotations should be removed where not necessary.
Original discussion: #17 (diff)
Basically there are too many things in firestorm.c that are not specific to firestorm. We should also figure out at some point what we want to do with regard to callbacks and async/sync. Some libraries hide the async-ness and others don't (probably because it's faster to write).
Some things that need to be straightened out (there may be others):
Need to edit src/platform/nrf_pca10001/README.md to include details on how to build for the nRF51 Development Kit. I started it off with some details that I'm pretty sure are right.
This will be done once c_hello is working for nRF51822 (which in turn depends on UART support).
Original discussion: #17 (diff)
__wait
implementation. The cortex-m0 and cortex-m4 differ in how they implement __wait
. The m4 version is correct (see @alevy's comment below)__wait
is special on purpose.(Original discussion: #17 (diff))
A driver on top of I2C for the the FXOS8700CQ accelerometer and magnetometer driver.
Initial user for timers will be the sample "c_blinky" app.
Registers r4-r11 are currently stacked in user space. This won't work when we want to prioritize capsules. If we switch to the kernel without the knowledge of user-space, the registers simply wouldn't be saved.
Provide applications access to exposed GPIO pins.
The GPIO driver provides a system call interface for setting the output of the GPIO pin. There are three command
with the following command numbers:
Command number | Functionality |
---|---|
0 | Enable output |
2 | Set |
3 | Clear |
4 | Toggle |
In each command the first argument to the system call (register r2
) marks which GPIO pin the command is referencing.
Is it possible to virtualize GPIO output? Probably not. I.e. if one application wants the pin high, and the other low, who wins?
One option is just to allow this as a free-for-all. Another is to restrict application access to particular pins --- so only allowed applications can change the output of a particular pin.
What does Linux do for, e.g., raspberry pi and other platforms with GPIO?
Input should be done via interrupts/callbacks. An app can subscribe to a change in value on a particular pin, specifying whether it is interested in the rising edge, falling edge or both.
Virtualization for input is easier than output, since we can simply keep track of which applications subscribed to what kind of change.
Tutorial K4: Set up SPI to be loopback, write a driver with a command to send to SPI and an event on completion, with a buffer for received string (allow/subscribe).
Goal: show allow/subscribe, non-blocking nature of kernel, buffer management.
Currently, a GPIO() private function is used for wrapping the unsafe transmutation of the register base address, which is not the "canonical" way of returning struct instances. But the canonical way may have some disadvantages (see original discussion for details).
Note: this is not a priority given the function is a private implementation detail.
Original discussion: #17 (diff)
I have written a ultra lightweight memory manager for microcontrollers that completely avoids issues with fragmentation
https://github.com/vitiral/tinymem
It's currently written in C and has a known bug, but I've been itching to rewrite it in rust. Would it be useful to you?
To get it to work transparently, I think it would require a compiler plugin - as it uses indirection of pointers to allow it to move memory around safely. But from what I know about rust, it should be possible to implement.
Firestorm has 64K RAM , while nRF51822 based boards have "only" 16K. Tock is able to run fine on smaller systems, as long as we are able to scale down static buffers or limit memory possibly using modular designs.
One example is per app memory reservation. Current master uses a hard-coded 8K limit, but that seems too much for 16K systems.
Python code: tools/nRF51_codegen.py
This will drop the only reason for depending on Python. Rust probably has the necessary crates for this task.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.