blob: 06bf3f73bbcdc88cbe25da7becaebeee83489e85 [file] [log] [blame] [view]
Userland
========
This document explains how application code works in Tock. This is not a guide
to creating your own applications, but rather documentation of the design
thoughts behind how applications function.
<!-- npm i -g markdown-toc; markdown-toc -i Userland.md -->
<!-- toc -->
- [Overview of Applications in Tock](#overview-of-applications-in-tock)
- [System Calls](#system-calls)
- [Callbacks](#callbacks)
- [Inter-Process Communication](#inter-process-communication)
* [Services](#services)
* [Clients](#clients)
- [Application Entry Point](#application-entry-point)
- [Stack and Heap](#stack-and-heap)
- [Debugging](#debugging)
- [Applications](#applications)
<!-- tocstop -->
## Overview of Applications in Tock
Applications in Tock are the user-level code meant to accomplish some type of
task for the end user. Applications are distinguished from kernel code which
handles device drivers, chip-specific details, and general operating system
tasks. Unlike many existing embedded operating systems, in Tock applications
are not compiled with the kernel. Instead they are entirely separate code
that interact with the kernel and each other through [system
calls](https://en.wikipedia.org/wiki/System_call).
Since applications are not a part of the kernel, they may be written in any
language that can be compiled into code capable of running on a microcontroller.
Tock supports running multiple applications concurrently. Co-operatively
multiprogramming is the default, but applications may also be time sliced.
Applications may talk to each other via Inter-Process Communication (IPC)
through system calls.
Applications do not have compile-time knowledge of the address at which they
will be installed and loaded. In the current design of Tock, applications must
be compiled as [position independent
code](https://en.wikipedia.org/wiki/Position-independent_code) (PIC). This
allows them to be run from any address they happen to be loaded into. The use
of PIC for Tock apps is not a fundamental choice, future versions of the system
may support run-time relocatable code.
Applications are unprivileged code. They may not access all portions of memory
and will fault if they attempt to access memory outside of their boundaries
(similarly to segmentation faults in Linux code). To interact with hardware,
applications must make calls to the kernel.
## System Calls
System calls (aka syscalls) are used to send commands to the kernel. These
could include commands to drivers, subscriptions to callbacks, granting of
memory to the kernel so it can store data related to the application,
communication with other application code, and many others. In practice,
system calls are made through library code and the application need not
deal with them directly.
For example, consider the following system call that sets a GPIO pin high:
```c
int gpio_set(GPIO_Pin_t pin) {
return command(GPIO_DRIVER_NUM, 2, pin);
}
```
The command system call itself is implemented as the ARM assembly instruction
`svc` (service call):
```c
int __attribute__((naked))
command(uint32_t driver, uint32_t command, int data) {
asm volatile("svc 2\nbx lr" ::: "memory", "r0");
}
```
A more in-depth discussion can be found in the [system call
documentation](./Syscalls.md).
## Callbacks
Tock is designed to support embedded applications, which often handle
asynchronous events through the use of [callback
functions](https://en.wikipedia.org/wiki/Callback_(computer_programming)). For
example, in order to receive timer callbacks, you first call `timer_subscribe`
with a function pointer to your own function that you want called when the
timer fires. Specific state that you want the callback to act upon can be
passed as the pointer `userdata`. After the application has started the timer,
calls `yield`, and the timer fires, the callback function will be called.
It is important to note that `yield` must be called for events to be serviced in
the current implementation of Tock. Callbacks to the application will be queued
when they occur but the application will not receive them until it yields. This
is not fundamental to Tock, and future version may service callbacks on any
system call or when performing application time slicing. After receiving and
running the callback, application code will continue after the `yield`.
Applications which are "finished" (i.e. have returned from `main()`) should call
`yield` in a loop to avoid being scheduled by the kernel.
## Inter-Process Communication
IPC allows for multiple applications to communicate directly through shared
buffers. IPC in Tock is implemented with a service-client model. Each app can
support one service and the service is identified by its package name which is
included in the Tock Binary Format Header for the app. An app can communicate
with multiple services and will get a unique handle for each discovered service.
Clients and services communicate through shared buffers. Each client can share
some of its own application memory with the service and then notify the service
to instruct it to parse the shared buffer.
### Services
Services are named by the package name included in the app's TBF header.
To register a service, an app can call `ipc_register_svc()` to setup a callback.
This callback will be called whenever a client calls notify on that service.
### Clients
Clients must first discover services they wish to use with the function
`ipc_discover()`. They can then share a buffer with the service by calling
`ipc_share()`. To instruct the service to do something with the buffer, the
client can call `ipc_notify_svc()`. If the app wants to get notifications from
the service, it must call `ipc_register_client_cb()` to receive events from when
the service when the service calls `ipc_notify_client()`.
See `ipc.h` in `libtock-c` for more information on these functions.
## Application Entry Point
An application specifies the first function the kernel should call by setting
the variable `init_fn_offset` in its TBF header. This function should have the
following signature:
```c
void _start(void* text_start, void* mem_start, void* memory_len, void* app_heap_break);
```
The Tock kernel tries to impart no restrictions on the stack and heap layout of
application processes. As such, a process starts in a very minimal environment,
with an initial stack sufficient to support a syscall, but not much more.
Application startup routines should first
[move their program break](/doc/syscalls/memop.md#operation-type-0-brk) to accomodate
their desired layout, and then setup local stack and heap tracking in accordance
with their runtime.
## Stack and Heap
Applications can specify memory requirements by setting the `minimum_ram_size` variable
in their TBF headers. Note that the Tock kernel treats this as a minimum,
depending on the underlying platform, the amount of memory may be larger than
requested, but will never be smaller.
If there is insufficient memory to load your application, the kernel will fail
during loading and print a message.
If an application exceeds its alloted memory during runtime, the application
will crash (see the [Debugging](#debugging) section for an example).
## Debugging
If an application crashes, Tock can provide a lot of useful information.
By default, when an application crashes Tock prints a crash dump over the
platform's default console interface.
Note that because an application is relocated when it is loaded, the binaries
and debugging .lst files generated when the app was originally compiled will not
match the actual executing application on the board. To generate matching files
(and in particular a matching .lst file), you can use the `make debug` target
app directory to create an appropriate .lst file that matches how the
application was actually executed. See the end of the debug print out for an
example command invocation.
```
---| Fault Status |---
Data Access Violation: true
Forced Hard Fault: true
Faulting Memory Address: 0x00000000
Fault Status Register (CFSR): 0x00000082
Hard Fault Status Register (HFSR): 0x40000000
---| App Status |---
App: crash_dummy - [Fault]
Events Queued: 0 Syscall Count: 0 Dropped Callback Count: 0
Restart Count: 0
Last Syscall: None
╔═══════════╤══════════════════════════════════════════╗
║ Address │ Region Name Used | Allocated (bytes) ║
╚0x20006000═╪══════════════════════════════════════════╝
│ ▼ Grant 948 | 948
0x20005C4C ┼───────────────────────────────────────────
│ Unused
0x200049F0 ┼───────────────────────────────────────────
│ ▲ Heap 0 | 4700 S
0x200049F0 ┼─────────────────────────────────────────── R
│ Data 496 | 496 A
0x20004800 ┼─────────────────────────────────────────── M
│ ▼ Stack 72 | 2048
0x200047B8 ┼───────────────────────────────────────────
│ Unused
0x20004000 ┴───────────────────────────────────────────
.....
0x00030400 ┬─────────────────────────────────────────── F
│ App Flash 976 L
0x00030030 ┼─────────────────────────────────────────── A
│ Protected 48 S
0x00030000 ┴─────────────────────────────────────────── H
R0 : 0x00000000 R6 : 0x20004894
R1 : 0x00000001 R7 : 0x20004000
R2 : 0x00000000 R8 : 0x00000000
R3 : 0x00000000 R10: 0x00000000
R4 : 0x00000000 R11: 0x00000000
R5 : 0x20004800 R12: 0x12E36C82
R9 : 0x20004800 (Static Base Register)
SP : 0x200047B8 (Process Stack Pointer)
LR : 0x000301B7
PC : 0x000300AA
YPC : 0x000301B6
APSR: N 0 Z 1 C 1 V 0 Q 0
GE 0 0 0 0
EPSR: ICI.IT 0x00
ThumbBit true
Cortex-M MPU
Region 0: base: 0x20004000, length: 8192 bytes; ReadWrite (0x3)
Region 1: base: 0x30000, length: 1024 bytes; ReadOnly (0x6)
Region 2: Unused
Region 3: Unused
Region 4: Unused
Region 5: Unused
Region 6: Unused
Region 7: Unused
To debug, run `make debug RAM_START=0x20004000 FLASH_INIT=0x30059`
in the app's folder and open the .lst file.
```
## Applications
For example applications, see the language specific userland repos:
- [libtock-c](https://github.com/tock/libtock-c): C and C++ apps.
- [libtock-rs](https://github.com/tock/libtock-rs): Rust apps.