forked from ROMEO/fsw-ws
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c111ec176c | ||
|
|
081c3a7472 | ||
|
|
4643cce673 | ||
|
|
1f52efecda | ||
|
|
46d8803ab7 | ||
|
|
de2c3dfede | ||
|
|
bcacc493d3 | ||
|
|
a1a5f7c417 | ||
|
|
e2280b138f | ||
|
|
e31b632bc8 | ||
|
|
f3e17fc7a1 | ||
|
|
0d69a52626 | ||
|
|
4769da31b7 | ||
|
|
b5ab8a601f | ||
|
|
825c0d1911 | ||
|
|
f3aa72b1cb | ||
|
|
3379b39105 | ||
|
|
c7062407e0 | ||
|
|
c5d2c65a2e | ||
|
|
84fa8721c2 | ||
|
|
cead8ba50b | ||
|
|
a5267b11bf | ||
|
|
f8dbe305d6 | ||
|
|
f9aacd4c91 | ||
|
|
3af76b93b2 | ||
|
|
556faaba08 | ||
|
|
bb5fbf19cf | ||
|
|
243fd19827 | ||
|
|
20ca676e63 | ||
|
|
1049c40f9d | ||
|
|
6c937fa8b0 | ||
|
|
a5713ac7b8 | ||
|
|
57ecba297b | ||
|
|
e3b7fb48f1 | ||
|
|
6b1fd91a70 | ||
|
|
10b98c3835 |
@@ -8,7 +8,7 @@ jobs:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.ROMEO_BUILDFIX_TOKEN }}
|
||||
token: ${{ secrets.ROMEO_NUMALFIX_TOKEN }}
|
||||
submodules: 'recursive'
|
||||
- name: Configure z7
|
||||
run: |
|
||||
|
||||
+2
-1
@@ -4,4 +4,5 @@
|
||||
.vscode
|
||||
__pycache__/*
|
||||
**/__pycache__/*
|
||||
*.elf
|
||||
*.elf
|
||||
.cache
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ The romeo-obsw binary can now be found in the `build_z7` directory. The next ste
|
||||
|
||||
## Linux
|
||||
|
||||
The obsw can also be compiled and run on linux using the gcc-posix port of FreeRTOS.
|
||||
The obsw can also be compiled and run on linux using the `gcc-posix` port of FreeRTOS.
|
||||
|
||||
### build
|
||||
|
||||
@@ -86,7 +86,7 @@ cmake ..
|
||||
|
||||
Compile (`-j` count your discretion):
|
||||
```sh
|
||||
make -j 8"
|
||||
make -j 8
|
||||
```
|
||||
|
||||
### run
|
||||
|
||||
+10
-2
@@ -15,7 +15,7 @@ set(ROMEO_WARNING_FLAGS
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
-Werror)
|
||||
-Werror) # TODO so far, this only affects mission code, not bsp
|
||||
|
||||
# CMake options which are only available when crosscompiling
|
||||
if (${CMAKE_CROSSCOMPILING})
|
||||
@@ -25,9 +25,17 @@ if (${CMAKE_CROSSCOMPILING})
|
||||
if(${ZYNQ_UART} STREQUAL UART0)
|
||||
add_compile_definitions(ZYNQ_USE_UART0)
|
||||
endif()
|
||||
|
||||
option(ARM_SEMIHOSTING "enable semihosting for emulation" OFF)
|
||||
|
||||
if(${ARM_SEMIHOSTING})
|
||||
add_compile_definitions(ARM_SEMIHOSTING)
|
||||
endif()
|
||||
else()
|
||||
unset(ZYNQ_UART)
|
||||
unset(ZYNQ_UART CACHE)
|
||||
unset(ARM_SEMIHOSTING)
|
||||
unset(ARM_SEMIHOSTING CACHE)
|
||||
endif()
|
||||
|
||||
# Add main executable
|
||||
@@ -44,7 +52,7 @@ endif()
|
||||
if(${CMAKE_CROSSCOMPILING})
|
||||
#TODO: this somewhat hardcodes zynq as the only cross target
|
||||
set(FREERTOS_PORT GCC_ARM_CA9 CACHE STRING "")
|
||||
set(FREERTOS_HEAP 1 CACHE STRING "")
|
||||
set(FREERTOS_HEAP ${CMAKE_CURRENT_SOURCE_DIR}/bsp_z7/freeRTOS/no_heap.c CACHE STRING "")
|
||||
|
||||
# config library for FreeRTOS
|
||||
add_library(freertos_config INTERFACE)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Qemu
|
||||
|
||||
qemu is an emulator which can be used to run code compiled for a different architecture (say ARMv7 as on zynq) on the host architecture (the PC you are using).
|
||||
|
||||
This is mainly useful for testing platform dependent code, in our case the operating system abstraction. This code can not be tested on linux, as the FreeRTOS port used is a different one.
|
||||
|
||||
By selecting the `ZYNQ_SEMIHOSTING` in cmake, the semihosting interface is enabled. This allows the software to signal an exit condition to qemu, which in turn makes qemu exit with the corresponding exit code. This functionality is used for unit testing, where the exit condition is needed to signal success or failure of the unit tests.
|
||||
|
||||
# Run on qemu
|
||||
|
||||
The full call to run the romeo-obsw on qemu is:
|
||||
|
||||
`qemu-system-arm -semihosting -nographic -monitor none -serial null -serial stdio -machine xilinx-zynq-a9 -m 500M -kernel romeo-obsw`
|
||||
|
||||
Where the arguments are the following:
|
||||
* `qemu-system-arm`: the qemu emulating arm processors
|
||||
* `-semihosting`: enables the emulated software to communicate with the host. This is only used to allow the software to exit including returning an exit code
|
||||
* `-nographic`: disable qemus graphical interface
|
||||
* `-monitor none`: disable qemu monitoring (not used)
|
||||
* `-serial null`: add first UART, do not connect it.
|
||||
* `-serial stdio`: add second UART, connect it to the console of the host
|
||||
* `-machine xilinx-zynq-a9`: select zynq as emulation target
|
||||
* `-m 500M`: set RAM size
|
||||
* `-device loader,addr=0x0000012c,data=0x00001234,data-len=4`: set data at the memory location `data`. This is used by the FSW to detect if it is runing on qemu or on a real zynq (where this memory location will not be 0x00001234)
|
||||
* `-kernel romeo-obsw`: which program to run
|
||||
|
||||
The first UART can be connected to a serial device on the host. Add `-chardev serial,id=serial0,path="/dev/ttyUSB0"` where `"/dev/ttyUSB0"` is the path to the serial device. Then, change the first invocation of `-serial` from `-serial null` to `-serial chardev:serial0`
|
||||
|
||||
|
||||
# Debug on qemu
|
||||
|
||||
Basically the same call as above, only `-kernel` is replaced by `-s -S`, which enables debugging and halts the CPU.
|
||||
|
||||
`qemu-system-arm -semihosting -nographic -monitor none -serial null -serial stdio -machine xilinx-zynq-a9 -m 500M -device loader,addr=0x0000012c,data=0x00001234,data-len=4 -s -S`
|
||||
|
||||
After qemu is started, connect to the gdb server:
|
||||
```sh
|
||||
arm-none-eabi-gdb romeo-obsw
|
||||
>target remote :1234
|
||||
>load
|
||||
>cont
|
||||
```
|
||||
@@ -43,6 +43,22 @@ The current working steps are:
|
||||
Consider using Docker or setting up a local compile toolchain.
|
||||
Please refer to [BUILD_WITH_DOCKER.md](./BUILD_WITH_DOCKER.md) or [BUILD_WITH_CMAKE.md](./BUILD_WITH_CMAKE.md).
|
||||
|
||||
### Compiling Annotations
|
||||
Bare metal rust on the Zynq requires the use of C-libraries, which affects the compilation.
|
||||
The current setup includes `cmake` which calls `cargo`to compile the rust `mission` and `framework` creates as `static libraries`.
|
||||
Then, `gcc` is used to link those two libraries , the `Xilinx Board Support Packages (BSP)` (incl. all hardware drivers), and supplier libraries in the final binary.
|
||||
|
||||
The canocial compiler is debian bookkworm's `arm-none-eabi-gcc 12.2` using `newlib 3.3.0`.
|
||||
|
||||
The **minimum** set of **compiler** (not linker) **flags** (as used by Xilinx to build the First Stage Bootloader (FSBL)) are
|
||||
+ hardware (HW) type: `-mcpu=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard`
|
||||
+ function and data sections, to be able to garbage-collect unused code: `-ffunction-sections -fdata-sections`
|
||||
+ optimization for release: `-O2`
|
||||
|
||||
Link Time Optimization (LTO) TBD
|
||||
<!--Currently LTO is not enabled, as it might break linkage with the static STR library, TBC with STR supplier.
|
||||
|
||||
... to be completed ... TODO PAUL-->
|
||||
## How to debug on hardware?
|
||||
|
||||
Please refer to [DEBUG_ON_ZEDBOARD.md](./DEBUG_ON_ZEDBOARD.md)
|
||||
@@ -50,7 +50,7 @@
|
||||
#define configUSE_16_BIT_TICKS 0
|
||||
#define configIDLE_SHOULD_YIELD 1
|
||||
#define configUSE_MUTEXES 1
|
||||
#define configCHECK_FOR_STACK_OVERFLOW 0
|
||||
#define configCHECK_FOR_STACK_OVERFLOW 2
|
||||
#define configUSE_RECURSIVE_MUTEXES 1
|
||||
#define configQUEUE_REGISTRY_SIZE 20
|
||||
#define configUSE_APPLICATION_TASK_TAG 1
|
||||
@@ -58,6 +58,7 @@
|
||||
#define configUSE_ALTERNATIVE_API 0
|
||||
#define configUSE_QUEUE_SETS 1
|
||||
#define configUSE_TASK_NOTIFICATIONS 1
|
||||
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1
|
||||
|
||||
/* The following 2 memory allocation schemes are possible for this demo:
|
||||
*
|
||||
@@ -72,7 +73,7 @@
|
||||
* Static only configuration is not possible for this demo as it utilizes
|
||||
* dynamic allocation.
|
||||
*/
|
||||
#define configSUPPORT_STATIC_ALLOCATION 0
|
||||
#define configSUPPORT_STATIC_ALLOCATION 1
|
||||
#define configSUPPORT_DYNAMIC_ALLOCATION 1
|
||||
|
||||
#define configRECORD_STACK_HIGH_ADDRESS 1
|
||||
@@ -86,7 +87,7 @@
|
||||
#define configTIMER_QUEUE_LENGTH 20
|
||||
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
|
||||
|
||||
#define configMAX_PRIORITIES ( 7 )
|
||||
#define configMAX_PRIORITIES ( 10 )
|
||||
|
||||
/* Run time stats gathering configuration options. */
|
||||
unsigned long ulGetRunTimeCounterValue( void ); /* Prototype of function that returns run time counter. */
|
||||
|
||||
@@ -1 +1 @@
|
||||
target_sources(bsp PRIVATE hardware.c)
|
||||
target_sources(bsp PRIVATE hardware.c serial.c)
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include <hardware/interfaces.h>
|
||||
|
||||
#include "serial.h"
|
||||
|
||||
extern const char *sim_ip;
|
||||
extern int ai_family;
|
||||
|
||||
@@ -39,6 +41,13 @@ const char *get_port_number(const char *path, size_t path_len) {
|
||||
}
|
||||
|
||||
int hw_device_open(const char *path, size_t path_len) {
|
||||
|
||||
int serial_fd = serial_open(path, path_len);
|
||||
|
||||
if (serial_fd >= -1) {
|
||||
return serial_fd;
|
||||
}
|
||||
|
||||
const char *port_number = get_port_number(path, path_len);
|
||||
if (port_number == NULL) {
|
||||
return -1;
|
||||
@@ -67,7 +76,7 @@ int hw_device_open(const char *path, size_t path_len) {
|
||||
|
||||
for (current_candidate = addr_candidates; current_candidate != NULL;
|
||||
current_candidate = current_candidate->ai_next) {
|
||||
sock = socket(current_candidate->ai_family, current_candidate->ai_socktype,
|
||||
sock = socket(current_candidate->ai_family, current_candidate->ai_socktype | SOCK_NONBLOCK,
|
||||
current_candidate->ai_protocol);
|
||||
if (sock == -1) {
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// TODO FIXME
|
||||
int compare_string_chars(const char *c_string, const char *chars,
|
||||
size_t chars_len);
|
||||
|
||||
int convert_errno(int errno_value) {
|
||||
// errno on linux will always be >0
|
||||
if (errno <= 0) {
|
||||
// something is very wrong
|
||||
return -1;
|
||||
} else {
|
||||
return -errno - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: can we extend errno safely?
|
||||
// returns fd if ok, -1 on error 0 if no match
|
||||
int serial_open_actual(const char *path, speed_t speed) {
|
||||
// open serial
|
||||
int fd = open(path, O_RDWR | O_NOCTTY | O_SYNC);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct termios termios;
|
||||
|
||||
// initialize termios struct
|
||||
int ret = tcgetattr(fd, &termios);
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// configure for raw input
|
||||
cfmakeraw(&termios);
|
||||
|
||||
// make it non-blocking
|
||||
termios.c_cc[VMIN] = 0;
|
||||
termios.c_cc[VTIME] = 0;
|
||||
|
||||
// set speed
|
||||
ret = cfsetspeed(&termios, speed);
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = tcsetattr(fd, TCSANOW, &termios);
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
// returns fd if success, -1 on error, -2 if no match
|
||||
int serial_open(const char *path, size_t path_len) {
|
||||
if (compare_string_chars("ps/uart_mtg", path, path_len) == 1) {
|
||||
return serial_open_actual("/dev/ttyUSB0", B921600);
|
||||
}
|
||||
if (compare_string_chars("debug/uart🚀", path, path_len) == 1) {
|
||||
return serial_open_actual("/dev/ttyUSB0", B115200);
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// returns fd if success, -1 if no path match <-1 if error
|
||||
int serial_open(const char *path, size_t path_len);
|
||||
+13
-11
@@ -14,24 +14,26 @@ int ai_family = AF_UNSPEC;
|
||||
|
||||
void mission(void);
|
||||
|
||||
int get_descriptor_rw() { return 1; }
|
||||
void done() { exit(0); }
|
||||
|
||||
void done() {
|
||||
printf("done.\n");
|
||||
exit(0);
|
||||
}
|
||||
void done_error() { exit(1); }
|
||||
|
||||
int test_socket();
|
||||
|
||||
// Don't ask me, it makes the linker happy and does not seem
|
||||
// to break anything ¯\_(ツ)_/¯
|
||||
// TODO link to GCC's personality or make the linux build not use it?
|
||||
void rust_eh_personality() { puts("eh_personality"); }
|
||||
|
||||
void print_usage(const char * name) {
|
||||
fprintf(stderr, "Usage: %s [-s sim_ip] [-4|6]\n", name);
|
||||
void print_usage(const char *name) {
|
||||
fprintf(stderr, "Usage: %s [-s sim_ip] [-4|6]\n", name);
|
||||
}
|
||||
|
||||
#include <hardware/interfaces.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
hw_device_open(
|
||||
"invalidpath",
|
||||
11); // TODO for some weird linker behaviour, if this function is not used
|
||||
// here, it will not be found by the linker when linking the rust lib
|
||||
static struct option long_options[] = {
|
||||
/* NAME ARGUMENT FLAG SHORTNAME */
|
||||
{"sim_ip", required_argument, NULL, 's'},
|
||||
@@ -57,8 +59,8 @@ int main(int argc, char **argv) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mission();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
#define configUSE_PREEMPTION 1
|
||||
#define configUSE_IDLE_HOOK 1
|
||||
#define configUSE_TICK_HOOK 1
|
||||
#define configMAX_PRIORITIES ( 7 )
|
||||
#define configMAX_PRIORITIES ( 10 )
|
||||
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 250 ) /* Large in case configUSE_TASK_FPU_SUPPORT is 2 in which case all tasks have an FPU context. */
|
||||
#define configTOTAL_HEAP_SIZE ( 204800 )
|
||||
#define configMAX_TASK_NAME_LEN ( 10 )
|
||||
@@ -90,7 +90,8 @@
|
||||
#define configUSE_COUNTING_SEMAPHORES 1
|
||||
#define configUSE_QUEUE_SETS 1
|
||||
#define configSUPPORT_STATIC_ALLOCATION 1
|
||||
#define configSUPPORT_DYNAMIC_ALLOCATION 1
|
||||
#define configSUPPORT_DYNAMIC_ALLOCATION 0
|
||||
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1
|
||||
|
||||
/* Include the query-heap CLI command to query the free heap space. */
|
||||
#define configINCLUDE_QUERY_HEAP_COMMAND 0
|
||||
@@ -127,6 +128,8 @@ to exclude the API function. */
|
||||
#define INCLUDE_xTaskGetTaskHandle 1
|
||||
#define INCLUDE_xTaskGetHandle 1
|
||||
#define INCLUDE_xSemaphoreGetMutexHolder 1
|
||||
#define INCLUDE_uxTaskGetStackHighWaterMark2 1
|
||||
#define INCLUDE_xTaskGetSchedulerState 1
|
||||
|
||||
|
||||
/* The private watchdog is used to generate run time stats. */
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// FreeRTOS build system needs to have a heap implementation, even if no dynamic allocation is used.
|
||||
// So we provide one:
|
||||
|
||||
void no_heap() {
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
int hw_interface_write(int fd, const char *ptr, int len);
|
||||
#include <stddef.h>
|
||||
|
||||
int hw_interface_read(int fd, char *ptr, int len);
|
||||
int hw_interface_write(int fd, const char *ptr, size_t len);
|
||||
|
||||
int hw_interface_read(int fd, char *ptr, size_t len);
|
||||
@@ -7,6 +7,10 @@
|
||||
#include "interface_fds.h"
|
||||
#include "uart.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <reent.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int compare_string_chars(const char *c_string, const char *chars,
|
||||
size_t chars_len) {
|
||||
for(int i = 0; i < chars_len; i++) {
|
||||
@@ -26,11 +30,16 @@ int hw_device_open(const char *path, size_t path_len) {
|
||||
uart0_enable_receiver();
|
||||
return UART_0;
|
||||
}
|
||||
if (compare_string_chars("debug/uart🚀", path, path_len) == 1) {
|
||||
uart0_enable_receiver();
|
||||
return UART_0;
|
||||
}
|
||||
if (compare_string_chars("uart1", path, path_len) == 1) {
|
||||
uart1_enable_receiver();
|
||||
return UART_1;
|
||||
}
|
||||
|
||||
// _REENT_ERRNO(_REENT) = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -39,17 +48,19 @@ ssize_t hw_device_transfer(int fd, void *sendbuffer, void *receivebuffer,
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// TODO move into uart.c
|
||||
// we could implement interrupt based nonblocking sending using a queue
|
||||
// like we do receiving (where we need it for the small hw buffer)
|
||||
// but in the end, we do not want too many interrupts, so we do it blocking
|
||||
void uart_send(uint32_t BaseAddress, const char *data, int data_len) {
|
||||
void uart_send(uint32_t BaseAddress, const char *data, size_t data_len) {
|
||||
int todo;
|
||||
for (todo = 0; todo < data_len; todo++) {
|
||||
XUartPs_SendByte(BaseAddress, *data++);
|
||||
}
|
||||
}
|
||||
|
||||
int hw_interface_write(int fd, const char *ptr, int len) {
|
||||
int hw_interface_write(int fd, const char *ptr, size_t len) {
|
||||
enum InterfaceFileDescriptors fd_enum = fd;
|
||||
switch (fd) {
|
||||
case UART_0:
|
||||
@@ -59,10 +70,11 @@ int hw_interface_write(int fd, const char *ptr, int len) {
|
||||
uart_send(XPS_UART1_BASEADDR, ptr, len);
|
||||
return len;
|
||||
}
|
||||
// _REENT_ERRNO(_REENT) = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int hw_interface_read(int fd, char *ptr, int len) {
|
||||
int hw_interface_read(int fd, char *ptr, size_t len) {
|
||||
enum InterfaceFileDescriptors fd_enum = fd;
|
||||
switch (fd) {
|
||||
case UART_0:
|
||||
@@ -71,4 +83,4 @@ int hw_interface_read(int fd, char *ptr, int len) {
|
||||
return uart1_read(ptr,len);;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,13 +69,12 @@ void uart1_handle_interrupt(void *) {
|
||||
// available into the stack
|
||||
uint8_t RecievedByte;
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
|
||||
|
||||
while (XUartPs_IsReceiveData(XPS_UART1_BASEADDR)) {
|
||||
RecievedByte = XUartPs_ReadReg(XPS_UART1_BASEADDR, XUARTPS_FIFO_OFFSET);
|
||||
xQueueSendToBackFromISR(uart1_receive_queue, &RecievedByte,
|
||||
&xHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
|
||||
/* Clear the interrupt status. */
|
||||
XUartPs_WriteReg(XPS_UART1_BASEADDR, XUARTPS_ISR_OFFSET, IsrStatus);
|
||||
@@ -144,8 +143,9 @@ void uart1_enable_receiver() {
|
||||
XScuGic_Enable(&xInterruptController, XPAR_XUARTPS_1_INTR);
|
||||
}
|
||||
|
||||
int uart0_read(char *ptr, int len) {
|
||||
// TODO for blocking, if first call was successfull, further calls need to be delay=0
|
||||
int uart0_read(char *ptr, size_t len) {
|
||||
// TODO for blocking, if first call was successfull, further calls need to be
|
||||
// delay=0
|
||||
int received = 0;
|
||||
while (len > 0) {
|
||||
BaseType_t result = xQueueReceive(uart0_receive_queue, ptr, 0);
|
||||
@@ -159,8 +159,9 @@ int uart0_read(char *ptr, int len) {
|
||||
return received;
|
||||
}
|
||||
|
||||
int uart1_read(char *ptr, int len) {
|
||||
// TODO for blocking, if first call was successfull, further calls need to be delay=0
|
||||
int uart1_read(char *ptr, size_t len) {
|
||||
// TODO for blocking, if first call was successfull, further calls need to be
|
||||
// delay=0
|
||||
int received = 0;
|
||||
while (len > 0) {
|
||||
BaseType_t result = xQueueReceive(uart1_receive_queue, ptr, 0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
void uart0_enable_receiver();
|
||||
int uart0_read(char *ptr, int len);
|
||||
int uart0_read(char *ptr, int size_t);
|
||||
void uart1_enable_receiver();
|
||||
int uart1_read(char *ptr, int len);
|
||||
int uart1_read(char *ptr, int size_t);
|
||||
+50
-64
@@ -47,10 +47,6 @@ XScuGic xInterruptController;
|
||||
|
||||
extern SemaphoreHandle_t malloc_mutex;
|
||||
|
||||
int get_descriptor_rw() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
void mission(void);
|
||||
|
||||
@@ -58,10 +54,17 @@ void initFreeRTOSHelper();
|
||||
|
||||
int main(void) {
|
||||
|
||||
// Enable UARTs, so qemu knows we use them (should already be enabled by fsbl
|
||||
// on actual hw)
|
||||
XUartPs_WriteReg(XPS_UART0_BASEADDR, XUARTPS_CR_OFFSET,
|
||||
XUARTPS_CR_TX_EN | XUARTPS_CR_RX_EN);
|
||||
XUartPs_WriteReg(XPS_UART1_BASEADDR, XUARTPS_CR_OFFSET,
|
||||
XUARTPS_CR_TX_EN | XUARTPS_CR_RX_EN);
|
||||
|
||||
/* Configure the hardware ready to run. */
|
||||
prvSetupHardware();
|
||||
|
||||
mission();
|
||||
mission();
|
||||
}
|
||||
|
||||
static void prvSetupHardware(void) {
|
||||
@@ -120,64 +123,44 @@ void vInitialiseTimerForRunTimeStats(void) {
|
||||
XScuWdt_Start(&xWatchDogInstance);
|
||||
}
|
||||
|
||||
/* configUSE_STATIC_ALLOCATION is set to 1, so the application must provide an
|
||||
implementation of vApplicationGetIdleTaskMemory() to provide the memory that is
|
||||
used by the Idle task. */
|
||||
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
|
||||
StackType_t **ppxIdleTaskStackBuffer,
|
||||
uint32_t *pulIdleTaskStackSize) {
|
||||
/* If the buffers to be provided to the Idle task are declared inside this
|
||||
function then they must be declared static - otherwise they will be allocated
|
||||
on the stack and so not exists after this function exits. */
|
||||
static StaticTask_t xIdleTaskTCB;
|
||||
static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE];
|
||||
|
||||
/* Pass out a pointer to the StaticTask_t structure in which the Idle task's
|
||||
state will be stored. */
|
||||
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
|
||||
|
||||
/* Pass out the array that will be used as the Idle task's stack. */
|
||||
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
|
||||
|
||||
/* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer.
|
||||
Note that, as the array is necessarily of type StackType_t,
|
||||
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
|
||||
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
|
||||
}
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
/* configUSE_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so the
|
||||
application must provide an implementation of vApplicationGetTimerTaskMemory()
|
||||
to provide the memory that is used by the Timer service task. */
|
||||
|
||||
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
|
||||
StackType_t **ppxTimerTaskStackBuffer,
|
||||
uint32_t *pulTimerTaskStackSize);
|
||||
|
||||
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
|
||||
StackType_t **ppxTimerTaskStackBuffer,
|
||||
uint32_t *pulTimerTaskStackSize) {
|
||||
/* If the buffers to be provided to the Timer task are declared inside this
|
||||
function then they must be declared static - otherwise they will be allocated
|
||||
on the stack and so not exists after this function exits. */
|
||||
static StaticTask_t xTimerTaskTCB;
|
||||
static StackType_t uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH];
|
||||
|
||||
/* Pass out a pointer to the StaticTask_t structure in which the Timer
|
||||
task's state will be stored. */
|
||||
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
|
||||
|
||||
/* Pass out the array that will be used as the Timer task's stack. */
|
||||
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
|
||||
|
||||
/* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
|
||||
Note that, as the array is necessarily of type StackType_t,
|
||||
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
|
||||
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
|
||||
#ifndef ARM_SEMIHOSTING
|
||||
void done() {
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
|
||||
// Marker for debugging sessions
|
||||
__attribute__((noinline)) void done() { asm(""); }
|
||||
void done_error() {
|
||||
// makes no difference
|
||||
done();
|
||||
}
|
||||
|
||||
#else // enable semihosting interface for done()
|
||||
void done() {
|
||||
|
||||
// Call semihosting interface to signal exit
|
||||
// see https://github.com/ARM-software/abi-aa -> Miscellaneous material ->
|
||||
// Semihosting for AArch32 and AArch64
|
||||
register int reg0 asm("r0");
|
||||
register int reg1 asm("r1");
|
||||
|
||||
reg0 = 0x18; // SYS_EXIT
|
||||
reg1 = 0x20026; // ADP_Stopped_ApplicationExit
|
||||
|
||||
asm("svc 0x123456"); // syscall to semihosting interface
|
||||
}
|
||||
|
||||
void done_error() {
|
||||
// same as done(), will make qemu return 1
|
||||
register int reg0 asm("r0");
|
||||
register int reg1 asm("r1");
|
||||
|
||||
reg0 = 0x18; // SYS_EXIT
|
||||
reg1 = 0x20023; // ADP_Stopped_RunTimeErrorUnknown
|
||||
|
||||
asm("svc 0x123456"); // syscall to semihosting interface
|
||||
}
|
||||
|
||||
#endif /* SEMIHOSTING */
|
||||
|
||||
void vApplicationIdleHook(void) {
|
||||
volatile size_t xFreeHeapSpace, xMinimumEverFreeHeapSpace;
|
||||
@@ -224,7 +207,9 @@ void vApplicationTickHook(void) {
|
||||
vBasicStreamBufferSendFromISR();
|
||||
|
||||
#if (configUSE_QUEUE_SETS == 1)
|
||||
{ vQueueSetAccessQueueSetFromISR(); }
|
||||
{
|
||||
vQueueSetAccessQueueSetFromISR();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Test flop alignment in interrupts - calling printf from an interrupt
|
||||
@@ -235,7 +220,9 @@ is BAD! */
|
||||
UBaseType_t uxSavedInterruptStatus;
|
||||
|
||||
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
|
||||
{ sprintf(cBuf, "%1.3f", 1.234); }
|
||||
{
|
||||
sprintf(cBuf, "%1.3f", 1.234);
|
||||
}
|
||||
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
|
||||
|
||||
configASSERT(strcmp(cBuf, "1.234") == 0);
|
||||
@@ -244,4 +231,3 @@ is BAD! */
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+23
-15
@@ -4,31 +4,39 @@
|
||||
#include "../hardware/interface_access.h"
|
||||
#include "../hardware/interface_fds.h"
|
||||
|
||||
// newlib offers a (weak) write implementation which
|
||||
// is reentrant by calling _read_r which in turn
|
||||
// relies on _read which we implement here.
|
||||
// This way, we get a global, reentrant read implementation
|
||||
// NOTE: This might be architecture dependent, so check your
|
||||
// newlib implementation!
|
||||
int _read(int fd, char *ptr, int len) {
|
||||
#include <errno.h>
|
||||
#include <reent.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int read(int fd, void *ptr, size_t len) {
|
||||
if (ptr == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 0 is stdin, TODO: do we support it?
|
||||
if (fd < 1) {
|
||||
// _REENT_ERRNO(_REENT) = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// 0 is stdin, TODO: do we support it?
|
||||
if (fd < 1) {
|
||||
// _REENT_ERRNO(_REENT) = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// stdout and stderr
|
||||
if (fd < 3) {
|
||||
// _REENT_ERRNO(_REENT) = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fd < INTERFACE_FDS_NEXT) {
|
||||
return hw_interface_read(fd, ptr, len);
|
||||
int result = hw_interface_read(fd, ptr, len);
|
||||
if (result >= 0) {
|
||||
return result;
|
||||
// } else if (_REENT_ERRNO(_REENT) != EBADF) {
|
||||
// return result;
|
||||
}
|
||||
// continue if fd did not match to try other fd providers
|
||||
}
|
||||
|
||||
// we do not have dynamic fds, so fd is invalid
|
||||
// _REENT_ERRNO(_REENT) = EBADF;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-14
@@ -4,39 +4,47 @@
|
||||
#include "../hardware/interface_access.h"
|
||||
#include "../hardware/interface_fds.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <reent.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// newlib offers a (weak) write implementation which
|
||||
// is reentrant by calling _write_r which in turn
|
||||
// relies on _write which we implement here.
|
||||
// This way, we get a global, reentrant write implementation
|
||||
// NOTE: This might be architecture dependent, so check your
|
||||
// newlib implementation!
|
||||
int _write(int fd, const char *ptr, int len) {
|
||||
int write(int fd, const void *ptr, size_t len) {
|
||||
if (ptr == NULL) {
|
||||
return 0;
|
||||
// _REENT_ERRNO(_REENT) = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
//TODO check len
|
||||
|
||||
// 0 is stdin, do not write to it
|
||||
if (fd < 1) {
|
||||
return -1; // TODO error
|
||||
// _REENT_ERRNO(_REENT) = EBADF;
|
||||
return -1;
|
||||
}
|
||||
// we only support a single debug UART, so
|
||||
// stdout and stderr are the same and go to the xilinx stdout UART
|
||||
// We output directely to avoid loops and allow debugging (not via a write)
|
||||
if (fd < 3) {
|
||||
int todo;
|
||||
size_t todo;
|
||||
|
||||
const char *data = ptr;
|
||||
|
||||
for (todo = 0; todo < len; todo++) {
|
||||
outbyte(*ptr++);
|
||||
outbyte(*data++);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
if (fd < INTERFACE_FDS_NEXT) {
|
||||
return hw_interface_write(fd, ptr, len);
|
||||
int result = hw_interface_write(fd, ptr, len);
|
||||
if (result >= 0) {
|
||||
return result;
|
||||
// } else if (_REENT_ERRNO(_REENT) != EBADF) {
|
||||
// return result;
|
||||
}
|
||||
// continue if fd did not match to try other fd providers
|
||||
}
|
||||
|
||||
// we do not have dynamic fds, so fd is invalid
|
||||
return -1;
|
||||
}
|
||||
// _REENT_ERRNO(_REENT) = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,14 @@
|
||||
|
||||
#include "xparameters_ps.h"
|
||||
|
||||
#ifdef ZYNQ_USE_UART0
|
||||
#define STDIN_BASEADDRESS 0xE0000000
|
||||
#define STDOUT_BASEADDRESS 0xE0000000
|
||||
#else
|
||||
#define STDIN_BASEADDRESS 0xE0001000
|
||||
#define STDOUT_BASEADDRESS 0xE0001000
|
||||
#endif
|
||||
|
||||
|
||||
/******************************************************************/
|
||||
|
||||
|
||||
@@ -447,10 +447,11 @@ typedef struct {
|
||||
* u32 XUartPs_IsTransmitEmpty(XUartPs InstancePtr)
|
||||
*
|
||||
******************************************************************************/
|
||||
#ifndef XUartPs_IsTransmitEmpty // Xilinx screwed up
|
||||
#define XUartPs_IsTransmitEmpty(InstancePtr) \
|
||||
((Xil_In32(((InstancePtr)->Config.BaseAddress) + (u32)XUARTPS_SR_OFFSET) & \
|
||||
(u32)XUARTPS_SR_TXEMPTY) == (u32)XUARTPS_SR_TXEMPTY)
|
||||
|
||||
#endif
|
||||
|
||||
/************************** Function Prototypes *****************************/
|
||||
|
||||
|
||||
@@ -447,10 +447,11 @@ typedef struct {
|
||||
* u32 XUartPs_IsTransmitEmpty(XUartPs InstancePtr)
|
||||
*
|
||||
******************************************************************************/
|
||||
#ifndef XUartPs_IsTransmitEmpty // Xilinx screwed up
|
||||
#define XUartPs_IsTransmitEmpty(InstancePtr) \
|
||||
((Xil_In32(((InstancePtr)->Config.BaseAddress) + (u32)XUARTPS_SR_OFFSET) & \
|
||||
(u32)XUARTPS_SR_TXEMPTY) == (u32)XUARTPS_SR_TXEMPTY)
|
||||
|
||||
#endif
|
||||
|
||||
/************************** Function Prototypes *****************************/
|
||||
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# TODO directly referencing bsp is not correct
|
||||
target_include_directories(bsp PUBLIC include)
|
||||
target_include_directories(bsp PUBLIC include)
|
||||
|
||||
add_subdirectory(git_version)
|
||||
@@ -0,0 +1,17 @@
|
||||
find_package(Git)
|
||||
if(Git_FOUND)
|
||||
message("Git found: ${GIT_EXECUTABLE}")
|
||||
|
||||
add_custom_target( get_git_version
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/git_version.h.in
|
||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/git_version.h
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/get_version.cmake
|
||||
)
|
||||
|
||||
add_dependencies(${OBSW_NAME} get_git_version)
|
||||
target_include_directories(${OBSW_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
else()
|
||||
message( FATAL_ERROR "Building outside of git is not supported yet")
|
||||
endif()
|
||||
@@ -0,0 +1,3 @@
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --dirty --always OUTPUT_VARIABLE GIT_VERSION_STRING OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
string(TIMESTAMP BUILD_TIME_STRING UTC)
|
||||
configure_file(${INPUT_FILE} ${OUTPUT_FILE})
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#cmakedefine GIT_VERSION_STRING "@GIT_VERSION_STRING@"
|
||||
#cmakedefine BUILD_TIME_STRING "@BUILD_TIME_STRING@"
|
||||
+1
-1
Submodule fsbl-compiled updated: e404fcf733...a4e4b2606a
+243
-68
@@ -2,89 +2,117 @@
|
||||
#include "semphr.h"
|
||||
#include "task.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
// TODO namespace the names
|
||||
// TODO panic if able, but not in runtime calls
|
||||
|
||||
SemaphoreHandle_t global_threading_semaphore = NULL;
|
||||
|
||||
uint8_t global_threading_available_c() {
|
||||
if (global_threading_semaphore == NULL) {
|
||||
|
||||
global_threading_semaphore = xSemaphoreCreateBinary();
|
||||
// xSemaphoreGive(global_threading_semaphore);
|
||||
}
|
||||
if (uxSemaphoreGetCount(global_threading_semaphore) == 1) {
|
||||
return 1;
|
||||
// Wraps xTaskGetCurrentTaskHandle and returns NULL if no Task is running
|
||||
//
|
||||
// xTaskGetCurrentTaskHandle() will return a handle even if no task is
|
||||
// running as long as one has been created already.
|
||||
void *freertos_task_current(void) {
|
||||
// If scheduler is not running, xTaskGetCurrentTaskHandle() might return a
|
||||
// valid handle of a already created task, so we check for Scheduler state
|
||||
// before calling it
|
||||
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
|
||||
return NULL;
|
||||
} else {
|
||||
return 0;
|
||||
return (void *)xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
}
|
||||
|
||||
void enable_global_threading_c() { xSemaphoreGive(global_threading_semaphore); }
|
||||
|
||||
void disable_global_threading_c() {
|
||||
xSemaphoreTake(global_threading_semaphore, portMAX_DELAY);
|
||||
}
|
||||
|
||||
const char *INVALID_TASK = "invalid task";
|
||||
|
||||
const char *get_task_name() {
|
||||
/* this function is called from rust's panic,
|
||||
* so we need to be extra sure to not cause another
|
||||
* one. pcTaskGetName will trigger an assertion
|
||||
* on debug builds if no task is running so we
|
||||
* check if the current task is valid before using it.
|
||||
* xTaskGetCurrentTaskHandle seems to be a lightweight
|
||||
* way to do that */
|
||||
void *task_handle = xTaskGetCurrentTaskHandle();
|
||||
if (task_handle == NULL) {
|
||||
return INVALID_TASK;
|
||||
}
|
||||
const char *name = pcTaskGetName(NULL);
|
||||
if (name == NULL) {
|
||||
return INVALID_TASK;
|
||||
}
|
||||
if (strlen(name) > configMAX_TASK_NAME_LEN) {
|
||||
return INVALID_TASK;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
void stop_it() { taskENTER_CRITICAL(); }
|
||||
|
||||
// TODO return some error code?
|
||||
void *create_task(TaskFunction_t taskFunction, void *parameter,
|
||||
uint32_t stack_size) {
|
||||
// TODO verify uint32_t vs configSTACK_DEPTH_TYPE
|
||||
TaskHandle_t newTask;
|
||||
BaseType_t result =
|
||||
xTaskCreate(taskFunction, "rust", stack_size, parameter, 4, &newTask);
|
||||
if (result == pdTRUE) {
|
||||
return newTask;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
StaticTask_t init_task_data;
|
||||
StackType_t init_task_stack[configMINIMAL_STACK_SIZE * 10];
|
||||
|
||||
void freertos_init_and_start_scheduling(TaskFunction_t init_task) {
|
||||
// TaskHandle_t handle =
|
||||
xTaskCreateStatic(init_task, "c_init", configMINIMAL_STACK_SIZE * 10,
|
||||
NULL, configMAX_PRIORITIES - 1, init_task_stack, &init_task_data);
|
||||
// vTaskSetThreadLocalStoragePointer(handle, 0, NULL);
|
||||
|
||||
vTaskStartScheduler();
|
||||
}
|
||||
|
||||
void task_delay(uint32_t milliseconds) {
|
||||
// TODO verify uint32_t vs TickType_t
|
||||
vTaskDelay(pdMS_TO_TICKS(milliseconds));
|
||||
void freertos_task_suspend(void *handle) { vTaskSuspend(handle); }
|
||||
|
||||
void freertos_task_delete(void *handle) { vTaskDelete(handle); }
|
||||
|
||||
void freertos_task_delay(uint32_t milliseconds) {
|
||||
// calculate conversion in 64bit to avoid overflow (Tick rate is < 32bit)
|
||||
uint64_t ticks_64 =
|
||||
((uint64_t)milliseconds * (uint64_t)configTICK_RATE_HZ) / (uint64_t)1000;
|
||||
|
||||
// convert to target type
|
||||
TickType_t delay_ticks = (TickType_t)ticks_64;
|
||||
|
||||
// check overflow
|
||||
if ((delay_ticks != ticks_64) || (delay_ticks > portMAX_DELAY)) {
|
||||
delay_ticks = portMAX_DELAY;
|
||||
}
|
||||
|
||||
vTaskDelay(delay_ticks);
|
||||
}
|
||||
|
||||
void freertos_task_storage_set(void *handle, void *data) {
|
||||
// if NULL is passed, try to get current task's handle
|
||||
if (handle == NULL) {
|
||||
handle = freertos_task_current();
|
||||
if (handle == NULL) {
|
||||
// No task running, invalid use of API
|
||||
// TODO panic
|
||||
return;
|
||||
}
|
||||
}
|
||||
vTaskSetThreadLocalStoragePointer(handle, 0, data);
|
||||
}
|
||||
|
||||
void *freertos_task_storage_get(void *handle) {
|
||||
// if NULL is passed, try to get current task's handle
|
||||
if (handle == NULL) {
|
||||
handle = freertos_task_current();
|
||||
if (handle == NULL) {
|
||||
// No task running, invalid use of API
|
||||
// TODO panic
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return pvTaskGetThreadLocalStoragePointer(handle, 0);
|
||||
}
|
||||
|
||||
uint32_t freertos_task_stack_watermark() {
|
||||
// TODO verify types
|
||||
return uxTaskGetStackHighWaterMark2(NULL);
|
||||
}
|
||||
|
||||
void delete_task(void *task) {
|
||||
vTaskSuspend(
|
||||
task); // we can not use vDeleteTask as it would free the allocated memory
|
||||
// which is forbidden using heap1 (which we use)
|
||||
// which is forbidden using static allocation
|
||||
}
|
||||
|
||||
void *create_queue(uint32_t length, uint32_t element_size) {
|
||||
void *freertos_queue_create_static(uint32_t depth, uint32_t element_size,
|
||||
char *queue_data, uint32_t queue_data_len,
|
||||
uint8_t *queue) {
|
||||
if (queue_data_len < sizeof(StaticQueue_t)) {
|
||||
// TODO panic
|
||||
// printf("freertos_queue_create_static: queue_data needs to be %zu long\n",
|
||||
// sizeof(StaticQueue_t));
|
||||
return 0;
|
||||
}
|
||||
// TODO verify uint32_t vs UBaseType_t
|
||||
QueueHandle_t newQueue = xQueueCreate(length, element_size);
|
||||
QueueHandle_t newQueue = xQueueCreateStatic(depth, element_size, queue,
|
||||
(StaticQueue_t *)queue_data);
|
||||
return newQueue;
|
||||
}
|
||||
|
||||
uint8_t queue_receive(void *queue, void *message) {
|
||||
uint8_t freertos_queue_receive(void *queue, void *message) {
|
||||
if (xQueueReceive(queue, message, 0) == pdPASS) {
|
||||
return 1;
|
||||
} else {
|
||||
@@ -92,7 +120,7 @@ uint8_t queue_receive(void *queue, void *message) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t queue_send(void *queue, void *message) {
|
||||
uint8_t freertos_queue_send(void *queue, void *message) {
|
||||
if (xQueueSend(queue, message, 0) != pdPASS) {
|
||||
return 1;
|
||||
} else {
|
||||
@@ -100,11 +128,7 @@ uint8_t queue_send(void *queue, void *message) {
|
||||
}
|
||||
}
|
||||
|
||||
void *create_mutex() { return xSemaphoreCreateRecursiveMutex(); }
|
||||
|
||||
uint8_t take_mutex(void *handle) {
|
||||
// TODO check if global semaphore is free (ie, we are doing multitasking)
|
||||
// if not, pointers are invalid, bail out
|
||||
uint8_t freertos_mutex_take(void *handle) {
|
||||
if (xSemaphoreTakeRecursive(handle, portMAX_DELAY) == pdPASS) {
|
||||
return 1;
|
||||
} else {
|
||||
@@ -112,12 +136,163 @@ uint8_t take_mutex(void *handle) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t give_mutex(void *handle) {
|
||||
// TODO check if global semaphore is free (ie, we are doing multitasking)
|
||||
// if not, pointers are invalid, bail out
|
||||
uint8_t freertos_mutex_give(void *handle) {
|
||||
if (xSemaphoreGiveRecursive(handle) == pdPASS) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
enum OnceState { UNINIT = 0, INIT = 1, TAKEN = 2 };
|
||||
|
||||
typedef struct {
|
||||
SemaphoreHandle_t local_mutex;
|
||||
StaticSemaphore_t local_mutex_data;
|
||||
uint8_t once_state;
|
||||
} StaticOnceData_t;
|
||||
|
||||
/**
|
||||
* mimics pthread_once()
|
||||
*
|
||||
* uses a mutex to guard the state, to allow threads to block waiting
|
||||
* until function has been called.
|
||||
*
|
||||
* After that, the local mutex guards once_data, which is used to only
|
||||
* call the function once. All calls sharing the local_mutex and
|
||||
* once_data will block until the function was called once.
|
||||
*
|
||||
* local_mutex and once_data need to be initialized to 0 before any calls.
|
||||
* local_mutex, local_mutex_data and once_data must not be used other
|
||||
* than a call to this function.
|
||||
*
|
||||
* panics
|
||||
*/
|
||||
uint8_t freertos_once(char *once_data_in, uint32_t once_data_len,
|
||||
void *function(void)) {
|
||||
if (once_data_in == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (once_data_len < sizeof(StaticOnceData_t)) {
|
||||
// TODO panic
|
||||
// printf("freertos_once: data needs to be %zu long\n",
|
||||
// sizeof(StaticTask_t));
|
||||
return 0;
|
||||
}
|
||||
StaticOnceData_t *once_data = (StaticOnceData_t *)once_data_in;
|
||||
|
||||
// TODO assert global_once_mutex != NULL
|
||||
|
||||
// first, we need to check if the local mutex was already created
|
||||
// this needs to be protected to be threadsafe
|
||||
// Documentation says that we must not call APIs from within a critical
|
||||
// Section but I think we are safe in the case of creating a mutex
|
||||
// which basically only intializes the fields
|
||||
// TODO verify?
|
||||
taskENTER_CRITICAL();
|
||||
if (once_data->once_state == UNINIT) {
|
||||
once_data->local_mutex =
|
||||
xSemaphoreCreateRecursiveMutexStatic(&once_data->local_mutex_data);
|
||||
// if (once_data->local_mutex == NULL) {
|
||||
// // will not happen, xSemaphoreCreateRecursiveMutexStatic returns its parameter
|
||||
// // which we checked above to be != NULL
|
||||
// }
|
||||
once_data->once_state = INIT;
|
||||
}
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
// Now, we know local mutex is valid, so we use it to guard access to
|
||||
// the once_state
|
||||
|
||||
if (xSemaphoreTakeRecursive(once_data->local_mutex, portMAX_DELAY) !=
|
||||
pdTRUE) {
|
||||
// TODO panic
|
||||
}
|
||||
// cache result to avoid concurrent access
|
||||
uint8_t result = 0;
|
||||
if (once_data->once_state == INIT) {
|
||||
once_data->once_state = TAKEN;
|
||||
result = 1;
|
||||
if (function != NULL) {
|
||||
function();
|
||||
}
|
||||
}
|
||||
if (xSemaphoreGiveRecursive(once_data->local_mutex) != pdTRUE) {
|
||||
// TODO panic
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 only for the first call, further calls return 0
|
||||
*
|
||||
* once_data needs to be initialized to 0 and not be accessed outside of this
|
||||
* function
|
||||
*/
|
||||
uint8_t freertos_simple_once(uint8_t *once_data) {
|
||||
// TODO assert global_once_mutex != NULL
|
||||
|
||||
uint8_t result = 0;
|
||||
// This function is basically a flag stored in once_data, protected by
|
||||
// a critical section
|
||||
// Critical section is ok, because simple arithmetic is bounded in execution time
|
||||
taskENTER_CRITICAL();
|
||||
if (*once_data == 0) {
|
||||
*once_data = 1;
|
||||
result = 1;
|
||||
}
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t freertos_task_priority_max(void) {
|
||||
// -1 is the max to be used per documentation
|
||||
return configMAX_PRIORITIES - 1;
|
||||
}
|
||||
|
||||
// Note char* to keep sizeof correct on any platform
|
||||
void *freertos_create_task_static(TaskFunction_t taskFunction, void *parameter, uint32_t priority,
|
||||
char *task_data, uint32_t task_data_len,
|
||||
char *stack, uint32_t stack_size) {
|
||||
// TODO verify uint32_t vs configSTACK_DEPTH_TYPE
|
||||
if (task_data_len < sizeof(StaticTask_t)) {
|
||||
// printf("freertos_create_task_static: task data needs to be %zu long\n",
|
||||
// sizeof(StaticTask_t));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (priority == UINT32_MAX) {
|
||||
priority = freertos_task_priority_max();
|
||||
}
|
||||
|
||||
if (priority > freertos_task_priority_max()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t stack_size_words = stack_size / sizeof(StackType_t);
|
||||
|
||||
// printf("freertos_create_task_static: Stack: %p %"PRIu32" %zu %zu\n", stack,
|
||||
// stack_size, stack_size_words, sizeof(StackType_t));
|
||||
|
||||
return xTaskCreateStatic(taskFunction, "rust", stack_size_words, parameter, priority,
|
||||
(StackType_t *)stack, (StaticTask_t *)task_data);
|
||||
}
|
||||
|
||||
void *freertos_mutex_create_static(char *mutex_data, uint32_t mutex_data_len) {
|
||||
// printf("freertos_create_mutex_static: create: %p, %"PRIu32"\n", mutex_data,
|
||||
// mutex_data_len);
|
||||
if (mutex_data_len < sizeof(StaticSemaphore_t)) {
|
||||
// TODO panic?
|
||||
// printf("freertos_create_mutex_static: mutex data needs to be %zu long\n",
|
||||
// sizeof(StaticSemaphore_t));
|
||||
return NULL;
|
||||
}
|
||||
return xSemaphoreCreateRecursiveMutexStatic((StaticSemaphore_t *)mutex_data);
|
||||
}
|
||||
|
||||
// TODO: might be the wrong place
|
||||
int freertos_get_sys_error(){
|
||||
return errno;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
|
||||
// Init framework objects, and start an init task.
|
||||
// Init task is created with minimal stack size, so it should be
|
||||
// used to dispatch an actual init task if stack space is needed
|
||||
void freertos_init_and_start_scheduling(TaskFunction_t init_task);
|
||||
+115
-61
@@ -1,83 +1,39 @@
|
||||
/* Standard includes. */
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Scheduler include files. */
|
||||
#include "FreeRTOS.h"
|
||||
#include "freeRTOS_rust_helper.h"
|
||||
#include "semphr.h"
|
||||
#include "task.h"
|
||||
|
||||
void rust_main();
|
||||
|
||||
#include <hardware/interfaces.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void test_hardware() {
|
||||
int fd0 = hw_device_open("uart0", 5);
|
||||
write(fd0, "UART0\n", 6);
|
||||
int fd1 = hw_device_open("uart1", 5);
|
||||
write(fd1, "uart1\n", 6);
|
||||
#include <git_version.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
void rust_main(void);
|
||||
|
||||
uint8_t buffer[255];
|
||||
|
||||
|
||||
// for (int i = 0; i< sizeof(buffer); i++) {
|
||||
// buffer[i] = i;
|
||||
// }
|
||||
|
||||
// write(fd0, buffer, sizeof(buffer));
|
||||
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
write(1, "got:\n", 5);
|
||||
|
||||
int read_bytes = read(fd0, buffer, sizeof(buffer));
|
||||
write(1, buffer, read_bytes);
|
||||
read_bytes = read(fd1, buffer, sizeof(buffer));
|
||||
write(1, buffer, read_bytes);
|
||||
}
|
||||
|
||||
// called to stop execution (either a panic or program ended)
|
||||
// called to stop execution on error
|
||||
// to be implemented by bsp (do not return from it!)
|
||||
void done();
|
||||
|
||||
void init_task(void * _) {
|
||||
(void )_;
|
||||
// printf("Starting Mission\n");
|
||||
|
||||
test_hardware();
|
||||
void done_error();
|
||||
|
||||
void init_task(void *_) {
|
||||
(void)_;
|
||||
rust_main();
|
||||
|
||||
// printf("Started Tasks, deleting init task\n");
|
||||
|
||||
done();
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
|
||||
#define STARTUP_MESSAGE1 "\nROMEO embedded obsw\nRelease: "
|
||||
#define STARTUP_MESSAGE2 "\nBuild time: "
|
||||
|
||||
void mission(void) {
|
||||
|
||||
int taskParameters = 0;
|
||||
write(1, STARTUP_MESSAGE1, strlen(STARTUP_MESSAGE1));
|
||||
write(1, GIT_VERSION_STRING, strlen(GIT_VERSION_STRING));
|
||||
write(1, STARTUP_MESSAGE2, strlen(STARTUP_MESSAGE2));
|
||||
write(1, BUILD_TIME_STRING, strlen(BUILD_TIME_STRING));
|
||||
write(1, "\n", 1);
|
||||
|
||||
// static const size_t stackSizeWords = 102400;
|
||||
// StaticTask_t xTaskBuffer;
|
||||
// StackType_t xStack[stackSizeWords];
|
||||
|
||||
xTaskCreate(init_task, /* The function that implements the task. */
|
||||
"init", /* The text name assigned to the task - for debug only as
|
||||
it is not used by the kernel. */
|
||||
10240, /* The size of the stack to allocate to the task. */
|
||||
&taskParameters, /* The parameter passed to the task - not used in
|
||||
this simple case. */
|
||||
4, /* The priority assigned to the task. */
|
||||
NULL);
|
||||
|
||||
vTaskStartScheduler();
|
||||
freertos_init_and_start_scheduling(init_task);
|
||||
|
||||
/* If all is well, the scheduler will now be running, and the following
|
||||
line will never be reached. If the following line does execute, then
|
||||
@@ -88,7 +44,7 @@ void mission(void) {
|
||||
mode from which main() is called is set in the C start up code and must be
|
||||
a privileged mode (not user mode). */
|
||||
|
||||
done();
|
||||
done_error();
|
||||
|
||||
for (;;)
|
||||
;
|
||||
@@ -101,6 +57,9 @@ void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName) {
|
||||
(void)pcTaskName;
|
||||
(void)pxTask;
|
||||
|
||||
// TODO panic
|
||||
write(1, "Overflow", 9);
|
||||
|
||||
/* Run time stack overflow checking is performed if
|
||||
configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook
|
||||
function is called if a stack overflow is detected. */
|
||||
@@ -135,3 +94,98 @@ void vAssertCalled(const char *pcFile, unsigned long ulLine) {
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
/* configSUPPORT_STATIC_ALLOCATION is set to 1, so the application must provide
|
||||
an
|
||||
|
||||
implementation of vApplicationGetIdleTaskMemory() to provide the memory that
|
||||
is
|
||||
|
||||
used by the Idle task. */
|
||||
|
||||
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
|
||||
|
||||
StackType_t **ppxIdleTaskStackBuffer,
|
||||
|
||||
uint32_t *pulIdleTaskStackSize)
|
||||
|
||||
{
|
||||
|
||||
/* If the buffers to be provided to the Idle task are declared inside this
|
||||
|
||||
function then they must be declared static - otherwise they will be
|
||||
allocated on
|
||||
|
||||
the stack and so not exists after this function exits. */
|
||||
|
||||
static StaticTask_t xIdleTaskTCB;
|
||||
|
||||
static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE];
|
||||
|
||||
/* Pass out a pointer to the StaticTask_t structure in which the Idle task's
|
||||
|
||||
state will be stored. */
|
||||
|
||||
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
|
||||
|
||||
/* Pass out the array that will be used as the Idle task's stack. */
|
||||
|
||||
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
|
||||
|
||||
/* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer.
|
||||
|
||||
Note that, as the array is necessarily of type StackType_t,
|
||||
|
||||
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
|
||||
|
||||
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
/* configSUPPORT_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so
|
||||
the
|
||||
|
||||
application must provide an implementation of
|
||||
vApplicationGetTimerTaskMemory()
|
||||
|
||||
to provide the memory that is used by the Timer service task. */
|
||||
|
||||
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
|
||||
|
||||
StackType_t **ppxTimerTaskStackBuffer,
|
||||
|
||||
uint32_t *pulTimerTaskStackSize)
|
||||
|
||||
{
|
||||
|
||||
/* If the buffers to be provided to the Timer task are declared inside this
|
||||
|
||||
function then they must be declared static - otherwise they will be
|
||||
allocated on
|
||||
|
||||
the stack and so not exists after this function exits. */
|
||||
|
||||
static StaticTask_t xTimerTaskTCB;
|
||||
|
||||
static StackType_t uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH];
|
||||
|
||||
/* Pass out a pointer to the StaticTask_t structure in which the Timer
|
||||
|
||||
task's state will be stored. */
|
||||
|
||||
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
|
||||
|
||||
/* Pass out the array that will be used as the Timer task's stack. */
|
||||
|
||||
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
|
||||
|
||||
/* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
|
||||
|
||||
Note that, as the array is necessarily of type StackType_t,
|
||||
|
||||
configTIMER_TASK_STACK_DEPTH is specified in words, not bytes. */
|
||||
|
||||
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
|
||||
}
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[target.armv7a-none-eabihf]
|
||||
rustflags = ['-A', 'explicit_builtin_cfgs_in_flags','--cfg', 'target_env="newlib"'] # We use gcc/newlib to link this lib crate
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
|
||||
|
||||
#TODO can we get CMake to configure cmake --build --clean to run cargo clean?
|
||||
#TODO look into corrosion cmake plugin
|
||||
|
||||
if (${CMAKE_CROSSCOMPILING})
|
||||
@@ -16,6 +15,8 @@ if (${CMAKE_CROSSCOMPILING})
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES ${CMAKE_CURRENT_SOURCE_DIR}/target/${CMAKE_SYSTEM_PROCESSOR}/$<IF:$<CONFIG:Release>,release,debug>/)
|
||||
|
||||
add_library(mission_rust INTERFACE)
|
||||
|
||||
add_dependencies(mission_rust mission_rust_internal)
|
||||
@@ -30,6 +31,8 @@ else()
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES ${CMAKE_CURRENT_SOURCE_DIR}/target/$<IF:$<CONFIG:Release>,release,debug>/)
|
||||
|
||||
add_library(mission_rust INTERFACE)
|
||||
|
||||
add_dependencies(mission_rust mission_rust_internal)
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
The framework is meant to be used without dynamic allocation. Currently the only supported RTOS is implemented in C, which adds additional constraints.
|
||||
|
||||
# Static allocation and rust
|
||||
|
||||
As far as rust goes, static allocation and multithreading with a C RTOS is not possible generally. No allocation means that almost all data will be located on the stack. References to data on the stack are per design unsafe in the most wide sense of the word. First, because they are on the stack which will be cleared after a function defining data returns. Secondly, rust specifies data to be ignorant of their location in memory, that is data can be moved in memory without any possibility of hooks which could update foreign references.
|
||||
|
||||
In a multithreaded software, references to data need to be passed to enable communication between objects, to be able to execute a task (which requires passing a reference to the task's data (which contains information on other tasks/data) to the RTOS), to send messages (passing the reference to the Queue, however encapsulated, is a reference to the queue which needs to be located somewhere) or to acess shared data (mutexes, same as with queues).
|
||||
All of these communication techniques are essential for this framework, so solutions need to be provided to be able to write safe code.
|
||||
|
||||
While statically allocating all (shared) data, using global `'static` variables putting them into static memory instead of the stack, is generally possible and might be a possible solution to static allocation, it is not consistent with either the object oriented style of this framework, nor with general rust coding style (which discourages static data).
|
||||
|
||||
# The framework's approach
|
||||
|
||||
## Task Executor
|
||||
|
||||
Central element in running multithreaded is the task executor. It is borrowing references to all data to be used during runtime. This way, the references are guaranteed to be valid for the lifetime of the executor. This can be seen as 'pinning' the objects on the stack to fixed adresses.
|
||||
By then coupling the availability of tasks to the executor (dropping the executor stops all tasks), the references can be guaranteed to be valid for the whole time tasks are available.
|
||||
|
||||
The step where references to other objects are stored in structs is called initialization. This is (enforced by the compiler via layout of the corresponding functions) the only time where access to other objects is granted and references can be stored.
|
||||
The initialization is performed by the task executor which controls that only pinned objects are given access to other pinned objects. Pinned objects are given an API to access other pinned objects (provided by an `ObjectManager`) as well as a token. The token can be passed to functions which will return references to objects to be stored by the calling object. This way, these references can only be created during the initialization.
|
||||
|
||||
For an production software, as soon as all tasks are started, the initial task is stopped/deleted. As such, there is no way (and no intention) for the multithreading to ever stop. In that case, the references shared during initialization will be valid for the whole runtime of the program.
|
||||
|
||||
As there might be use cases, for example in test cases, where multithreading is to be stopped, additional safeguards are implemented to ensure that the references shared during initialization are invalidated or their use is restricted. As running outside of the multithreaded environment is not meant for production, failing without corruption, ie panicking, is an acceptable way out. This is implemented by using a global semaphore indicating if the thread executor is alive. If that is not the case, all access to shared ressources will result in a panic. This adds an additional overhead to all access to shared data, namely checking the semaphore.
|
||||
|
||||
## Shared references
|
||||
|
||||
To be able to implement aforementioned safeguards, access to references is guarded by the framework.
|
||||
|
||||
The only time where references to other objects can be acquired is the initialization step performed by the task executor. As the task executor borrows all objects mutably, no references (mutable or not) to other objects, can be stored within any object.
|
||||
|
||||
Access to the other objects is granted via an object manager implementation, which will provide other objects as a nonmutable reference. Again, this reference can not be stored, only queried, without violating the borrow checker.
|
||||
|
||||
The reference obtained by the object manager is typed as `dyn SystemObjectIF`, so the ways to obtain references is governed by this trait and its super traits. These traits only offer threadsafe APIs. Additionally, these APIs require a token which will be passed by the task executor so they can only be used during initialization.
|
||||
|
||||
Those references are either protected by a mutex or implemented using queues, which are the two primitives used to implement thread safety. As such, all access to shared references must use either the mutex or the queue API which is protected by an additional semaphore as described above.
|
||||
|
||||
In some cases, smart pointers are used. These store raw pointers to other objects offering a threadsafe API. As dereferencing such a pointer is only allowed when objects are pinned, the dereferencing is protected by the additional semaphore as well.
|
||||
|
||||
## RTOS Metadata
|
||||
|
||||
RTOS metadata is the data the RTOS needs to work on its provided primitives such as tasks, queues and mutexes. Those come in two variants, statically sized, which here are called descriptors, and dynamic data. Descriptors have a size known at compile time which is the same for all instances of the primitive. Dynamic data is for instance the backend of a queue or the stack of a task. These do generally differ in size for the different instances. Keeping with the general theme of object orientation, the dynamic information is encapsulated within the structs abstracting the RTOS primitives.
|
||||
|
||||
### Descriptors
|
||||
|
||||
Two options exist for storing the descriptors. Either they are stored in a preallocated static C array, or they are stored in memory allocated as part of the corresponding struct.
|
||||
|
||||
The first option has the advantage that in C, the size of the descriptors is known at compile time (it is determined by the RTOS configuration which is implemented in C macros). Its disadvantage is that the size of the array needs to be adapted to the actual number ob instances used by the RTOS, which might not be trivially determined except for running the software.
|
||||
|
||||
The second option does not have this disadvantage, as the memory is provided by the user of the API. The disadvantage here is that the size of the data needs to be encoded manually for the selected configuration of the RTOS, which again can only be verified during runtime, when rust and C interact.
|
||||
|
||||
Both solutions lead to a detection of the configuration error (too few descriptors preallocated, too little memory allocated) only during runtime. As the configuration of the RTOS is expected to be more stable than the number of primitive instances, the second option is implemented.
|
||||
|
||||
### Dynamic data
|
||||
|
||||
Dynamic data is allocated on the stack as part of the encapsulating struct. Having dynamic data preallocated is not trivial as the actual size is determined by the instantiation of the corresponding struct, which is done at runtime.
|
||||
|
||||
### Passing to the RTOS
|
||||
|
||||
The metadata should only passed to the RTOS when the references are fixed, that is when the task executor is constructed. To make sure that no uninitialized primitives are used, they are created in an invalid state within `new()` of the encapsulating struct. Using an uninitialized struct does fail mostly silently, as this could be happen during runtime, when no panic is allowed.
|
||||
|
||||
As the structs are used to facilitate inter task communication, the initialization is hidden in the call which copies the contained reference out to the using object. That way, structs which are used by other tasks are guaranteed to be initialized, and uninitialized structs can only be used locally, so they do not need to be protected by the primitives at all.
|
||||
|
||||
The handling of the failure is delegated to the structs using the primitives, as the 'correct' way to fail is dependent on the usage:
|
||||
|
||||
* Uninitialized `MessageQueue`s will return empty from `read()` calls
|
||||
* Uninitialized `Mutex`es [TBCoded] will behave nominally, but not lock any actual mutex
|
||||
* Uninitialized `OwnedDataset`s will behave nominally, but not lock any mutex (which is uninitialized)
|
||||
* Uninitialized `ReferencedDataset`s will return default data on `read()` and do nothing on `commit()`
|
||||
|
||||
## Smart pointers
|
||||
|
||||
Datasets are smart pointers to T, with a read/commit API. Both a mutex and the threading semaphore protect each call. Clones (`ReferencedDataset`) are created invalid and can only made valid during init using the init token.
|
||||
Can be compared to the mutex in rust std with a slightly different API.
|
||||
|
||||
Stores are a statically preallocated slotted allocation scheme:
|
||||
StoreAccessor is a smart pointer to the Store, offering same API as Store. Internally, it dereferences a raw pointer in each call, protected by the threading semaphore. Store in turn protects a shared backend with a mutex. Acessors can only be obtained during init. Together, StoreAccessor and Store are an thread safe allocator to the backend.
|
||||
StoreSlots are smart pointers to memory allocated in a store backend. Can be created/allocated during runtime.
|
||||
@@ -0,0 +1,35 @@
|
||||
use crate::fsrc::dh::DeviceCom;
|
||||
|
||||
pub struct EchoHandler {
|
||||
pub buffer: [u8; 10],
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Command(pub u8);
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Reply(pub u8);
|
||||
|
||||
impl DeviceCom for EchoHandler {
|
||||
type DeviceCommand = Command;
|
||||
type DeviceReply = Reply;
|
||||
|
||||
fn get_rx_buffer(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
fn build_command<'a>(command: Self::DeviceCommand, tx_buffer: &'a mut [u8]) -> &'a [u8] {
|
||||
tx_buffer[0] = command.0;
|
||||
&tx_buffer[0..1]
|
||||
}
|
||||
|
||||
fn parse_device_reply_and_return_remainder<'a>(
|
||||
data: &'a [u8],
|
||||
) -> (Option<Self::DeviceReply>, &'a [u8]) {
|
||||
if data.len() == 0 {
|
||||
return (None, data);
|
||||
}
|
||||
|
||||
(Some(Reply(data[0])), &data[1..])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use core::fmt::Write;
|
||||
use core::{fmt::Display, time::Duration};
|
||||
|
||||
use crate::{
|
||||
fsrc::{
|
||||
dh::DeviceCom,
|
||||
osal::{
|
||||
self,
|
||||
io::{HardwareInterface, Read, Write as DeviceWrite},
|
||||
},
|
||||
},
|
||||
sifln,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
IoError(osal::io::Error),
|
||||
ToDo
|
||||
}
|
||||
|
||||
impl From<osal::io::Error> for Error {
|
||||
fn from(value: osal::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "😢")?;
|
||||
match self {
|
||||
Error::IoError(os_error) => write!(f, "Io::{}", os_error),
|
||||
_ => write!(f, "not implemented"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
pub struct DeviceHandlerDebugger<'a, T: DeviceCom> {
|
||||
implementation: T,
|
||||
init_commands: &'a [<T as DeviceCom>::DeviceCommand],
|
||||
periodic_commands: &'a [<T as DeviceCom>::DeviceCommand],
|
||||
wait_until_read: Duration,
|
||||
wait_after_read: Duration,
|
||||
interface: HardwareInterface,
|
||||
}
|
||||
|
||||
impl<'a, T: DeviceCom> DeviceHandlerDebugger<'a, T> {
|
||||
pub fn new(
|
||||
handler: T,
|
||||
interface: HardwareInterface,
|
||||
init_commands: &'a [<T as DeviceCom>::DeviceCommand],
|
||||
periodic_commands: &'a [<T as DeviceCom>::DeviceCommand],
|
||||
period: Duration,
|
||||
mut read_percentage: f32,
|
||||
) -> Self {
|
||||
if read_percentage > 1.0 {
|
||||
read_percentage = 1.0;
|
||||
}
|
||||
|
||||
let period_ms = period.as_millis() as f32;
|
||||
let wait_until_read_ms = period_ms * read_percentage;
|
||||
let wait_until_read_ms = wait_until_read_ms as u32;
|
||||
let period_ms = period_ms as u32;
|
||||
let wait_after_read_ms = period_ms - wait_until_read_ms;
|
||||
|
||||
let wait_until_read = Duration::from_millis(wait_until_read_ms as u64);
|
||||
let wait_after_read = Duration::from_millis(wait_after_read_ms as u64);
|
||||
|
||||
Self {
|
||||
implementation: handler,
|
||||
interface,
|
||||
init_commands,
|
||||
periodic_commands,
|
||||
wait_until_read,
|
||||
wait_after_read,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_one_iteration(&mut self, command: T::DeviceCommand) -> Result<()> {
|
||||
let tx_buffer = self.implementation.get_tx_buffer();
|
||||
let binary_command = T::build_command(command, tx_buffer);
|
||||
self.interface.write(binary_command)?;
|
||||
sifln!("sent {binary_command:?}");
|
||||
osal::thread::current().delay(self.wait_until_read);
|
||||
let rx_buffer = self.implementation.get_rx_buffer();
|
||||
let rx_buffer = self.interface.read(rx_buffer)?;
|
||||
let (reply, remainder) = T::parse_device_reply_and_return_remainder(rx_buffer);
|
||||
sifln!("got reply: {reply:?}, remainder {remainder:?}");
|
||||
osal::thread::current().delay(self.wait_after_read);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
for command in self.init_commands.iter() {
|
||||
self.run_one_iteration(*command)?;
|
||||
}
|
||||
|
||||
if self.periodic_commands.len() == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
loop {
|
||||
for command in self.periodic_commands.iter() {
|
||||
self.run_one_iteration(*command)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
pub mod debug;
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
|
||||
pub trait DeviceCom{
|
||||
type DeviceCommand: Copy + Debug;
|
||||
type DeviceReply: Copy + Debug;
|
||||
|
||||
fn build_command<'a>(command: Self::DeviceCommand, tx_buffer: &'a mut [u8]) -> &'a [u8];
|
||||
|
||||
fn get_rx_buffer(&mut self) -> & mut [u8];
|
||||
|
||||
fn get_tx_buffer(&mut self) -> &mut [u8] {
|
||||
self.get_rx_buffer()
|
||||
}
|
||||
|
||||
fn parse_device_reply_and_return_remainder<'a>(
|
||||
data: &'a [u8],
|
||||
) -> (Option<Self::DeviceReply>, &'a [u8]);
|
||||
|
||||
// Advance State Machine?
|
||||
// Write into Datapool
|
||||
// fn handle_reply(&mut self, reply: Self::DeviceReply);
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
//TODO control visibility of internal structs
|
||||
|
||||
pub mod sif;
|
||||
pub mod queues;
|
||||
//pub mod queues;
|
||||
pub mod osal;
|
||||
pub mod tasks;
|
||||
pub mod objectmanager;
|
||||
pub mod datasets;
|
||||
pub mod store;
|
||||
mod mutex;
|
||||
pub mod introspection;
|
||||
//pub mod tasks;
|
||||
//pub mod objectmanager;
|
||||
//pub mod datasets;
|
||||
//pub mod store;
|
||||
//pub mod mutex;
|
||||
pub mod introspection;
|
||||
pub mod dh;
|
||||
@@ -1,88 +0,0 @@
|
||||
use crate::check_global_threading_available;
|
||||
|
||||
use super::osal;
|
||||
|
||||
// TODO we do not discern between a mutex (holding the descriptor) and a clone (pointing to the
|
||||
// original descriptor), waisting some memory
|
||||
|
||||
pub struct RawMutex {
|
||||
handle: Option<*const core::ffi::c_void>,
|
||||
descriptor: [u8; 10], //TODO which size?
|
||||
}
|
||||
|
||||
pub struct RawMutexGuard {
|
||||
handle: *const core::ffi::c_void,
|
||||
}
|
||||
|
||||
impl Drop for RawMutexGuard {
|
||||
fn drop(&mut self) {
|
||||
check_global_threading_available!(); // tell me when you reach this one!
|
||||
// We use nullptr as marker for an uninitialized mutex, which we
|
||||
// must not give
|
||||
if self.handle != 0 as *const core::ffi::c_void {
|
||||
unsafe { osal::give_mutex(self.handle) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RawMutex {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
handle: None,
|
||||
descriptor: [0; 10],
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self) {
|
||||
// check_global_threading_available!(); we have a token, so this is redundant
|
||||
if self.handle != None {
|
||||
return;
|
||||
}
|
||||
// TODO verify handle size
|
||||
let handle = unsafe { osal::create_mutex() };
|
||||
if handle == 0 as *const core::ffi::c_void {
|
||||
panic!("Could not create mutex")
|
||||
}
|
||||
self.handle = Some(handle);
|
||||
}
|
||||
|
||||
pub fn take(&self) -> Result<RawMutexGuard, ()> {
|
||||
check_global_threading_available!();
|
||||
if let Some(handle) = self.handle {
|
||||
return match unsafe { osal::take_mutex(handle) } {
|
||||
1 => Ok(RawMutexGuard { handle: handle }),
|
||||
_ => Err(()), // Only when timeout expired (we have none) TODO error code
|
||||
};
|
||||
} else {
|
||||
// nullptr makes the guard do nothing
|
||||
Ok(RawMutexGuard {
|
||||
handle: 0 as *const core::ffi::c_void,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO protect with token
|
||||
pub fn clone(&self) -> Self {
|
||||
let mut_self = self as *const Self as *mut Self; //oh look, a C developer wrote this
|
||||
unsafe { (*mut_self).initialize() }; //TODO this might be safe (we are in init), but does not look very good
|
||||
// At this point self.handle must be valid, initialize would have panicked otherwise
|
||||
Self {
|
||||
handle: self.handle,
|
||||
descriptor: [0; 10],
|
||||
}
|
||||
}
|
||||
|
||||
// Mutex guard takes care of this in its drop
|
||||
// Do not offer this in API to avoid duplicate give
|
||||
// pub fn give(&self) -> Result<(),()> {
|
||||
// osal::check_global_threading_available();
|
||||
// let handle = match self.handle {
|
||||
// None => return Ok(()),
|
||||
// Some(handle) => handle
|
||||
// };
|
||||
// match unsafe {osal::give_mutex(handle)} {
|
||||
// 1 => Ok(()),
|
||||
// _ => Err(()) //TODO error code
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub(crate) mod ffi;
|
||||
pub mod sync;
|
||||
pub mod once;
|
||||
pub mod thread;
|
||||
pub mod queue;
|
||||
pub mod error;
|
||||
pub mod io;
|
||||
@@ -0,0 +1,161 @@
|
||||
use crate::fsrc::osal::ffi;
|
||||
|
||||
// wrap helper shim, we use c to call errno to make sure we do it right
|
||||
pub fn errno() -> i32 {
|
||||
unsafe { ffi::freertos_get_sys_error() }
|
||||
}
|
||||
|
||||
// This is a merge of newlib and x86_64-glibc-linux
|
||||
#[derive(Debug,PartialEq)]
|
||||
pub enum OsError {
|
||||
ArgumentListTooLong,
|
||||
PermissionDenied,
|
||||
AddressAlreadyInUse,
|
||||
AddressNotAvailable,
|
||||
AdvertiseError,
|
||||
AddressFamilyNotSupportedByProtocol,
|
||||
OperationWouldBlock,
|
||||
OperationAlreadyInProgress,
|
||||
InvalidExchange,
|
||||
BadFileDescriptor,
|
||||
FileDescriptorInBadState,
|
||||
BadMessage,
|
||||
InvalidRequestDescriptor,
|
||||
InvalidRequestCode,
|
||||
InvalidSlot,
|
||||
BadFontFileFormat,
|
||||
DeviceOrResourceBusy,
|
||||
OperationCanceled,
|
||||
FilenameExistsWithDifferentCase,
|
||||
NoChildProcesses,
|
||||
ChannelNumberOutOfRange,
|
||||
CommunicationErrorOnSend,
|
||||
SoftwareCausedConnectionAbort,
|
||||
ConnectionRefused,
|
||||
ConnectionResetByPeer,
|
||||
Deadlock,
|
||||
DestinationAddressRequired,
|
||||
NumericalArgumentOutOfDomain,
|
||||
FsError,
|
||||
DiskQuotaExceeded,
|
||||
FileExists,
|
||||
BadAddress,
|
||||
FileTooLarge,
|
||||
InappropriateFileTypeOrFormat,
|
||||
HostIsDown,
|
||||
NoRouteToHost,
|
||||
MemoryPageHasHardwareError,
|
||||
IdentifierRemoved,
|
||||
InvalidOrIncompleteMultibyteOrWideCharacter,
|
||||
OperationNowInProgress,
|
||||
InterruptedSystemCall,
|
||||
InvalidArgument,
|
||||
InputOutputError,
|
||||
TransportEndpointIsAlreadyConnected,
|
||||
IsADirectory,
|
||||
IsANamedTypeFile,
|
||||
KeyHasExpired,
|
||||
KeyWasRejectedByService,
|
||||
KeyHasBeenRevoked,
|
||||
Level2Halted,
|
||||
Level2NotSynchronized,
|
||||
Level3Halted,
|
||||
Level3Reset,
|
||||
InodeIsRemote,
|
||||
CanNotAccessANeededSharedLibrary,
|
||||
AccessingACorruptedSharedLibrary,
|
||||
CannotExecASharedLibraryDirectly,
|
||||
AttemptingToLinkInTooManySharedLibraries,
|
||||
LibSectionInAOutCorrupted,
|
||||
LinkNumberOutOfRange,
|
||||
TooManySymbolicLinks,
|
||||
WrongMediumType,
|
||||
TooManyOpenFiles,
|
||||
TooManyLinks,
|
||||
MessageTooLong,
|
||||
MultihopAttempted,
|
||||
FileNameTooLong,
|
||||
NoXenixSemaphoresAvailable,
|
||||
NetworkIsDown,
|
||||
NetworkDroppedConnectionOnReset,
|
||||
HostIsUnreachable,
|
||||
TooManyOpenFilesInSystem,
|
||||
NoMoreFiles,
|
||||
NoAnode,
|
||||
NoBufferSpaceAvailable,
|
||||
NoCsiStructureAvailable,
|
||||
NoDataAvailable,
|
||||
NoSuchDevice,
|
||||
NoSuchFileOrDirectory,
|
||||
ExecFormatError,
|
||||
RequiredKeyNotAvailable,
|
||||
NoLocksAvailable,
|
||||
LinkHasBeenSevered,
|
||||
NoMediumFound,
|
||||
CannotAllocateMemory,
|
||||
NoMessageOfDesiredType,
|
||||
MachineIsNotOnTheNetwork,
|
||||
PackageNotInstalled,
|
||||
ProtocolNotAvailable,
|
||||
NoShare,
|
||||
NoSpaceLeftOnDevice,
|
||||
OutOfStreamsResources,
|
||||
DeviceNotAStream,
|
||||
FunctionNotImplemented,
|
||||
BlockDeviceRequired,
|
||||
TransportEndpointIsNotConnected,
|
||||
NotADirectory,
|
||||
DirectoryNotEmpty,
|
||||
NotAXenixNamedTypeFile,
|
||||
StateNotRecoverable,
|
||||
SocketOperationOnNonSocket,
|
||||
OperationNotSupported,
|
||||
NotACharacterDevice,
|
||||
NameNotUniqueOnNetwork,
|
||||
NoSuchDeviceOrAddress,
|
||||
ValueTooLargeForDefinedDataType,
|
||||
OwnerDied,
|
||||
OperationNotPermitted,
|
||||
ProtocolFamilyNotSupported,
|
||||
BrokenPipe,
|
||||
LimitOnNewProcessesReached,
|
||||
ProtocolError,
|
||||
ProtocolNotSupported,
|
||||
ProtocolWrongTypeForSocket,
|
||||
NumericalResultOutOfRange,
|
||||
RemoteAddressChanged,
|
||||
ObjectIsRemote,
|
||||
RemoteIOError,
|
||||
InterruptedSystemCallShouldBeRestarted,
|
||||
OperationNotPossibleDueToRfKill,
|
||||
ReadOnlyFileSystem,
|
||||
CannotSendAfterShutdown,
|
||||
SocketTypeNotSupported,
|
||||
IllegalSeek,
|
||||
NoSuchProcess,
|
||||
SrmountError,
|
||||
StaleFileHandle,
|
||||
StreamsPipeError,
|
||||
TimerExpired,
|
||||
ConnectionTimedOut,
|
||||
TooManyReferencesCannotSplice,
|
||||
TextFileBusy,
|
||||
StructureNeedsCleaning,
|
||||
ProtocolDriverNotAttached,
|
||||
TooManyUsers,
|
||||
InvalidCrossDeviceLink,
|
||||
ExchangeFull,
|
||||
Other(i32),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
|
||||
#[cfg(target_env = "newlib")]
|
||||
mod newlib;
|
||||
|
||||
// #[cfg(target_os = "linux")]
|
||||
// pub use linux::*;
|
||||
|
||||
// #[cfg(target_env = "newlib")]
|
||||
// pub use linux::*;
|
||||
@@ -0,0 +1,425 @@
|
||||
use core::fmt;
|
||||
|
||||
mod libc {
|
||||
// taken from a glibc x86_64 linux:
|
||||
use core::ffi::c_int;
|
||||
pub const EPERM: c_int = 1;
|
||||
pub const ENOENT: c_int = 2;
|
||||
pub const ESRCH: c_int = 3;
|
||||
pub const EINTR: c_int = 4;
|
||||
pub const EIO: c_int = 5;
|
||||
pub const ENXIO: c_int = 6;
|
||||
pub const E2BIG: c_int = 7;
|
||||
pub const ENOEXEC: c_int = 8;
|
||||
pub const EBADF: c_int = 9;
|
||||
pub const ECHILD: c_int = 10;
|
||||
pub const EAGAIN: c_int = 11;
|
||||
#[allow(unused)]
|
||||
pub const EWOULDBLOCK: c_int = 11; // equals EAGAIN
|
||||
pub const ENOMEM: c_int = 12;
|
||||
pub const EACCES: c_int = 13;
|
||||
pub const EFAULT: c_int = 14;
|
||||
pub const ENOTBLK: c_int = 15;
|
||||
pub const EBUSY: c_int = 16;
|
||||
pub const EEXIST: c_int = 17;
|
||||
pub const EXDEV: c_int = 18;
|
||||
pub const ENODEV: c_int = 19;
|
||||
pub const ENOTDIR: c_int = 20;
|
||||
pub const EISDIR: c_int = 21;
|
||||
pub const EINVAL: c_int = 22;
|
||||
pub const ENFILE: c_int = 23;
|
||||
pub const EMFILE: c_int = 24;
|
||||
pub const ENOTTY: c_int = 25;
|
||||
pub const ETXTBSY: c_int = 26;
|
||||
pub const EFBIG: c_int = 27;
|
||||
pub const ENOSPC: c_int = 28;
|
||||
pub const ESPIPE: c_int = 29;
|
||||
pub const EROFS: c_int = 30;
|
||||
pub const EMLINK: c_int = 31;
|
||||
pub const EPIPE: c_int = 32;
|
||||
pub const EDOM: c_int = 33;
|
||||
pub const ERANGE: c_int = 34;
|
||||
pub const EDEADLOCK: c_int = 35;
|
||||
pub const ENAMETOOLONG: c_int = 36;
|
||||
pub const ENOLCK: c_int = 37;
|
||||
pub const ENOSYS: c_int = 38;
|
||||
pub const ENOTEMPTY: c_int = 39;
|
||||
pub const ELOOP: c_int = 40;
|
||||
pub const ENOMSG: c_int = 42;
|
||||
pub const EIDRM: c_int = 43;
|
||||
pub const ECHRNG: c_int = 44;
|
||||
pub const ELNRNG: c_int = 48;
|
||||
pub const EUNATCH: c_int = 49;
|
||||
pub const ENOCSI: c_int = 50;
|
||||
pub const EBADE: c_int = 52;
|
||||
pub const EBADR: c_int = 53;
|
||||
pub const EXFULL: c_int = 54;
|
||||
pub const ENOANO: c_int = 55;
|
||||
pub const EBADRQC: c_int = 56;
|
||||
pub const EBADSLT: c_int = 57;
|
||||
pub const EBFONT: c_int = 59;
|
||||
pub const ENOSTR: c_int = 60;
|
||||
pub const ENODATA: c_int = 61;
|
||||
pub const ETIME: c_int = 62;
|
||||
pub const ENOSR: c_int = 63;
|
||||
pub const ENONET: c_int = 64;
|
||||
pub const ENOPKG: c_int = 65;
|
||||
pub const EREMOTE: c_int = 66;
|
||||
pub const ENOLINK: c_int = 67;
|
||||
pub const EADV: c_int = 68;
|
||||
pub const ESRMNT: c_int = 69;
|
||||
pub const ECOMM: c_int = 70;
|
||||
pub const EPROTO: c_int = 71;
|
||||
pub const EMULTIHOP: c_int = 72;
|
||||
pub const EDOTDOT: c_int = 73;
|
||||
pub const EBADMSG: c_int = 74;
|
||||
pub const EOVERFLOW: c_int = 75;
|
||||
pub const ENOTUNIQ: c_int = 76;
|
||||
pub const EBADFD: c_int = 77;
|
||||
pub const EREMCHG: c_int = 78;
|
||||
pub const ELIBACC: c_int = 79;
|
||||
pub const ELIBBAD: c_int = 80;
|
||||
pub const ELIBSCN: c_int = 81;
|
||||
pub const ELIBMAX: c_int = 82;
|
||||
pub const ELIBEXEC: c_int = 83;
|
||||
pub const EILSEQ: c_int = 84;
|
||||
pub const ERESTART: c_int = 85;
|
||||
pub const ESTRPIPE: c_int = 86;
|
||||
pub const EUSERS: c_int = 87;
|
||||
pub const ENOTSOCK: c_int = 88;
|
||||
pub const EDESTADDRREQ: c_int = 89;
|
||||
pub const EMSGSIZE: c_int = 90;
|
||||
pub const EPROTOTYPE: c_int = 91;
|
||||
pub const ENOPROTOOPT: c_int = 92;
|
||||
pub const EPROTONOSUPPORT: c_int = 93;
|
||||
pub const ESOCKTNOSUPPORT: c_int = 94;
|
||||
pub const EOPNOTSUPP: c_int = 95;
|
||||
#[allow(unused)]
|
||||
pub const ENOTSUP: c_int = 95; // equals EOPNOTSUPP
|
||||
pub const EPFNOSUPPORT: c_int = 96;
|
||||
pub const EAFNOSUPPORT: c_int = 97;
|
||||
pub const EADDRINUSE: c_int = 98;
|
||||
pub const EADDRNOTAVAIL: c_int = 99;
|
||||
pub const ENETDOWN: c_int = 100;
|
||||
pub const ENETUNREACH: c_int = 101;
|
||||
pub const ENETRESET: c_int = 102;
|
||||
pub const ECONNABORTED: c_int = 103;
|
||||
pub const ECONNRESET: c_int = 104;
|
||||
pub const ENOBUFS: c_int = 105;
|
||||
pub const EISCONN: c_int = 106;
|
||||
pub const ENOTCONN: c_int = 107;
|
||||
pub const ESHUTDOWN: c_int = 108;
|
||||
pub const ETOOMANYREFS: c_int = 109;
|
||||
pub const ETIMEDOUT: c_int = 110;
|
||||
pub const ECONNREFUSED: c_int = 111;
|
||||
pub const EHOSTDOWN: c_int = 112;
|
||||
pub const EHOSTUNREACH: c_int = 113;
|
||||
pub const EALREADY: c_int = 114;
|
||||
pub const EINPROGRESS: c_int = 115;
|
||||
pub const ESTALE: c_int = 116;
|
||||
pub const EUCLEAN: c_int = 117;
|
||||
pub const ENOTNAM: c_int = 118;
|
||||
pub const ENAVAIL: c_int = 119;
|
||||
pub const EISNAM: c_int = 120;
|
||||
pub const EREMOTEIO: c_int = 121;
|
||||
pub const EDQUOT: c_int = 122;
|
||||
pub const ENOMEDIUM: c_int = 123;
|
||||
pub const EMEDIUMTYPE: c_int = 124;
|
||||
pub const ECANCELED: c_int = 125;
|
||||
pub const ENOKEY: c_int = 126;
|
||||
pub const EKEYEXPIRED: c_int = 127;
|
||||
pub const EKEYREVOKED: c_int = 128;
|
||||
pub const EKEYREJECTED: c_int = 129;
|
||||
pub const EOWNERDEAD: c_int = 130;
|
||||
pub const ENOTRECOVERABLE: c_int = 131;
|
||||
pub const ERFKILL: c_int = 132;
|
||||
pub const EHWPOISON: c_int = 133;
|
||||
}
|
||||
|
||||
|
||||
impl From<i32> for super::OsError {
|
||||
fn from(value: i32) -> Self {
|
||||
use super::OsError::*;
|
||||
match value {
|
||||
libc::EPERM => OperationNotPermitted,
|
||||
libc::ENOENT => NoSuchFileOrDirectory,
|
||||
libc::ESRCH => NoSuchProcess,
|
||||
libc::EINTR => InterruptedSystemCall,
|
||||
libc::EIO => InputOutputError,
|
||||
libc::ENXIO => NoSuchDeviceOrAddress,
|
||||
libc::E2BIG => ArgumentListTooLong,
|
||||
libc::ENOEXEC => ExecFormatError,
|
||||
libc::EBADF => BadFileDescriptor,
|
||||
libc::ECHILD => NoChildProcesses,
|
||||
libc::EAGAIN => OperationWouldBlock,
|
||||
//libc::EWOULDBLOCK => OperationWouldBlock, == EAGAIN
|
||||
libc::ENOMEM => CannotAllocateMemory,
|
||||
libc::EACCES => PermissionDenied,
|
||||
libc::EFAULT => BadAddress,
|
||||
libc::ENOTBLK => BlockDeviceRequired,
|
||||
libc::EBUSY => DeviceOrResourceBusy,
|
||||
libc::EEXIST => FileExists,
|
||||
libc::EXDEV => InvalidCrossDeviceLink,
|
||||
libc::ENODEV => NoSuchDevice,
|
||||
libc::ENOTDIR => NotADirectory,
|
||||
libc::EISDIR => IsADirectory,
|
||||
libc::EINVAL => InvalidArgument,
|
||||
libc::ENFILE => TooManyOpenFilesInSystem,
|
||||
libc::EMFILE => TooManyOpenFiles,
|
||||
libc::ENOTTY => NotACharacterDevice,
|
||||
libc::ETXTBSY => TextFileBusy,
|
||||
libc::EFBIG => FileTooLarge,
|
||||
libc::ENOSPC => NoSpaceLeftOnDevice,
|
||||
libc::ESPIPE => IllegalSeek,
|
||||
libc::EROFS => ReadOnlyFileSystem,
|
||||
libc::EMLINK => TooManyLinks,
|
||||
libc::EPIPE => BrokenPipe,
|
||||
libc::EDOM => NumericalArgumentOutOfDomain,
|
||||
libc::ERANGE => NumericalResultOutOfRange,
|
||||
libc::EDEADLOCK => Deadlock,
|
||||
libc::ENAMETOOLONG => FileNameTooLong,
|
||||
libc::ENOLCK => NoLocksAvailable,
|
||||
libc::ENOSYS => FunctionNotImplemented,
|
||||
libc::ENOTEMPTY => DirectoryNotEmpty,
|
||||
libc::ELOOP => TooManySymbolicLinks,
|
||||
libc::ENOMSG => NoMessageOfDesiredType,
|
||||
libc::EIDRM => IdentifierRemoved,
|
||||
libc::ECHRNG => ChannelNumberOutOfRange,
|
||||
libc::ELNRNG => LinkNumberOutOfRange,
|
||||
libc::EUNATCH => ProtocolDriverNotAttached,
|
||||
libc::ENOCSI => NoCsiStructureAvailable,
|
||||
libc::EBADE => InvalidExchange,
|
||||
libc::EBADR => InvalidRequestDescriptor,
|
||||
libc::EXFULL => ExchangeFull,
|
||||
libc::ENOANO => NoAnode,
|
||||
libc::EBADRQC => InvalidRequestCode,
|
||||
libc::EBADSLT => InvalidSlot,
|
||||
libc::EBFONT => BadFontFileFormat,
|
||||
libc::ENOSTR => DeviceNotAStream,
|
||||
libc::ENODATA => NoDataAvailable,
|
||||
libc::ETIME => TimerExpired,
|
||||
libc::ENOSR => OutOfStreamsResources,
|
||||
libc::ENONET => MachineIsNotOnTheNetwork,
|
||||
libc::ENOPKG => PackageNotInstalled,
|
||||
libc::EREMOTE => ObjectIsRemote,
|
||||
libc::ENOLINK => LinkHasBeenSevered,
|
||||
libc::EADV => AdvertiseError,
|
||||
libc::ESRMNT => SrmountError,
|
||||
libc::ECOMM => CommunicationErrorOnSend,
|
||||
libc::EPROTO => ProtocolError,
|
||||
libc::EMULTIHOP => MultihopAttempted,
|
||||
libc::EDOTDOT => FsError,
|
||||
libc::EBADMSG => BadMessage,
|
||||
libc::EOVERFLOW => ValueTooLargeForDefinedDataType,
|
||||
libc::ENOTUNIQ => NameNotUniqueOnNetwork,
|
||||
libc::EBADFD => FileDescriptorInBadState,
|
||||
libc::EREMCHG => RemoteAddressChanged,
|
||||
libc::ELIBACC => CanNotAccessANeededSharedLibrary,
|
||||
libc::ELIBBAD => AccessingACorruptedSharedLibrary,
|
||||
libc::ELIBSCN => LibSectionInAOutCorrupted,
|
||||
libc::ELIBMAX => AttemptingToLinkInTooManySharedLibraries,
|
||||
libc::ELIBEXEC => CannotExecASharedLibraryDirectly,
|
||||
libc::EILSEQ => InvalidOrIncompleteMultibyteOrWideCharacter,
|
||||
libc::ERESTART => InterruptedSystemCallShouldBeRestarted,
|
||||
libc::ESTRPIPE => StreamsPipeError,
|
||||
libc::EUSERS => TooManyUsers,
|
||||
libc::ENOTSOCK => SocketOperationOnNonSocket,
|
||||
libc::EDESTADDRREQ => DestinationAddressRequired,
|
||||
libc::EMSGSIZE => MessageTooLong,
|
||||
libc::EPROTOTYPE => ProtocolWrongTypeForSocket,
|
||||
libc::ENOPROTOOPT => ProtocolNotAvailable,
|
||||
libc::EPROTONOSUPPORT => ProtocolNotSupported,
|
||||
libc::ESOCKTNOSUPPORT => SocketTypeNotSupported,
|
||||
libc::EOPNOTSUPP => OperationNotSupported,
|
||||
// libc::ENOTSUP => OperationNotSupported, == EOPNOTSUPP
|
||||
libc::EPFNOSUPPORT => ProtocolFamilyNotSupported,
|
||||
libc::EAFNOSUPPORT => AddressFamilyNotSupportedByProtocol,
|
||||
libc::EADDRINUSE => AddressAlreadyInUse,
|
||||
libc::EADDRNOTAVAIL => AddressNotAvailable,
|
||||
libc::ENETDOWN => NetworkIsDown,
|
||||
libc::ENETUNREACH => HostIsUnreachable,
|
||||
libc::ENETRESET => NetworkDroppedConnectionOnReset,
|
||||
libc::ECONNABORTED => SoftwareCausedConnectionAbort,
|
||||
libc::ECONNRESET => ConnectionResetByPeer,
|
||||
libc::ENOBUFS => NoBufferSpaceAvailable,
|
||||
libc::EISCONN => TransportEndpointIsAlreadyConnected,
|
||||
libc::ENOTCONN => TransportEndpointIsNotConnected,
|
||||
libc::ESHUTDOWN => CannotSendAfterShutdown,
|
||||
libc::ETOOMANYREFS => TooManyReferencesCannotSplice,
|
||||
libc::ETIMEDOUT => ConnectionTimedOut,
|
||||
libc::ECONNREFUSED => ConnectionRefused,
|
||||
libc::EHOSTDOWN => HostIsDown,
|
||||
libc::EHOSTUNREACH => NoRouteToHost,
|
||||
libc::EALREADY => OperationAlreadyInProgress,
|
||||
libc::EINPROGRESS => OperationNowInProgress,
|
||||
libc::ESTALE => StaleFileHandle,
|
||||
libc::EUCLEAN => StructureNeedsCleaning,
|
||||
libc::ENOTNAM => NotAXenixNamedTypeFile,
|
||||
libc::ENAVAIL => NoXenixSemaphoresAvailable,
|
||||
libc::EISNAM => IsANamedTypeFile,
|
||||
libc::EREMOTEIO => RemoteIOError,
|
||||
libc::EDQUOT => DiskQuotaExceeded,
|
||||
libc::ENOMEDIUM => NoMediumFound,
|
||||
libc::EMEDIUMTYPE => WrongMediumType,
|
||||
libc::ECANCELED => OperationCanceled,
|
||||
libc::ENOKEY => RequiredKeyNotAvailable,
|
||||
libc::EKEYEXPIRED => KeyHasExpired,
|
||||
libc::EKEYREVOKED => KeyHasBeenRevoked,
|
||||
libc::EKEYREJECTED => KeyWasRejectedByService,
|
||||
libc::EOWNERDEAD => OwnerDied,
|
||||
libc::ENOTRECOVERABLE => StateNotRecoverable,
|
||||
libc::ERFKILL => OperationNotPossibleDueToRfKill,
|
||||
libc::EHWPOISON => MemoryPageHasHardwareError,
|
||||
value => Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for super::OsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use super::OsError::*;
|
||||
match self {
|
||||
OperationNotPermitted => write!(f, "Operation not permitted"),
|
||||
NoSuchFileOrDirectory => write!(f, "No such file or directory"),
|
||||
NoSuchProcess => write!(f, "No such process"),
|
||||
InterruptedSystemCall => write!(f, "Interrupted system call"),
|
||||
InputOutputError => write!(f, "Input/output error"),
|
||||
NoSuchDeviceOrAddress => write!(f, "No such device or address"),
|
||||
ArgumentListTooLong => write!(f, "Argument list too long"),
|
||||
ExecFormatError => write!(f, "Exec format error"),
|
||||
BadFileDescriptor => write!(f, "Bad file descriptor"),
|
||||
NoChildProcesses => write!(f, "No child processes"),
|
||||
OperationWouldBlock => write!(f, "Resource temporarily unavailable"),
|
||||
CannotAllocateMemory => write!(f, "Cannot allocate memory"),
|
||||
PermissionDenied => write!(f, "Permission denied"),
|
||||
BadAddress => write!(f, "Bad address"),
|
||||
BlockDeviceRequired => write!(f, "Block device required"),
|
||||
DeviceOrResourceBusy => write!(f, "Device or resource busy"),
|
||||
FileExists => write!(f, "File exists"),
|
||||
InvalidCrossDeviceLink => write!(f, "Invalid cross-device link"),
|
||||
NoSuchDevice => write!(f, "No such device"),
|
||||
NotADirectory => write!(f, "Not a directory"),
|
||||
IsADirectory => write!(f, "Is a directory"),
|
||||
InvalidArgument => write!(f, "Invalid argument"),
|
||||
TooManyOpenFilesInSystem => write!(f, "Too many open files in system"),
|
||||
TooManyOpenFiles => write!(f, "Too many open files"),
|
||||
NotACharacterDevice => write!(f, "Inappropriate ioctl for device"),
|
||||
TextFileBusy => write!(f, "Text file busy"),
|
||||
FileTooLarge => write!(f, "File too large"),
|
||||
NoSpaceLeftOnDevice => write!(f, "No space left on device"),
|
||||
IllegalSeek => write!(f, "Illegal seek"),
|
||||
ReadOnlyFileSystem => write!(f, "Read-only file system"),
|
||||
TooManyLinks => write!(f, "Too many links"),
|
||||
BrokenPipe => write!(f, "Broken pipe"),
|
||||
NumericalArgumentOutOfDomain => write!(f, "Numerical argument out of domain"),
|
||||
NumericalResultOutOfRange => write!(f, "Numerical result out of range"),
|
||||
Deadlock => write!(f, "Resource deadlock avoided"),
|
||||
FileNameTooLong => write!(f, "File name too long"),
|
||||
NoLocksAvailable => write!(f, "No locks available"),
|
||||
FunctionNotImplemented => write!(f, "Function not implemented"),
|
||||
DirectoryNotEmpty => write!(f, "Directory not empty"),
|
||||
TooManySymbolicLinks => write!(f, "Too many levels of symbolic links"),
|
||||
NoMessageOfDesiredType => write!(f, "No message of desired type"),
|
||||
IdentifierRemoved => write!(f, "Identifier removed"),
|
||||
ChannelNumberOutOfRange => write!(f, "Channel number out of range"),
|
||||
LinkNumberOutOfRange => write!(f, "Link number out of range"),
|
||||
ProtocolDriverNotAttached => write!(f, "Protocol driver not attached"),
|
||||
NoCsiStructureAvailable => write!(f, "No CSI structure available"),
|
||||
InvalidExchange => write!(f, "Invalid exchange"),
|
||||
InvalidRequestDescriptor => write!(f, "Invalid request descriptor"),
|
||||
ExchangeFull => write!(f, "Exchange full"),
|
||||
NoAnode => write!(f, "No anode"),
|
||||
InvalidRequestCode => write!(f, "Invalid request code"),
|
||||
InvalidSlot => write!(f, "Invalid slot"),
|
||||
BadFontFileFormat => write!(f, "Bad font file format"),
|
||||
DeviceNotAStream => write!(f, "Device not a stream"),
|
||||
NoDataAvailable => write!(f, "No data available"),
|
||||
TimerExpired => write!(f, "Timer expired"),
|
||||
OutOfStreamsResources => write!(f, "Out of streams resources"),
|
||||
MachineIsNotOnTheNetwork => write!(f, "Machine is not on the network"),
|
||||
PackageNotInstalled => write!(f, "Package not installed"),
|
||||
ObjectIsRemote => write!(f, "Object is remote"),
|
||||
LinkHasBeenSevered => write!(f, "Link has been severed"),
|
||||
AdvertiseError => write!(f, "Advertise error"),
|
||||
SrmountError => write!(f, "Srmount error"),
|
||||
CommunicationErrorOnSend => write!(f, "Communication error on send"),
|
||||
ProtocolError => write!(f, "Protocol error"),
|
||||
MultihopAttempted => write!(f, "Multihop attempted"),
|
||||
FsError => write!(f, "RFS specific error"),
|
||||
BadMessage => write!(f, "Bad message"),
|
||||
ValueTooLargeForDefinedDataType => write!(f, "Value too large for defined data type"),
|
||||
NameNotUniqueOnNetwork => write!(f, "Name not unique on network"),
|
||||
FileDescriptorInBadState => write!(f, "File descriptor in bad state"),
|
||||
RemoteAddressChanged => write!(f, "Remote address changed"),
|
||||
CanNotAccessANeededSharedLibrary => write!(f, "Can not access a needed shared library"),
|
||||
AccessingACorruptedSharedLibrary => write!(f, "Accessing a corrupted shared library"),
|
||||
LibSectionInAOutCorrupted => write!(f, ".lib section in a.out corrupted"),
|
||||
AttemptingToLinkInTooManySharedLibraries => {
|
||||
write!(f, "Attempting to link in too many shared libraries")
|
||||
}
|
||||
CannotExecASharedLibraryDirectly => write!(f, "Cannot exec a shared library directly"),
|
||||
InvalidOrIncompleteMultibyteOrWideCharacter => {
|
||||
write!(f, "Invalid or incomplete multibyte or wide character")
|
||||
}
|
||||
InterruptedSystemCallShouldBeRestarted => {
|
||||
write!(f, "Interrupted system call should be restarted")
|
||||
}
|
||||
StreamsPipeError => write!(f, "Streams pipe error"),
|
||||
TooManyUsers => write!(f, "Too many users"),
|
||||
SocketOperationOnNonSocket => write!(f, "Socket operation on non-socket"),
|
||||
DestinationAddressRequired => write!(f, "Destination address required"),
|
||||
MessageTooLong => write!(f, "Message too long"),
|
||||
ProtocolWrongTypeForSocket => write!(f, "Protocol wrong type for socket"),
|
||||
ProtocolNotAvailable => write!(f, "Protocol not available"),
|
||||
ProtocolNotSupported => write!(f, "Protocol not supported"),
|
||||
SocketTypeNotSupported => write!(f, "Socket type not supported"),
|
||||
OperationNotSupported => write!(f, "Operation not supported"),
|
||||
ProtocolFamilyNotSupported => write!(f, "Protocol family not supported"),
|
||||
AddressFamilyNotSupportedByProtocol => {
|
||||
write!(f, "Address family not supported by protocol")
|
||||
}
|
||||
AddressAlreadyInUse => write!(f, "Address already in use"),
|
||||
AddressNotAvailable => write!(f, "Cannot assign requested address"),
|
||||
NetworkIsDown => write!(f, "Network is down"),
|
||||
HostIsUnreachable => write!(f, "Network is unreachable"),
|
||||
NetworkDroppedConnectionOnReset => write!(f, "Network dropped connection on reset"),
|
||||
SoftwareCausedConnectionAbort => write!(f, "Software caused connection abort"),
|
||||
ConnectionResetByPeer => write!(f, "Connection reset by peer"),
|
||||
NoBufferSpaceAvailable => write!(f, "No buffer space available"),
|
||||
TransportEndpointIsAlreadyConnected => {
|
||||
write!(f, "Transport endpoint is already connected")
|
||||
}
|
||||
TransportEndpointIsNotConnected => write!(f, "Transport endpoint is not connected"),
|
||||
CannotSendAfterShutdown => {
|
||||
write!(f, "Cannot send after transport endpoint shutdown")
|
||||
}
|
||||
TooManyReferencesCannotSplice => write!(f, "Too many references: cannot splice"),
|
||||
ConnectionTimedOut => write!(f, "Connection timed out"),
|
||||
ConnectionRefused => write!(f, "Connection refused"),
|
||||
HostIsDown => write!(f, "Host is down"),
|
||||
NoRouteToHost => write!(f, "No route to host"),
|
||||
OperationAlreadyInProgress => write!(f, "Operation already in progress"),
|
||||
OperationNowInProgress => write!(f, "Operation now in progress"),
|
||||
StaleFileHandle => write!(f, "Stale file handle"),
|
||||
StructureNeedsCleaning => write!(f, "Structure needs cleaning"),
|
||||
NotAXenixNamedTypeFile => write!(f, "Not a XENIX named type file"),
|
||||
NoXenixSemaphoresAvailable => write!(f, "No XENIX semaphores available"),
|
||||
IsANamedTypeFile => write!(f, "Is a named type file"),
|
||||
RemoteIOError => write!(f, "Remote I/O error"),
|
||||
DiskQuotaExceeded => write!(f, "Disk quota exceeded"),
|
||||
NoMediumFound => write!(f, "No medium found"),
|
||||
WrongMediumType => write!(f, "Wrong medium type"),
|
||||
OperationCanceled => write!(f, "Operation canceled"),
|
||||
RequiredKeyNotAvailable => write!(f, "Required key not available"),
|
||||
KeyHasExpired => write!(f, "Key has expired"),
|
||||
KeyHasBeenRevoked => write!(f, "Key has been revoked"),
|
||||
KeyWasRejectedByService => write!(f, "Key was rejected by service"),
|
||||
OwnerDied => write!(f, "Owner died"),
|
||||
StateNotRecoverable => write!(f, "State not recoverable"),
|
||||
OperationNotPossibleDueToRfKill => write!(f, "Operation not possible due to RF-kill"),
|
||||
MemoryPageHasHardwareError => write!(f, "Memory page has hardware error"),
|
||||
Other(value) => write!(f, "Other error: {}", value),
|
||||
_ => write!(f, "Unknown Error"), // Errors not present in linux
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
use core::fmt::Display;
|
||||
|
||||
mod libc {
|
||||
// taken from newlib source code
|
||||
use core::ffi::c_int;
|
||||
pub const EPERM: c_int = 1;
|
||||
pub const ENOENT: c_int = 2;
|
||||
pub const ESRCH: c_int = 3;
|
||||
pub const EINTR: c_int = 4;
|
||||
pub const EIO: c_int = 5;
|
||||
pub const ENXIO: c_int = 6;
|
||||
pub const E2BIG: c_int = 7;
|
||||
pub const ENOEXEC: c_int = 8;
|
||||
pub const EBADF: c_int = 9;
|
||||
pub const ECHILD: c_int = 10;
|
||||
pub const EAGAIN: c_int = 11;
|
||||
pub const ENOMEM: c_int = 12;
|
||||
pub const EACCES: c_int = 13;
|
||||
pub const EFAULT: c_int = 14;
|
||||
pub const ENOTBLK: c_int = 15;
|
||||
pub const EBUSY: c_int = 16;
|
||||
pub const EEXIST: c_int = 17;
|
||||
pub const EXDEV: c_int = 18;
|
||||
pub const ENODEV: c_int = 19;
|
||||
pub const ENOTDIR: c_int = 20;
|
||||
pub const EISDIR: c_int = 21;
|
||||
pub const EINVAL: c_int = 22;
|
||||
pub const ENFILE: c_int = 23;
|
||||
pub const EMFILE: c_int = 24;
|
||||
pub const ENOTTY: c_int = 25;
|
||||
pub const ETXTBSY: c_int = 26;
|
||||
pub const EFBIG: c_int = 27;
|
||||
pub const ENOSPC: c_int = 28;
|
||||
pub const ESPIPE: c_int = 29;
|
||||
pub const EROFS: c_int = 30;
|
||||
pub const EMLINK: c_int = 31;
|
||||
pub const EPIPE: c_int = 32;
|
||||
pub const EDOM: c_int = 33;
|
||||
pub const ERANGE: c_int = 34;
|
||||
pub const ENOMSG: c_int = 35;
|
||||
pub const EIDRM: c_int = 36;
|
||||
pub const ECHRNG: c_int = 37;
|
||||
pub const EL2NSYNC: c_int = 38;
|
||||
pub const EL3HLT: c_int = 39;
|
||||
pub const EL3RST: c_int = 40;
|
||||
pub const ELNRNG: c_int = 41;
|
||||
pub const EUNATCH: c_int = 42;
|
||||
pub const ENOCSI: c_int = 43;
|
||||
pub const EL2HLT: c_int = 44;
|
||||
pub const EDEADLK: c_int = 45;
|
||||
pub const ENOLCK: c_int = 46;
|
||||
pub const EBADE: c_int = 50;
|
||||
pub const EBADR: c_int = 51;
|
||||
pub const EXFULL: c_int = 52;
|
||||
pub const ENOANO: c_int = 53;
|
||||
pub const EBADRQC: c_int = 54;
|
||||
pub const EBADSLT: c_int = 55;
|
||||
pub const EDEADLOCK: c_int = 56;
|
||||
pub const EBFONT: c_int = 57;
|
||||
pub const ENOSTR: c_int = 60;
|
||||
pub const ENODATA: c_int = 61;
|
||||
pub const ETIME: c_int = 62;
|
||||
pub const ENOSR: c_int = 63;
|
||||
pub const ENONET: c_int = 64;
|
||||
pub const ENOPKG: c_int = 65;
|
||||
pub const EREMOTE: c_int = 66;
|
||||
pub const ENOLINK: c_int = 67;
|
||||
pub const EADV: c_int = 68;
|
||||
pub const ESRMNT: c_int = 69;
|
||||
pub const ECOMM: c_int = 70;
|
||||
pub const EPROTO: c_int = 71;
|
||||
pub const EMULTIHOP: c_int = 74;
|
||||
pub const ELBIN: c_int = 75;
|
||||
pub const EDOTDOT: c_int = 76;
|
||||
pub const EBADMSG: c_int = 77;
|
||||
pub const EFTYPE: c_int = 79;
|
||||
pub const ENOTUNIQ: c_int = 80;
|
||||
pub const EBADFD: c_int = 81;
|
||||
pub const EREMCHG: c_int = 82;
|
||||
pub const ELIBACC: c_int = 83;
|
||||
pub const ELIBBAD: c_int = 84;
|
||||
pub const ELIBSCN: c_int = 85;
|
||||
pub const ELIBMAX: c_int = 86;
|
||||
pub const ELIBEXEC: c_int = 87;
|
||||
pub const ENOSYS: c_int = 88;
|
||||
pub const ENMFILE: c_int = 89;
|
||||
pub const ENOTEMPTY: c_int = 90;
|
||||
pub const ENAMETOOLONG: c_int = 91;
|
||||
pub const ELOOP: c_int = 92;
|
||||
pub const EOPNOTSUPP: c_int = 95;
|
||||
pub const EPFNOSUPPORT: c_int = 96;
|
||||
pub const ECONNRESET: c_int = 104;
|
||||
pub const ENOBUFS: c_int = 105;
|
||||
pub const EAFNOSUPPORT: c_int = 106;
|
||||
pub const EPROTOTYPE: c_int = 107;
|
||||
pub const ENOTSOCK: c_int = 108;
|
||||
pub const ENOPROTOOPT: c_int = 109;
|
||||
pub const ESHUTDOWN: c_int = 110;
|
||||
pub const ECONNREFUSED: c_int = 111;
|
||||
pub const EADDRINUSE: c_int = 112;
|
||||
pub const ECONNABORTED: c_int = 113;
|
||||
pub const ENETUNREACH: c_int = 114;
|
||||
pub const ENETDOWN: c_int = 115;
|
||||
pub const ETIMEDOUT: c_int = 116;
|
||||
pub const EHOSTDOWN: c_int = 117;
|
||||
pub const EHOSTUNREACH: c_int = 118;
|
||||
pub const EINPROGRESS: c_int = 119;
|
||||
pub const EALREADY: c_int = 120;
|
||||
pub const EDESTADDRREQ: c_int = 121;
|
||||
pub const EMSGSIZE: c_int = 122;
|
||||
pub const EPROTONOSUPPORT: c_int = 123;
|
||||
pub const ESOCKTNOSUPPORT: c_int = 124;
|
||||
pub const EADDRNOTAVAIL: c_int = 125;
|
||||
pub const ENETRESET: c_int = 126;
|
||||
pub const EISCONN: c_int = 127;
|
||||
pub const ENOTCONN: c_int = 128;
|
||||
pub const ETOOMANYREFS: c_int = 129;
|
||||
pub const EPROCLIM: c_int = 130;
|
||||
pub const EUSERS: c_int = 131;
|
||||
pub const EDQUOT: c_int = 132;
|
||||
pub const ESTALE: c_int = 133;
|
||||
pub const ENOTSUP: c_int = 134;
|
||||
pub const ENOMEDIUM: c_int = 135;
|
||||
pub const ENOSHARE: c_int = 136;
|
||||
pub const ECASECLASH: c_int = 137;
|
||||
pub const EILSEQ: c_int = 138;
|
||||
pub const EOVERFLOW: c_int = 139;
|
||||
pub const ECANCELED: c_int = 140;
|
||||
pub const ENOTRECOVERABLE: c_int = 141;
|
||||
pub const EOWNERDEAD: c_int = 142;
|
||||
pub const ESTRPIPE: c_int = 143;
|
||||
#[allow(unused)]
|
||||
pub const ELASTERROR: c_int = 2000; //User Errors start here
|
||||
#[allow(unused)]
|
||||
pub const EWOULDBLOCK: c_int = EAGAIN;
|
||||
}
|
||||
|
||||
impl From<i32> for super::OsError {
|
||||
fn from(value: i32) -> Self {
|
||||
use super::OsError::*;
|
||||
match value {
|
||||
libc::EPERM => OperationNotPermitted,
|
||||
libc::ENOENT => NoSuchFileOrDirectory,
|
||||
libc::ESRCH => NoSuchProcess,
|
||||
libc::EINTR => InterruptedSystemCall,
|
||||
libc::EIO => InputOutputError,
|
||||
libc::ENXIO => NoSuchDeviceOrAddress,
|
||||
libc::E2BIG => ArgumentListTooLong,
|
||||
libc::ENOEXEC => ExecFormatError,
|
||||
libc::EBADF => BadFileDescriptor,
|
||||
libc::ECHILD => NoChildProcesses,
|
||||
libc::EAGAIN => OperationWouldBlock,
|
||||
// libc::EWOULDBLOCK => OperationWouldBlock == EAGAIN
|
||||
libc::ENOMEM => CannotAllocateMemory,
|
||||
libc::EACCES => PermissionDenied,
|
||||
libc::EFAULT => BadAddress,
|
||||
libc::ENOTBLK => BlockDeviceRequired,
|
||||
libc::EBUSY => DeviceOrResourceBusy,
|
||||
libc::EEXIST => FileExists,
|
||||
libc::EXDEV => InvalidCrossDeviceLink,
|
||||
libc::ENODEV => NoSuchDevice,
|
||||
libc::ENOTDIR => NotADirectory,
|
||||
libc::EISDIR => IsADirectory,
|
||||
libc::EINVAL => InvalidArgument,
|
||||
libc::ENFILE => TooManyOpenFilesInSystem,
|
||||
libc::EMFILE => TooManyOpenFiles,
|
||||
libc::ENOTTY => NotACharacterDevice,
|
||||
libc::ETXTBSY => TextFileBusy,
|
||||
libc::EFBIG => FileTooLarge,
|
||||
libc::ENOSPC => NoSpaceLeftOnDevice,
|
||||
libc::ESPIPE => IllegalSeek,
|
||||
libc::EROFS => ReadOnlyFileSystem,
|
||||
libc::EMLINK => TooManyLinks,
|
||||
libc::EPIPE => BrokenPipe,
|
||||
libc::EDOM => NumericalArgumentOutOfDomain,
|
||||
libc::ERANGE => NumericalResultOutOfRange,
|
||||
libc::ENOMSG => NoMessageOfDesiredType,
|
||||
libc::EIDRM => IdentifierRemoved,
|
||||
libc::ECHRNG => ChannelNumberOutOfRange,
|
||||
libc::EL2NSYNC => Level2NotSynchronized,
|
||||
libc::EL3HLT => Level3Halted,
|
||||
libc::EL3RST => Level3Reset,
|
||||
libc::ELNRNG => LinkNumberOutOfRange,
|
||||
libc::EUNATCH => ProtocolDriverNotAttached,
|
||||
libc::ENOCSI => NoCsiStructureAvailable,
|
||||
libc::EL2HLT => Level2Halted,
|
||||
libc::EDEADLK => Deadlock,
|
||||
libc::ENOLCK => NoLocksAvailable,
|
||||
libc::EBADE => InvalidExchange,
|
||||
libc::EBADR => InvalidRequestDescriptor,
|
||||
libc::EXFULL => ExchangeFull,
|
||||
libc::ENOANO => NoAnode,
|
||||
libc::EBADRQC => InvalidRequestCode,
|
||||
libc::EBADSLT => InvalidSlot,
|
||||
libc::EDEADLOCK => Deadlock,
|
||||
libc::EBFONT => BadFontFileFormat,
|
||||
libc::ENOSTR => DeviceNotAStream,
|
||||
libc::ENODATA => NoDataAvailable,
|
||||
libc::ETIME => TimerExpired,
|
||||
libc::ENOSR => OutOfStreamsResources,
|
||||
libc::ENONET => MachineIsNotOnTheNetwork,
|
||||
libc::ENOPKG => PackageNotInstalled,
|
||||
libc::EREMOTE => ObjectIsRemote,
|
||||
libc::ENOLINK => LinkHasBeenSevered,
|
||||
libc::EADV => AdvertiseError,
|
||||
libc::ESRMNT => SrmountError,
|
||||
libc::ECOMM => CommunicationErrorOnSend,
|
||||
libc::EPROTO => ProtocolError,
|
||||
libc::EMULTIHOP => MultihopAttempted,
|
||||
libc::ELBIN => InodeIsRemote,
|
||||
libc::EDOTDOT => FsError,
|
||||
libc::EBADMSG => BadMessage,
|
||||
libc::EFTYPE => InappropriateFileTypeOrFormat,
|
||||
libc::ENOTUNIQ => NameNotUniqueOnNetwork,
|
||||
libc::EBADFD => FileDescriptorInBadState,
|
||||
libc::EREMCHG => RemoteAddressChanged,
|
||||
libc::ELIBACC => CanNotAccessANeededSharedLibrary,
|
||||
libc::ELIBBAD => AccessingACorruptedSharedLibrary,
|
||||
libc::ELIBSCN => LibSectionInAOutCorrupted,
|
||||
libc::ELIBMAX => AttemptingToLinkInTooManySharedLibraries,
|
||||
libc::ELIBEXEC => CannotExecASharedLibraryDirectly,
|
||||
libc::ENOSYS => FunctionNotImplemented,
|
||||
libc::ENMFILE => NoMoreFiles,
|
||||
libc::ENOTEMPTY => DirectoryNotEmpty,
|
||||
libc::ENAMETOOLONG => FileNameTooLong,
|
||||
libc::ELOOP => TooManySymbolicLinks,
|
||||
libc::EOPNOTSUPP => OperationNotSupported,
|
||||
libc::EPFNOSUPPORT => ProtocolFamilyNotSupported,
|
||||
libc::ECONNRESET => ConnectionResetByPeer,
|
||||
libc::ENOBUFS => NoBufferSpaceAvailable,
|
||||
libc::EAFNOSUPPORT => AddressFamilyNotSupportedByProtocol,
|
||||
libc::EPROTOTYPE => ProtocolWrongTypeForSocket,
|
||||
libc::ENOTSOCK => SocketOperationOnNonSocket,
|
||||
libc::ENOPROTOOPT => ProtocolNotAvailable,
|
||||
libc::ESHUTDOWN => CannotSendAfterShutdown,
|
||||
libc::ECONNREFUSED => ConnectionRefused,
|
||||
libc::EADDRINUSE => AddressAlreadyInUse,
|
||||
libc::ECONNABORTED => SoftwareCausedConnectionAbort,
|
||||
libc::ENETUNREACH => HostIsUnreachable,
|
||||
libc::ENETDOWN => NetworkIsDown,
|
||||
libc::ETIMEDOUT => ConnectionTimedOut,
|
||||
libc::EHOSTDOWN => HostIsDown,
|
||||
libc::EHOSTUNREACH => HostIsUnreachable,
|
||||
libc::EINPROGRESS => OperationNowInProgress,
|
||||
libc::EALREADY => OperationAlreadyInProgress,
|
||||
libc::EDESTADDRREQ => DestinationAddressRequired,
|
||||
libc::EMSGSIZE => MessageTooLong,
|
||||
libc::EPROTONOSUPPORT => ProtocolNotSupported,
|
||||
libc::ESOCKTNOSUPPORT => SocketTypeNotSupported,
|
||||
libc::EADDRNOTAVAIL => AddressNotAvailable,
|
||||
libc::ENETRESET => NetworkDroppedConnectionOnReset,
|
||||
libc::EISCONN => TransportEndpointIsAlreadyConnected,
|
||||
libc::ENOTCONN => TransportEndpointIsNotConnected,
|
||||
libc::ETOOMANYREFS => TooManyReferencesCannotSplice,
|
||||
libc::EPROCLIM => LimitOnNewProcessesReached,
|
||||
libc::EUSERS => TooManyUsers,
|
||||
libc::EDQUOT => DiskQuotaExceeded,
|
||||
libc::ESTALE => StaleFileHandle,
|
||||
libc::ENOTSUP => OperationNotSupported,
|
||||
libc::ENOMEDIUM => NoMediumFound,
|
||||
libc::ENOSHARE => NoShare,
|
||||
libc::ECASECLASH => FilenameExistsWithDifferentCase,
|
||||
libc::EILSEQ => InvalidOrIncompleteMultibyteOrWideCharacter,
|
||||
libc::EOVERFLOW => ValueTooLargeForDefinedDataType,
|
||||
libc::ECANCELED => OperationCanceled,
|
||||
libc::ENOTRECOVERABLE => StateNotRecoverable,
|
||||
libc::EOWNERDEAD => OwnerDied,
|
||||
libc::ESTRPIPE => StreamsPipeError,
|
||||
value => Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for super::OsError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
use super::OsError::*;
|
||||
match self {
|
||||
AccessingACorruptedSharedLibrary => write!(f, "Accessing a corrupted shared lib"),
|
||||
AddressAlreadyInUse => write!(f, "Address already in use"),
|
||||
AddressFamilyNotSupportedByProtocol => {
|
||||
write!(f, "Address family not supported by protocol family")
|
||||
}
|
||||
AddressNotAvailable => write!(f, "Address not available"),
|
||||
AdvertiseError => write!(f, "Advertise error"),
|
||||
ArgumentListTooLong => write!(f, "Arg list too long"),
|
||||
AttemptingToLinkInTooManySharedLibraries => {
|
||||
write!(f, "Attempting to link in too many libs")
|
||||
}
|
||||
BadAddress => write!(f, "Bad address"),
|
||||
BadFileDescriptor => write!(f, "Bad file number"),
|
||||
BadFontFileFormat => write!(f, "Bad font file fmt"),
|
||||
BadMessage => write!(f, "Bad message"),
|
||||
BlockDeviceRequired => write!(f, "Block device required"),
|
||||
BrokenPipe => write!(f, "Broken pipe"),
|
||||
CanNotAccessANeededSharedLibrary => write!(f, "Can't access a needed shared lib"),
|
||||
CannotAllocateMemory => write!(f, "Not enough space"),
|
||||
CannotExecASharedLibraryDirectly => write!(f, "Attempting to exec a shared library"),
|
||||
CannotSendAfterShutdown => {
|
||||
write!(f, "Can't send after socket shutdown")
|
||||
}
|
||||
ChannelNumberOutOfRange => write!(f, "Channel number out of range"),
|
||||
CommunicationErrorOnSend => write!(f, "Communication error on send"),
|
||||
ConnectionRefused => write!(f, "Connection refused"),
|
||||
ConnectionResetByPeer => write!(f, "Connection reset by peer"),
|
||||
ConnectionTimedOut => write!(f, "Connection timed out"),
|
||||
Deadlock => write!(f, "Deadlock"),
|
||||
DestinationAddressRequired => write!(f, "Destination address required"),
|
||||
DeviceNotAStream => write!(f, "Not a stream"),
|
||||
DeviceOrResourceBusy => write!(f, "Device or resource busy"),
|
||||
DirectoryNotEmpty => write!(f, "Directory not empty"),
|
||||
DiskQuotaExceeded => write!(f, "disk quota exceeded"),
|
||||
ExchangeFull => write!(f, "Exchange full"),
|
||||
ExecFormatError => write!(f, "Exec format error"),
|
||||
FileDescriptorInBadState => write!(f, "f.d. invalid for this operation"),
|
||||
FileExists => write!(f, "File exists"),
|
||||
FilenameExistsWithDifferentCase => write!(f, "Filename exists with different case"),
|
||||
FileNameTooLong => write!(f, "File or path name too long"),
|
||||
FileTooLarge => write!(f, "File too large"),
|
||||
FsError => write!(f, "Cross mount point (not really error)"),
|
||||
FunctionNotImplemented => write!(f, "Function not implemented"),
|
||||
HostIsDown => write!(f, "Host is down"),
|
||||
IdentifierRemoved => write!(f, "Identifier removed"),
|
||||
IllegalSeek => write!(f, "Illegal seek"),
|
||||
InappropriateFileTypeOrFormat => write!(f, "Inappropriate file type or format"),
|
||||
InodeIsRemote => write!(f, "Inode is remote (not really error)"),
|
||||
InputOutputError => write!(f, "I/O error"),
|
||||
InterruptedSystemCall => write!(f, "Interrupted system call"),
|
||||
InvalidArgument => write!(f, "Invalid argument"),
|
||||
InvalidCrossDeviceLink => write!(f, "Cross-device link"),
|
||||
InvalidExchange => write!(f, "Invalid exchange"),
|
||||
InvalidOrIncompleteMultibyteOrWideCharacter => write!(f, "Illegal byte sequence"),
|
||||
InvalidRequestCode => write!(f, "Invalid request code"),
|
||||
InvalidRequestDescriptor => write!(f, "Invalid request descriptor"),
|
||||
InvalidSlot => write!(f, "Invalid slot"),
|
||||
IsADirectory => write!(f, "Is a directory"),
|
||||
Level2Halted => write!(f, "Level 2 halted"),
|
||||
Level2NotSynchronized => write!(f, "Level 2 not synchronized"),
|
||||
Level3Halted => write!(f, "Level 3 halted"),
|
||||
Level3Reset => write!(f, "Level 3 reset"),
|
||||
LibSectionInAOutCorrupted => write!(f, ".lib section in a.out corrupted"),
|
||||
LimitOnNewProcessesReached => write!(f, "limit on new processes reached"),
|
||||
LinkHasBeenSevered => write!(f, "Virtual circuit is gone"),
|
||||
LinkNumberOutOfRange => write!(f, "Link number out of range"),
|
||||
MachineIsNotOnTheNetwork => write!(f, "Machine is not on the network"),
|
||||
MessageTooLong => write!(f, "Message too long"),
|
||||
MultihopAttempted => write!(f, "Multihop attempted"),
|
||||
NameNotUniqueOnNetwork => write!(f, "Given log. name not unique"),
|
||||
NetworkDroppedConnectionOnReset => write!(f, "Connection aborted by network"),
|
||||
NetworkIsDown => write!(f, "Network interface is not configured"),
|
||||
HostIsUnreachable => write!(f, "Network is unreachable"),
|
||||
NoAnode => write!(f, "No anode"),
|
||||
NoBufferSpaceAvailable => write!(f, "No buffer space available"),
|
||||
NoChildProcesses => write!(f, "No children"),
|
||||
NoCsiStructureAvailable => write!(f, "No CSI structure available"),
|
||||
NoDataAvailable => write!(f, "No data (for no delay io)"),
|
||||
NoLocksAvailable => write!(f, "No lock"),
|
||||
NoMediumFound => write!(f, "No medium (in tape drive)"),
|
||||
NoMessageOfDesiredType => write!(f, "No message of desired type"),
|
||||
NoMoreFiles => write!(f, "No more files"),
|
||||
NoRouteToHost => write!(f, "Host is unreachable"),
|
||||
NoShare => write!(f, "No Share"),
|
||||
NoSpaceLeftOnDevice => write!(f, "No space left on device"),
|
||||
NoSuchDevice => write!(f, "No such device"),
|
||||
NoSuchDeviceOrAddress => write!(f, "No such device or address"),
|
||||
NoSuchFileOrDirectory => write!(f, "No such file or directory"),
|
||||
NoSuchProcess => write!(f, "No such process"),
|
||||
NotACharacterDevice => write!(f, "Not a character device"),
|
||||
NotADirectory => write!(f, "Not a directory"),
|
||||
NumericalArgumentOutOfDomain => {
|
||||
write!(f, "Mathematics argument out of domain of function")
|
||||
}
|
||||
NumericalResultOutOfRange => write!(f, "Result too large"),
|
||||
ObjectIsRemote => write!(f, "The object is remote"),
|
||||
OperationAlreadyInProgress => write!(f, "Socket already connected"),
|
||||
OperationCanceled => write!(f, "Operation canceled"),
|
||||
OperationNotPermitted => write!(f, "Not owner"),
|
||||
OperationNotSupported => write!(f, "Not supported"),
|
||||
OperationNowInProgress => write!(f, "Connection already in progress"),
|
||||
OperationWouldBlock => write!(f, "Operation would block"),
|
||||
OutOfStreamsResources => write!(f, "No stream resources"),
|
||||
OwnerDied => write!(f, "Previous owner died"),
|
||||
PackageNotInstalled => write!(f, "Package not installed"),
|
||||
PermissionDenied => write!(f, "Permission denied"),
|
||||
ProtocolDriverNotAttached => write!(f, "Protocol driver not attached"),
|
||||
ProtocolError => write!(f, "Protocol error"),
|
||||
ProtocolFamilyNotSupported => write!(f, "Protocol family not supported"),
|
||||
ProtocolNotAvailable => write!(f, "Protocol not available"),
|
||||
ProtocolNotSupported => write!(f, "Unknown protocol"),
|
||||
ProtocolWrongTypeForSocket => write!(f, "Protocol wrong type for socket"),
|
||||
ReadOnlyFileSystem => write!(f, "Read-only file system"),
|
||||
RemoteAddressChanged => write!(f, "Remote address changed"),
|
||||
SocketOperationOnNonSocket => write!(f, "Socket operation on non-socket"),
|
||||
SocketTypeNotSupported => write!(f, "Socket type not supported"),
|
||||
SoftwareCausedConnectionAbort => write!(f, "Software caused connection abort"),
|
||||
SrmountError => write!(f, "Srmount error"),
|
||||
StaleFileHandle => write!(f, "Stale NFS file handle"),
|
||||
StateNotRecoverable => write!(f, "State not recoverable"),
|
||||
StreamsPipeError => write!(f, "Streams pipe error"),
|
||||
TextFileBusy => write!(f, "Text file busy"),
|
||||
TimerExpired => write!(f, "Stream ioctl timeout"),
|
||||
TooManyLinks => write!(f, "Too many links"),
|
||||
TooManyOpenFiles => write!(f, "File descriptor value too large"),
|
||||
TooManyOpenFilesInSystem => write!(f, "Too many open files in system"),
|
||||
TooManyReferencesCannotSplice => write!(f, "Too many references: cannot splice."),
|
||||
TooManySymbolicLinks => write!(f, "Too many symbolic links"),
|
||||
TooManyUsers => write!(f, "file quota system confused too many users"),
|
||||
TransportEndpointIsAlreadyConnected => write!(f, "Socket is already connected"),
|
||||
TransportEndpointIsNotConnected => write!(f, "Socket is not connected"),
|
||||
ValueTooLargeForDefinedDataType => write!(f, "Value too large for defined data type"),
|
||||
Other(value) => write!(f, "Other error: {}", value),
|
||||
_ => write!(f, "Unknown Error"), // Errors not present in newlib
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// TODO verify uXX == uintXX_t
|
||||
// TODO Document size_t != usize problems better
|
||||
type TaskFunction = unsafe extern "C" fn(*mut core::ffi::c_void) -> !;
|
||||
|
||||
pub struct Sizes {}
|
||||
|
||||
// TODO these should be passed by compile time value
|
||||
#[cfg(target_os = "linux")]
|
||||
impl Sizes {
|
||||
pub const TASK_DATA_SIZE: usize = 192;
|
||||
pub const MUTEX_DATA_SIZE: usize = 168;
|
||||
pub const QUEUE_DATA_SIZE: usize = 168;
|
||||
pub const TASK_MINIMAL_STACK_SIZE: usize = 16424;
|
||||
}
|
||||
|
||||
// We use target_os=none for bare metal, as we only support one platform so far
|
||||
// so we select on target_env
|
||||
#[cfg(target_env = "newlib")]
|
||||
impl Sizes {
|
||||
pub const TASK_DATA_SIZE: usize = 184;
|
||||
pub const MUTEX_DATA_SIZE: usize = 168;
|
||||
pub const QUEUE_DATA_SIZE: usize = 168;
|
||||
pub const TASK_MINIMAL_STACK_SIZE: usize = 10240;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
////////////////////////
|
||||
/// FreeRTOS API
|
||||
/// shimmed in mission/freeRTOS_rust_helper.c
|
||||
//void *create_task_static(TaskFunction_t taskFunction, void *parameter,
|
||||
// char *task_data, uint32_t task_data_len, char *stack, uint32_t stack_size)
|
||||
pub fn freertos_create_task_static(
|
||||
taskFunction: TaskFunction,
|
||||
parameter: *const core::ffi::c_void,
|
||||
priority: u32,
|
||||
task_data: *mut core::ffi::c_char,
|
||||
task_data_len: u32,
|
||||
stack: *mut core::ffi::c_char,
|
||||
stack_size: u32,
|
||||
) -> *const core::ffi::c_void;
|
||||
|
||||
// pub fn stop_it();
|
||||
|
||||
pub fn freertos_task_suspend(handle: *const core::ffi::c_void);
|
||||
|
||||
pub fn freertos_task_delete(handle: *const core::ffi::c_void) -> !;
|
||||
|
||||
pub fn freertos_task_storage_set(
|
||||
handle: *const core::ffi::c_void,
|
||||
data: *const core::ffi::c_void,
|
||||
);
|
||||
|
||||
pub fn freertos_task_storage_get(handle: *const core::ffi::c_void) -> *const core::ffi::c_void;
|
||||
|
||||
pub fn freertos_task_delay(milliseconds: u32);
|
||||
|
||||
pub fn freertos_task_stack_watermark() -> u32;
|
||||
|
||||
pub fn freertos_task_current() -> *const core::ffi::c_void;
|
||||
|
||||
// TODO this should be passed by compile time value
|
||||
pub fn freertos_task_priority_max() -> u32;
|
||||
|
||||
//void *freertos_queue_create_static(uint32_t depth, uint32_t element_size,
|
||||
// char *queue_data, uint32_t queue_data_len,
|
||||
// uint8_t *queue, uint32_t queue_len) {
|
||||
pub fn freertos_queue_create_static(
|
||||
depth: u32,
|
||||
element_size: u32,
|
||||
queue_data: *mut core::ffi::c_char,
|
||||
queue_data_len: u32,
|
||||
queue: *mut u8,
|
||||
) -> *const core::ffi::c_void;
|
||||
|
||||
pub fn freertos_queue_receive(
|
||||
queue: *const core::ffi::c_void,
|
||||
message: *const core::ffi::c_void,
|
||||
) -> u8;
|
||||
pub fn freertos_queue_send(
|
||||
queue: *const core::ffi::c_void,
|
||||
message: *const core::ffi::c_void,
|
||||
) -> u8;
|
||||
|
||||
pub fn freertos_mutex_create_static(
|
||||
mutex_data: *const core::ffi::c_char,
|
||||
mutex_data_len: u32,
|
||||
) -> *const core::ffi::c_void;
|
||||
|
||||
pub fn freertos_mutex_take(mutex: *const core::ffi::c_void) -> u8;
|
||||
pub fn freertos_mutex_give(mutex: *const core::ffi::c_void) -> u8;
|
||||
|
||||
//uint8_t freertos_simple_once(uint8_t *once_data)
|
||||
pub fn freertos_simple_once(once_data: *const u8) -> u8;
|
||||
|
||||
//int freertos_get_sys_error()
|
||||
pub fn freertos_get_sys_error() -> core::ffi::c_int;
|
||||
|
||||
////////////////////////
|
||||
/// Harware Abstraction API in common/include/interfaces.h
|
||||
/// Used for access to peripherals to make switching between linux and embedded easier
|
||||
//int hw_device_open(const char * path, size_t path_len);
|
||||
pub fn hw_device_open(path: *const core::ffi::c_char, path_len: core::ffi::c_size_t) -> core::ffi::c_int;
|
||||
}
|
||||
////////////////////////
|
||||
/// libc API (read/write/etc)
|
||||
#[cfg(target_env = "gnu")]
|
||||
mod gnu;
|
||||
#[cfg(target_env = "gnu")]
|
||||
pub use gnu::*;
|
||||
|
||||
#[cfg(target_env = "newlib")]
|
||||
mod newlib;
|
||||
#[cfg(target_env = "newlib")]
|
||||
pub use newlib::*;
|
||||
@@ -0,0 +1,15 @@
|
||||
extern "C" {
|
||||
// The GNU C Library Reference Manual 2.42, ch. 13.2
|
||||
pub fn write(
|
||||
fd: core::ffi::c_int,
|
||||
buffer: *const core::ffi::c_void,
|
||||
count: core::ffi::c_size_t,
|
||||
) -> core::ffi::c_ssize_t;
|
||||
|
||||
// The GNU C Library Reference Manual 2.42, ch. 13.2
|
||||
pub fn read(
|
||||
fd: core::ffi::c_int,
|
||||
buffer: *const core::ffi::c_void,
|
||||
count: core::ffi::c_size_t,
|
||||
) -> core::ffi::c_ssize_t;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// TODO: newlib uses int only on some platforms (including arm) as read/write return
|
||||
// see newlib:newlib/libc/include/sys/config.h for details
|
||||
// count is size_t according to newlib:newlib/libc/include/sys/unistd.h, not int as the website shows in the syscall examples
|
||||
|
||||
extern "C" {
|
||||
pub fn write(
|
||||
fd: core::ffi::c_int,
|
||||
buffer: *const core::ffi::c_void,
|
||||
count: core::ffi::c_size_t,
|
||||
) -> core::ffi::c_int;
|
||||
pub fn read(
|
||||
fd: core::ffi::c_int,
|
||||
buffer: *const core::ffi::c_void,
|
||||
count: core::ffi::c_size_t,
|
||||
) -> core::ffi::c_int;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
use core::fmt::Display;
|
||||
|
||||
pub use super::error::OsError;
|
||||
use super::ffi;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Os(OsError),
|
||||
IncompleteWrite(usize),
|
||||
InvalidPath,
|
||||
Unknown(i32),
|
||||
InternalError,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "😢")?;
|
||||
match self {
|
||||
Error::Os(os_error) => write!(f, "Os::{}", os_error),
|
||||
_ => write!(f, "not implemented"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
pub trait Read {
|
||||
fn read<'a>(&self, buffer: &'a [u8]) -> Result<&'a [u8]>;
|
||||
}
|
||||
|
||||
pub trait Write {
|
||||
fn write(&mut self, data: &[u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HardwareInterface {
|
||||
fd: core::ffi::c_int,
|
||||
}
|
||||
|
||||
impl HardwareInterface {
|
||||
pub fn new(path: &str) -> Result<Self> {
|
||||
// Safe: we trust our own implementation to only read
|
||||
// as we cut our string into bytes, conversion to c_char is correct, C will compare bit value
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
// is refutable on some exotic targets. If you wanna have fun: https://internals.rust-lang.org/t/pre-rfc-usize-is-not-size-t/15369
|
||||
let Ok(len_converted) = path.as_bytes().len().try_into() else {
|
||||
panic!(); // TODO Abort
|
||||
};
|
||||
let fd = unsafe {
|
||||
crate::osal::ffi::hw_device_open(
|
||||
path.as_bytes().as_ptr() as *const core::ffi::c_char,
|
||||
len_converted,
|
||||
)
|
||||
};
|
||||
if fd >= 0 {
|
||||
Ok(Self { fd })
|
||||
} else {
|
||||
// Safe: getter on os
|
||||
let errno = unsafe { ffi::freertos_get_sys_error() };
|
||||
Err(Error::Os(errno.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for HardwareInterface {
|
||||
fn write(&mut self, data: &[u8]) -> Result<()> {
|
||||
#[allow(irrefutable_let_patterns)] // is refutable on some exotic targets. See above
|
||||
let Ok(len_converted) = data.len().try_into() else {
|
||||
return Err(Error::Os(OsError::NumericalResultOutOfRange));
|
||||
};
|
||||
let written = unsafe {
|
||||
ffi::write(
|
||||
self.fd,
|
||||
data.as_ptr() as *const core::ffi::c_void,
|
||||
len_converted,
|
||||
)
|
||||
};
|
||||
if written < 0 {
|
||||
// Safe: getter on os
|
||||
let errno = unsafe { ffi::freertos_get_sys_error() };
|
||||
return Err(Error::Os(errno.into()));
|
||||
} else {
|
||||
let Ok(written_unsigned) = written.try_into() else {
|
||||
// we are in written >=0, this should never happen
|
||||
return Err(Error::Os(OsError::NumericalResultOutOfRange));
|
||||
};
|
||||
if written_unsigned < data.len() {
|
||||
return Err(Error::IncompleteWrite(written_unsigned));
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for HardwareInterface {
|
||||
fn read<'a>(&self, buffer: &'a [u8]) -> Result<&'a [u8]> {
|
||||
// Safe as we trust the OS
|
||||
let read = unsafe {
|
||||
#[allow(irrefutable_let_patterns)] // is refutable on some targets
|
||||
let Ok(len_converted) = buffer.len().try_into() else {
|
||||
return Err(Error::Os(OsError::NumericalResultOutOfRange));
|
||||
};
|
||||
ffi::read(
|
||||
self.fd,
|
||||
buffer.as_ptr() as *const core::ffi::c_void,
|
||||
len_converted,
|
||||
)
|
||||
};
|
||||
if read < 0 {
|
||||
// Safe: getter on os
|
||||
let errno = unsafe { ffi::freertos_get_sys_error() };
|
||||
let error = errno.into();
|
||||
if error == OsError::OperationWouldBlock {
|
||||
// non blocking call returned
|
||||
return Ok(&[]);
|
||||
}
|
||||
return Err(Error::Os(error));
|
||||
} else {
|
||||
let Ok(read_unsigned) = read.try_into() else {
|
||||
// we are in read >=0, this should never happen
|
||||
return Err(Error::Os(OsError::NumericalResultOutOfRange));
|
||||
};
|
||||
Ok(&buffer[..read_unsigned])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
type TaskFunction = unsafe extern "C" fn(*mut core::ffi::c_void);
|
||||
|
||||
//TODO verify uXX == uintXX_t
|
||||
//TODO safe API
|
||||
|
||||
|
||||
// This is a macro so that the panic is at the right place
|
||||
#[macro_export]
|
||||
macro_rules! check_global_threading_available {
|
||||
() => (
|
||||
if !crate::osal::global_threading_available() {
|
||||
panic!("using threaded API outside of threading environment")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn outbyte(c: u8);
|
||||
//void *create_task(TaskFunction_t taskFunction, void *parameter, size_t stack_size)
|
||||
pub fn create_task(
|
||||
taskFunction: TaskFunction,
|
||||
parameter: *const core::ffi::c_void,
|
||||
stack_size: u32,
|
||||
) -> *const core::ffi::c_void;
|
||||
|
||||
pub fn get_task_name() -> *const core::ffi::c_char;
|
||||
|
||||
pub fn stop_it();
|
||||
|
||||
pub fn delete_task(handle: *const core::ffi::c_void);
|
||||
|
||||
pub fn task_delay(milliseconds: u32);
|
||||
|
||||
//void *create_queue(size_t length, size_t element_size)
|
||||
pub fn create_queue(length: u32, element_size: u32) -> *const core::ffi::c_void;
|
||||
|
||||
pub fn queue_receive(queue: *const core::ffi::c_void, message: *const core::ffi::c_void) -> u8;
|
||||
pub fn queue_send(queue: *const core::ffi::c_void, message: *const core::ffi::c_void) -> u8;
|
||||
|
||||
pub fn create_mutex() -> *const core::ffi::c_void;
|
||||
|
||||
pub fn take_mutex(mutex: *const core::ffi::c_void) -> u8;
|
||||
pub fn give_mutex(mutex: *const core::ffi::c_void) -> u8;
|
||||
|
||||
fn global_threading_available_c() -> u8;
|
||||
fn enable_global_threading_c();
|
||||
fn disable_global_threading_c();
|
||||
|
||||
}
|
||||
|
||||
pub fn task_delete_self() {
|
||||
unsafe {
|
||||
delete_task(0 as *const core::ffi::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_threading_available() -> bool {
|
||||
unsafe { global_threading_available_c() == 1 }
|
||||
}
|
||||
|
||||
pub fn enable_global_threading() {
|
||||
unsafe { enable_global_threading_c() };
|
||||
}
|
||||
|
||||
pub fn disable_global_threading() {
|
||||
unsafe { disable_global_threading_c() };
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use super::ffi::freertos_simple_once;
|
||||
|
||||
pub struct SimpleOnce {
|
||||
data: u8
|
||||
}
|
||||
|
||||
impl SimpleOnce {
|
||||
pub const fn new() -> Self {
|
||||
Self{data: 0}
|
||||
}
|
||||
|
||||
// returns true only once
|
||||
pub fn once(&self) -> bool {
|
||||
unsafe{freertos_simple_once(&self.data) == 1}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
use core::marker::{self, PhantomData};
|
||||
|
||||
use super::ffi::*;
|
||||
use super::sync::{StaticInit, StaticReadOnceLock};
|
||||
|
||||
pub struct MessageQueueSender<T>
|
||||
where
|
||||
T: 'static + Clone + Copy,
|
||||
{
|
||||
queue_id: *const core::ffi::c_void,
|
||||
_marker: marker::PhantomData<T>, // we do not actually own any T, only take them in impl
|
||||
}
|
||||
|
||||
impl<T> MessageQueueSender<T>
|
||||
where
|
||||
T: 'static + Clone + Copy,
|
||||
{
|
||||
pub fn send(&self, message: T) -> Result<(), T> {
|
||||
// we move ownership out to C (so must not drop it)
|
||||
// regaining ownership in receive() where it then will be dropped
|
||||
let message = core::mem::ManuallyDrop::new(message);
|
||||
let res: u8;
|
||||
unsafe {
|
||||
// safe because:
|
||||
// OS will read not more than length of message queue elements
|
||||
// queue was created with size_of::<T> as length of message queue elements
|
||||
// in MessageQueue::static_init()
|
||||
// making this pointer access safe as long as OS is holding the contract
|
||||
let message_pointer: *const core::ffi::c_void =
|
||||
&message as *const _ as *const core::ffi::c_void;
|
||||
res = super::ffi::freertos_queue_send(self.queue_id, message_pointer);
|
||||
}
|
||||
if res == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
// give the message back to let user decide when to drop
|
||||
Err(core::mem::ManuallyDrop::into_inner(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T> Clone for MessageQueueSender<T>
|
||||
where
|
||||
T: 'static + Clone + Copy,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
queue_id: self.queue_id,
|
||||
_marker: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for MessageQueueSender<T> where T: 'static + Clone + Copy {}
|
||||
|
||||
pub struct MessageQueue<T, const DEPTH: usize>
|
||||
where
|
||||
T: 'static + Clone + Copy,
|
||||
{
|
||||
queue: StaticReadOnceLock<[T; DEPTH]>,
|
||||
queue_data: StaticReadOnceLock<[core::ffi::c_char; Sizes::QUEUE_DATA_SIZE]>,
|
||||
queue_id: Option<*const core::ffi::c_void>,
|
||||
}
|
||||
|
||||
impl<T, const DEPTH: usize> MessageQueue<T, DEPTH>
|
||||
where
|
||||
T: 'static + Clone + Copy,
|
||||
{
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
// SAFE: this is uninitialized memory for C to use
|
||||
// we only type it by T to get the size right,
|
||||
// the array can not be used other than once, which is when it is
|
||||
// passed to C
|
||||
queue: StaticReadOnceLock::new([unsafe { core::mem::zeroed() }; DEPTH]),
|
||||
queue_data: StaticReadOnceLock::new([0; Sizes::QUEUE_DATA_SIZE]),
|
||||
queue_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive(&self) -> Result<T, ()> {
|
||||
let queue_id = if let Some(queue_id) = self.queue_id {
|
||||
queue_id
|
||||
} else {
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let mut message = core::mem::MaybeUninit::uninit();
|
||||
let res: u8;
|
||||
unsafe {
|
||||
// safe beacuse:
|
||||
// OS will write not more than length of message queue elements
|
||||
// queue was created with size_of::<T> as length of message queue elements
|
||||
// in MessageQueue::static_init()
|
||||
// making this pointer access safe as long as OS is holding the contract
|
||||
let message_pointer: *mut core::ffi::c_void =
|
||||
message.as_mut_ptr() as *mut T as *mut core::ffi::c_void;
|
||||
res = super::ffi::freertos_queue_receive(queue_id, message_pointer);
|
||||
}
|
||||
if res == 1 {
|
||||
// SAFE: the OS only returns data passed into the queue, which is only ever a valid T
|
||||
// TODO: alignment correct?
|
||||
let message = unsafe { message.assume_init() };
|
||||
Ok(message)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sender(&self) -> MessageQueueSender<T> {
|
||||
if let Some(queue_id) = self.queue_id {
|
||||
return MessageQueueSender {
|
||||
queue_id,
|
||||
_marker: PhantomData::default(),
|
||||
};
|
||||
} else {
|
||||
panic!("MessageQeue::send() called on uninitialized MessageQueue, make sure to call static_init() first")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const DEPTH: usize> StaticInit for MessageQueue<T, DEPTH>
|
||||
where
|
||||
T: 'static + Copy + Clone + Default,
|
||||
{
|
||||
fn static_init(&'static mut self) {
|
||||
let depth: u32 = u32::try_from(DEPTH).unwrap(); //TODO check these
|
||||
let element_size: u32 = u32::try_from(core::mem::size_of::<T>()).unwrap();
|
||||
let queue: *mut u8 = self.queue.take_no_init().unwrap() as *mut T as *mut u8;
|
||||
let queue_data = self.queue_data.take_no_init().unwrap();
|
||||
let queue_id = unsafe {
|
||||
super::ffi::freertos_queue_create_static(
|
||||
depth,
|
||||
element_size,
|
||||
queue_data.as_mut_ptr(),
|
||||
queue_data.len().try_into().unwrap(),
|
||||
queue,
|
||||
)
|
||||
};
|
||||
|
||||
if queue_id == 0 as *mut core::ffi::c_void {
|
||||
panic!("could not create Queue");
|
||||
}
|
||||
self.queue_id = Some(queue_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// TODO consider sync and send instead of copy for the sharable states
|
||||
|
||||
mod mutex_raw;
|
||||
mod mutex;
|
||||
pub use self::mutex::{Mutex, MutexClone};
|
||||
|
||||
mod static_read_once_lock;
|
||||
pub use self::static_read_once_lock::{StaticInit,StaticReadOnceLock};
|
||||
|
||||
//mod spmc_mutex;
|
||||
//pub use spmc_mutex::McspMutex;
|
||||
@@ -0,0 +1,131 @@
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use super::{
|
||||
mutex_raw::{RawMutex, RawMutexClone, RawMutexGuard},
|
||||
StaticInit,
|
||||
};
|
||||
|
||||
// TODO: Differences to std::Mutex
|
||||
|
||||
// No poisoning, as panic halts all execution
|
||||
|
||||
pub struct Mutex<T> {
|
||||
data: T,
|
||||
// marker to track initialization
|
||||
// will only be set in static_init, where it is
|
||||
// ensured to be pointing to a 'static reference
|
||||
pointer: Option<*mut T>,
|
||||
mutex: RawMutex,
|
||||
}
|
||||
|
||||
impl<T> Mutex<T> {
|
||||
pub const fn new(init_value: T) -> Self {
|
||||
Self {
|
||||
data: init_value,
|
||||
pointer: None,
|
||||
mutex: RawMutex::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// takes &mut self to be able to work in uninitialized state
|
||||
// where we do not have a raw pointer to get a mut ref from
|
||||
// other than std::sync::Mutex, this Mutex is never to be cloned
|
||||
// (the clone() fn returns MutexClone), so it is assumed that the
|
||||
// owner will have mut access
|
||||
pub fn lock(&mut self) -> MutexGuard<'_,T> {
|
||||
let guard = self.mutex.take();
|
||||
|
||||
// Threadsafety is ensured by the inner RawMutex, which will
|
||||
// be used by any Clones of this Mutex.
|
||||
// Note that the inner RawMutex might be uninitialized. This will
|
||||
// happen if this Mutex was not initialized with static_init.
|
||||
// As clone() checks for initialization, this can only happen if no
|
||||
// clones exist. In this case, threadsafety is no concern, as only
|
||||
// One instance and no Clone of this Mutex can exist (it does not impl
|
||||
// Copy). Borrowing rules ensure in that case that only one guard exits
|
||||
// per Mutex, making access via &mut self.data safe.
|
||||
MutexGuard {
|
||||
_guard: guard,
|
||||
data: &mut self.data,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a clone
|
||||
// MutexClone is Copy/Clone, so the address passed out needs to be valid
|
||||
// for &'static as the clone might dereference it (after taking the mutex)
|
||||
// This is ensured in static_init, which can only be called on static
|
||||
// objects and will set self.pointer as a signal. As Mutex does not impl
|
||||
// Copy, self can not be a non-static uninitialized Copy.
|
||||
// The clone upholds the same guarantees in its lock fn as Mutex does,
|
||||
// so the pointer to the data can safely be passed on
|
||||
pub fn clone(&self) -> MutexClone<T> {
|
||||
if let Some(pointer) = self.pointer {
|
||||
MutexClone {
|
||||
data: pointer,
|
||||
mutex: self.mutex.clone(),
|
||||
}
|
||||
} else {
|
||||
panic!("Mutex::clone() called on unitialized Mutex, call static_init() first");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StaticInit for Mutex<T> {
|
||||
fn static_init(&'static mut self) {
|
||||
self.mutex.static_init();
|
||||
// self is &'static, so the
|
||||
// address of the member is valid for whole runtime
|
||||
// cached here so its validity can be checked in clone()
|
||||
self.pointer = Some(&mut self.data);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MutexClone<T> {
|
||||
data: *mut T,
|
||||
mutex: RawMutexClone,
|
||||
}
|
||||
|
||||
impl<T> MutexClone<T> {
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
let guard = self.mutex.take();
|
||||
// SAFE: We only pass out the reference to the data
|
||||
// after acquiring the inner mutex
|
||||
// so, from here on, this guard is the only holder of the reference
|
||||
// and owns it until it is dropped, when it also gives back
|
||||
// the mutex
|
||||
// Borrowing rules ensure that only one guard exits per Mutex
|
||||
MutexGuard {
|
||||
_guard: guard,
|
||||
data: unsafe { &mut *self.data },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for MutexClone<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: self.data,
|
||||
mutex: self.mutex,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for MutexClone<T> {}
|
||||
|
||||
pub struct MutexGuard<'a, T> {
|
||||
_guard : RawMutexGuard,
|
||||
data: &'a mut T,
|
||||
}
|
||||
|
||||
impl<T> Deref for MutexGuard<'_, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for MutexGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
use core::panic;
|
||||
|
||||
use super::super::ffi::*;
|
||||
use super::static_read_once_lock::{StaticInit, StaticReadOnceLock};
|
||||
|
||||
pub struct RawMutex {
|
||||
mutex_data: StaticReadOnceLock<[core::ffi::c_char; Sizes::MUTEX_DATA_SIZE]>,
|
||||
mutex_handle: Option<*const core::ffi::c_void>,
|
||||
}
|
||||
|
||||
// Shared access to a RawMutex (which does not implement Clone)
|
||||
pub struct RawMutexClone {
|
||||
mutex_handle: *const core::ffi::c_void,
|
||||
}
|
||||
|
||||
unsafe impl Send for RawMutexClone {
|
||||
|
||||
}
|
||||
|
||||
impl Clone for RawMutexClone {
|
||||
fn clone(&self) -> Self {
|
||||
Self{mutex_handle: self.mutex_handle}
|
||||
}
|
||||
}
|
||||
|
||||
impl Copy for RawMutexClone{}
|
||||
|
||||
impl RawMutexClone {
|
||||
pub fn take(&self) -> RawMutexGuard {
|
||||
// Safe: Clones can only be made from initialized Mutexes
|
||||
unsafe { freertos_mutex_take(self.mutex_handle) };
|
||||
RawMutexGuard {
|
||||
mutex_handle: Some(self.mutex_handle),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl RawMutex {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
mutex_data: StaticReadOnceLock::new([0; Sizes::MUTEX_DATA_SIZE]),
|
||||
mutex_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Does not panic/err on uninitialized mutex
|
||||
// Cloning a mutex checks for initialization, so we can be sure to either
|
||||
// - be the only instance of this mutex, which is threadsafe
|
||||
// - be shared and initialized, which is threadsafe
|
||||
pub fn take(&self) -> RawMutexGuard {
|
||||
if let Some(handle) = self.mutex_handle {
|
||||
// Safe: Option is set only when create call succeeded
|
||||
unsafe { freertos_mutex_take(handle) };
|
||||
}
|
||||
RawMutexGuard {
|
||||
mutex_handle: self.mutex_handle,
|
||||
}
|
||||
}
|
||||
|
||||
// We do not implement Clone, as we want to return a RawMutexClone, but other than that
|
||||
// it is used quite the same, so we use `clone()` as function name
|
||||
pub fn clone(&self) -> RawMutexClone {
|
||||
// clone should be an init operation, so we can panic here on uninitialized mutex
|
||||
match self.mutex_handle {
|
||||
None => panic!(
|
||||
"RawMutex::clone() on uninitialized Mutex, make sure to call static_init() first"
|
||||
),
|
||||
Some(handle) => RawMutexClone {
|
||||
mutex_handle: handle,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticInit for RawMutex {
|
||||
fn static_init(&'static mut self) {
|
||||
let mutex_data = self.mutex_data.take_no_init().unwrap();
|
||||
let handle = unsafe {
|
||||
freertos_mutex_create_static(
|
||||
mutex_data.as_mut_ptr(),
|
||||
mutex_data.len().try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
if handle == 0 as *const core::ffi::c_void {
|
||||
panic!("RawMutex::init: Could not create mutex");
|
||||
}
|
||||
self.mutex_handle = Some(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// can be uninitialized, but as we do not provide a new() the only way to
|
||||
// acquire an uninitialized Guard is via a take() on an uninitialized RawMutex
|
||||
// which is threadsafe (see there)
|
||||
pub struct RawMutexGuard {
|
||||
mutex_handle: Option<*const core::ffi::c_void>,
|
||||
}
|
||||
|
||||
impl Drop for RawMutexGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(handle) = self.mutex_handle {
|
||||
// Safe: RawMutex only returns a handle when create call succeeded
|
||||
unsafe { freertos_mutex_give(handle) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use super::{mutex::{Mutex, MutexClone}, static_read_once_cell::StaticInit};
|
||||
|
||||
struct McspMutex<T> {
|
||||
mutex: Mutex<T>,
|
||||
}
|
||||
|
||||
impl<T> McspMutex<T>
|
||||
where
|
||||
T: Copy + Clone,
|
||||
{
|
||||
pub fn read(&mut self) -> T {
|
||||
*self.mutex.lock()
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: T) {
|
||||
*self.mutex.lock() = data;
|
||||
}
|
||||
|
||||
pub fn clone(&self)-> McspMutexClone<T> {
|
||||
McspMutexClone { mutex: self.mutex.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StaticInit for McspMutex<T> {
|
||||
fn static_init(&'static mut self) {
|
||||
self.mutex.static_init();
|
||||
}
|
||||
}
|
||||
|
||||
struct McspMutexClone<T> {
|
||||
mutex: MutexClone<T>
|
||||
}
|
||||
|
||||
impl<T> McspMutexClone<T> where T: Clone + Copy {
|
||||
pub fn read(&self) -> T {
|
||||
*self.mutex.lock()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use core::cell::UnsafeCell;
|
||||
use super::super::once::SimpleOnce;
|
||||
|
||||
pub trait StaticInit {
|
||||
fn static_init(&'static mut self);
|
||||
}
|
||||
|
||||
|
||||
pub struct StaticReadOnceLock<T> {
|
||||
inner: UnsafeCell<T>,
|
||||
once: SimpleOnce
|
||||
}
|
||||
|
||||
impl<T: 'static> StaticReadOnceLock<T> {
|
||||
pub const fn new(init: T) -> Self {
|
||||
Self{inner: UnsafeCell::new(init), once: SimpleOnce::new()}
|
||||
}
|
||||
|
||||
pub fn take_no_init(&'static self)-> Option<&'static mut T> {
|
||||
if self.once.once() {
|
||||
return Some(unsafe{&mut *self.inner.get()})
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + StaticInit> StaticReadOnceLock<T> {
|
||||
pub fn take(&'static self) -> Option<&'static mut T> {
|
||||
|
||||
if self.once.once() {
|
||||
// SAFE: Prozected by once
|
||||
let inner: &mut T = unsafe{&mut *self.inner.get()};
|
||||
inner.static_init();
|
||||
// Re-Borrow inner.
|
||||
// TODO Is unsafe by leaking static references via static_init()
|
||||
return Some(unsafe{&mut *self.inner.get()})
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
unsafe impl<T> Sync for StaticReadOnceLock<T> {}
|
||||
@@ -0,0 +1,360 @@
|
||||
use core::panic;
|
||||
|
||||
use super::{
|
||||
ffi::{self, *},
|
||||
sync::StaticReadOnceLock,
|
||||
};
|
||||
|
||||
pub struct Sizes {}
|
||||
|
||||
impl Sizes {
|
||||
pub const MINIMAL_STACK_SIZE: usize = ffi::Sizes::TASK_MINIMAL_STACK_SIZE;
|
||||
}
|
||||
|
||||
// Thread Metadata
|
||||
//
|
||||
// Metadata is stored in a separate rust struct.
|
||||
// This includes the thread's name. The name could also be stored in FreeRTOS native
|
||||
// metadata but this poses the following problems:
|
||||
// - Converting C Strings to rust is always a hassle
|
||||
// - The name is passed as a pointer with unknown lifetime
|
||||
// Especially the second problem can be quite harmful with access to "freed" data.
|
||||
// To avoid the problems, a rust object with static lifetime is created and stored in
|
||||
// FreeRTOS thread local storage as void pointer.
|
||||
// Recasting the void pointer back is inherently unsafe but can be managed.
|
||||
// Problems arise on threads created in C. Here, convention states to set the thread local
|
||||
// storage to NULL. Even if that is missed, the FreeRTOS handle is stored in the ThreadHandle struct by rust.
|
||||
// By comparing the handle used to obtain the thread local storage pointer with the handle
|
||||
// within the ThreadHandle object, the validity of the returned pointer can be checked with
|
||||
// high confidence (probability of finding a random 32bit number n at memory location n + x
|
||||
// where x is dependant on the layout of the ThreadHandle struct).
|
||||
pub struct ThreadHandle {
|
||||
name: &'static str,
|
||||
freertos_handle: Option<*const core::ffi::c_void>,
|
||||
}
|
||||
|
||||
impl ThreadHandle {
|
||||
pub fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
|
||||
pub fn delay(&self, time: core::time::Duration) {
|
||||
// freertos_task_delay must not be called when no task
|
||||
// running. If that is the case, freertos_handle will be None
|
||||
if self.freertos_handle == None {
|
||||
panic!("Delay called on non running thread");
|
||||
//TODO call a idling delay?
|
||||
} else {
|
||||
let time_32: u32;
|
||||
let time_option = TryInto::<u32>::try_into(time.as_millis());
|
||||
if let Ok(time) = time_option {
|
||||
time_32 = time;
|
||||
} else {
|
||||
time_32 = u32::MAX;
|
||||
}
|
||||
// SAFE: pass by value into OS
|
||||
unsafe { freertos_task_delay(time_32) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suspend(&self) {
|
||||
if let Some(handle) = self.freertos_handle {
|
||||
//SAFE: handle is only set if returned by OS for a valid task
|
||||
unsafe {
|
||||
freertos_task_suspend(handle);
|
||||
};
|
||||
} else {
|
||||
panic!("suspend called on non running thread");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&self) -> ! {
|
||||
if let Some(handle) = self.freertos_handle {
|
||||
//SAFE: handle is only set if returned by OS for a valid task
|
||||
unsafe {
|
||||
freertos_task_delete(handle);
|
||||
};
|
||||
} else {
|
||||
panic!("delete called on non running thread");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack_watermark(&self) -> u32 {
|
||||
// freertos_task_stack_watermark must not be called when no task
|
||||
// is running. If that is the case, freertos_handle will be None
|
||||
if self.freertos_handle == None {
|
||||
0
|
||||
} else {
|
||||
unsafe { freertos_task_stack_watermark() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ThreadHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
name: self.name,
|
||||
freertos_handle: self.freertos_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Copy for ThreadHandle {}
|
||||
|
||||
pub fn current() -> ThreadHandle {
|
||||
// SAFE: Is a getter that will return either a valid pointer or NULL
|
||||
let freertos_handle = unsafe { freertos_task_current() };
|
||||
if freertos_handle == 0 as *const core::ffi::c_void {
|
||||
return ThreadHandle {
|
||||
name: "No Thread Running",
|
||||
freertos_handle: None,
|
||||
};
|
||||
}
|
||||
|
||||
// freertos_handle is valid, create a default return value for error paths
|
||||
// This will be returned if checks of the data stored in thread local storage
|
||||
// fail.
|
||||
// Fields are set to sane defaults
|
||||
let c_handle = ThreadHandle {
|
||||
name: "C Thread",
|
||||
freertos_handle: Some(freertos_handle),
|
||||
};
|
||||
|
||||
// SAFE: this variable is set in StaticThread::spawn to a &'static reference or to NULL in C.
|
||||
// Only way this is unsafe, is if C spawned a thread and did not nullpointer it.
|
||||
// We will check the redundant handle information to catch this case
|
||||
let pointer = unsafe { freertos_task_storage_get(freertos_handle) };
|
||||
|
||||
// Nullpointer, C created this task and behaved well
|
||||
if pointer == 0 as *const core::ffi::c_void {
|
||||
return c_handle;
|
||||
}
|
||||
|
||||
let pointer = pointer as *const ThreadHandle;
|
||||
// pointer is not aligned, someone messed up
|
||||
if !pointer.is_aligned() {
|
||||
return c_handle;
|
||||
}
|
||||
|
||||
// SAFE: is not a nullpointer and aligned, so we may dereference
|
||||
// Two options:
|
||||
// 1) created by StaticThread::spawn and valid
|
||||
// 2) created by C and not nullpointered: Check the stored freertos_handle and
|
||||
// compare it to the handle used to get the pointer. Call it a ~32bit checksum
|
||||
// and trust it.
|
||||
let handle = unsafe { *(pointer) };
|
||||
|
||||
// Stored handle will always have freertos_handle set
|
||||
if let Some(freertos_handle_from_thread) = handle.freertos_handle {
|
||||
// verify freertos_handle
|
||||
if freertos_handle_from_thread == freertos_handle {
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
return c_handle;
|
||||
}
|
||||
|
||||
pub struct StaticThreadInner<const STACKSIZE: usize> {
|
||||
stack: StaticReadOnceLock<[core::ffi::c_char; STACKSIZE]>,
|
||||
thread_data: StaticReadOnceLock<[core::ffi::c_char; ffi::Sizes::TASK_DATA_SIZE]>,
|
||||
handle: StaticReadOnceLock<ThreadHandle>,
|
||||
priority: u32,
|
||||
name: &'static str,
|
||||
pub closure_data: Option<*mut core::ffi::c_void>,
|
||||
}
|
||||
|
||||
pub struct StaticThread<const STACKSIZE: usize> {
|
||||
pub inner: StaticThreadInner<STACKSIZE>,
|
||||
}
|
||||
|
||||
pub struct StaticThreadPeriodic<const STACKSIZE: usize> {
|
||||
pub inner: StaticThreadInner<STACKSIZE>,
|
||||
period: core::time::Duration,
|
||||
}
|
||||
|
||||
impl<const STACKSIZE: usize> StaticThreadInner<STACKSIZE> {
|
||||
|
||||
// priority may be u32::MAX to select highest priority
|
||||
pub const fn new(name: &'static str, priority: u32) -> Self {
|
||||
Self {
|
||||
stack: StaticReadOnceLock::new([0; STACKSIZE]),
|
||||
thread_data: StaticReadOnceLock::new([0; ffi::Sizes::TASK_DATA_SIZE]),
|
||||
handle: StaticReadOnceLock::new(ThreadHandle {
|
||||
name: name,
|
||||
freertos_handle: None,
|
||||
}),
|
||||
priority,
|
||||
name,
|
||||
closure_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
// This could partially be moved into impl StaticInit
|
||||
// But we know the actual type of the closure only at start(), not before,
|
||||
// so we can not safely determine how much space we need to reserve before
|
||||
// the stack. That means we can not call freertos_create_task_static() there.
|
||||
// So we just omit StaticInit and do it here
|
||||
fn spawn<F: Sized + 'static>(
|
||||
&'static mut self,
|
||||
thread_function: F,
|
||||
thread_parameter: *const core::ffi::c_void,
|
||||
callback: unsafe extern "C" fn(*mut core::ffi::c_void) -> !,
|
||||
) -> ThreadHandle {
|
||||
|
||||
//SAFE: freertos_task_priority_max() is a getter for a compile time const
|
||||
if (self.priority != u32::MAX) && (self.priority > unsafe{super::ffi::freertos_task_priority_max()}) {
|
||||
panic!("StaticThread(\"{}\")::spawn: priority {} is larger than maximum ({})", self.name, self.priority, unsafe{super::ffi::freertos_task_priority_max()});
|
||||
}
|
||||
|
||||
let stack = self.stack.take_no_init().unwrap();
|
||||
|
||||
let closure_space = core::mem::size_of::<F>();
|
||||
// Will be checked later on, but this way we get a more precise output instead of a generic panic
|
||||
if stack.len() < closure_space {
|
||||
panic!("StaticThread(\"{}\")::spawn: Stack is too small to store closure. Increase stack size!", self.name);
|
||||
}
|
||||
|
||||
// we need to store the closure in the context of the new thread
|
||||
// but we do not know its type (is decided when using this function, not
|
||||
// when creating the Thread struct), so we can not use a member of Thread.
|
||||
// The only place left is the new thread's stack which will be owned by the
|
||||
// new thread.
|
||||
// Stack is behind a &'static reference, so the memory is a valid target to write
|
||||
// data to.
|
||||
// SAFE: The closure is 'static so we are safe to move it into the new thread. The stack is a &'static pointer
|
||||
// and the cast checks its size
|
||||
unsafe {
|
||||
core::ptr::write_unaligned(
|
||||
stack[..closure_space].as_mut_ptr() as *mut F,
|
||||
thread_function,
|
||||
)
|
||||
};
|
||||
|
||||
// remember the location of the closure for later (we can not use self.stack,
|
||||
// as it is behind a StaticReadOnceCell so it can safely be passed to the OS)
|
||||
self.closure_data = Some(stack as *mut _ as *mut core::ffi::c_void);
|
||||
|
||||
// move the beginning of the stack which will be used by the OS
|
||||
let stack = &mut stack[closure_space..];
|
||||
|
||||
// Will be checked later on, but this way we get a more precise output instead of a generic panic
|
||||
if stack.len() < super::ffi::Sizes::TASK_MINIMAL_STACK_SIZE {
|
||||
panic!(
|
||||
"StaticThread(\"{}\")::spawn: Stack size after storing closure is {}, which is too small. Minimal size is {}.",
|
||||
self.name, stack.len(),
|
||||
super::ffi::Sizes::TASK_MINIMAL_STACK_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
let thread_data = self.thread_data.take_no_init().unwrap();
|
||||
|
||||
// SAFE: We only pass pointers derived from &'static references to the OS, so the pointers will
|
||||
// be valid for the complete runtime. This includes values borrowed by the closure
|
||||
let freertos_handle = unsafe {
|
||||
super::ffi::freertos_create_task_static(
|
||||
callback,
|
||||
thread_parameter,
|
||||
self.priority,
|
||||
thread_data.as_mut_ptr(),
|
||||
thread_data.len().try_into().unwrap(),
|
||||
stack.as_mut_ptr(),
|
||||
stack.len().try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
if freertos_handle == 0 as *mut core::ffi::c_void {
|
||||
panic!("could not create thread");
|
||||
}
|
||||
|
||||
let handle = self.handle.take_no_init().unwrap();
|
||||
handle.freertos_handle = Some(freertos_handle);
|
||||
|
||||
// Store the Handle in task local storage, so we can use the rust types later on
|
||||
// Stored data will be used as non-mut shared &'static
|
||||
//SAFE: handle is &'static behind a StaticReadOnceCell, can not be used in rust after this call
|
||||
unsafe {
|
||||
freertos_task_storage_set(
|
||||
freertos_handle,
|
||||
handle as *const _ as *const core::ffi::c_void,
|
||||
)
|
||||
};
|
||||
// this creates a copy of handle, as it impls Copy
|
||||
*handle
|
||||
}
|
||||
}
|
||||
|
||||
impl<const STACKSIZE: usize> StaticThread<STACKSIZE> {
|
||||
// priority may be u32::MAX to select highest priority
|
||||
pub const fn new(name: &'static str, priority: u32) -> Self {
|
||||
Self {
|
||||
inner: StaticThreadInner::new(name,priority),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn callback<F: FnMut() -> ! + 'static>(
|
||||
thread_object: *mut core::ffi::c_void,
|
||||
) -> ! {
|
||||
let thread: &mut Self;
|
||||
unsafe {
|
||||
let pointer = thread_object as *mut Self;
|
||||
thread = &mut *pointer;
|
||||
}
|
||||
if let Some(closure_data) = thread.inner.closure_data {
|
||||
// Documentation of ptr::read_unaligned()says:
|
||||
// * src must be valid for reads.
|
||||
// -> it is memory which came from a &'static reference
|
||||
// *src must point to a properly initialized value of type T.
|
||||
// -> we only set the Option after using ptr::write_unaligned()
|
||||
let mut closure = (closure_data as *mut F).read_unaligned();
|
||||
|
||||
closure();
|
||||
} else {
|
||||
panic!("StaticThread::callback(): No closure set");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn<F: FnMut() -> ! + 'static>(&'static mut self, thread_function: F) -> ThreadHandle {
|
||||
self.inner.spawn(thread_function, self as *const _ as *const core::ffi::c_void, Self::callback::<F>)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const STACKSIZE: usize> StaticThreadPeriodic<STACKSIZE> {
|
||||
// priority may be u32::MAX to select highest priority
|
||||
pub const fn new(name: &'static str, priority: u32, period: core::time::Duration) -> Self {
|
||||
Self {
|
||||
inner: StaticThreadInner::new(name, priority),
|
||||
period: period,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn callback<F: FnMut() -> () + 'static>(
|
||||
thread_object: *mut core::ffi::c_void,
|
||||
) -> ! {
|
||||
let thread: &mut Self;
|
||||
unsafe {
|
||||
let pointer = thread_object as *mut Self;
|
||||
thread = &mut *pointer;
|
||||
}
|
||||
if let Some(closure_data) = thread.inner.closure_data {
|
||||
// Documentation of ptr::read_unaligned()says:
|
||||
// * src must be valid for reads.
|
||||
// -> it is memory which came from a &'static reference
|
||||
// *src must point to a properly initialized value of type T.
|
||||
// -> we only set the Option after using ptr::write_unaligned()
|
||||
let mut closure = (closure_data as *mut F).read_unaligned();
|
||||
|
||||
let current = current();
|
||||
|
||||
loop {
|
||||
closure();
|
||||
// TODO precise period
|
||||
current.delay(thread.period);
|
||||
}
|
||||
} else {
|
||||
panic!("StaticThread::callback(): No closure set");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn<F: FnMut() -> () + 'static>(&'static mut self, thread_function: F) -> ThreadHandle {
|
||||
self.inner.spawn(thread_function, self as *const _ as *const core::ffi::c_void, Self::callback::<F>)
|
||||
}
|
||||
}
|
||||
@@ -3,73 +3,76 @@
|
||||
pub struct Stdout {}
|
||||
pub struct Stderr {}
|
||||
|
||||
use core::fmt::{Error, Write};
|
||||
// TODO why or how does pub work (users from same crate need to use core::fmt::... )
|
||||
pub use core::fmt::{Error, Write};
|
||||
|
||||
extern "C" {
|
||||
pub fn write(fd: core::ffi::c_int, buffer: *const core::ffi::c_void, count: usize) -> core::ffi::c_int;
|
||||
}
|
||||
|
||||
impl Write for Stdout {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), Error> {
|
||||
|
||||
#[allow(irrefutable_let_patterns)] // is refutable on some exotic targets. See above
|
||||
let Ok(len_converted) = s.as_bytes().len().try_into() else {
|
||||
return Err(Error);
|
||||
};
|
||||
// Safe because write will only read the pointer an then return
|
||||
let result = unsafe {
|
||||
write(
|
||||
let written = unsafe {
|
||||
crate::fsrc::osal::ffi::write(
|
||||
1,
|
||||
s.as_bytes().as_ptr() as *const core::ffi::c_void,
|
||||
s.as_bytes().len(),
|
||||
len_converted,
|
||||
)
|
||||
};
|
||||
// We do not retry incomplete writes, this is stdout after all...
|
||||
let safe_count: i32 = match s.as_bytes().len().try_into() {
|
||||
Ok(count)=> count,
|
||||
Err(_) => return Err(Error)
|
||||
let Ok(written_unsigned): Result<usize, _> = written.try_into() else {
|
||||
return Err(Error);
|
||||
};
|
||||
if result < safe_count {
|
||||
Err(Error)
|
||||
if written_unsigned < s.as_bytes().len() {
|
||||
return Err(Error);
|
||||
} else {
|
||||
Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Stderr {
|
||||
fn write_str(&mut self, s: &str) -> Result<(), Error> {
|
||||
|
||||
#[allow(irrefutable_let_patterns)] // is refutable on some exotic targets. See above
|
||||
let Ok(len_converted) = s.as_bytes().len().try_into() else {
|
||||
return Err(Error);
|
||||
};
|
||||
// Safe because write will only read the pointer an then return
|
||||
let result = unsafe {
|
||||
write(
|
||||
let written = unsafe {
|
||||
crate::fsrc::osal::ffi::write(
|
||||
2,
|
||||
s.as_bytes().as_ptr() as *const core::ffi::c_void,
|
||||
s.as_bytes().len(),
|
||||
len_converted,
|
||||
)
|
||||
};
|
||||
// We do not retry incomplete writes, this is stdout after all...
|
||||
let safe_count: i32 = match s.as_bytes().len().try_into() {
|
||||
Ok(count)=> count,
|
||||
Err(_) => return Err(Error)
|
||||
let Ok(written_unsigned): Result<usize, _> = written.try_into() else {
|
||||
return Err(Error);
|
||||
};
|
||||
if result < safe_count {
|
||||
Err(Error)
|
||||
if written_unsigned < s.as_bytes().len() {
|
||||
return Err(Error);
|
||||
} else {
|
||||
Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO I am not sure if crate::fsrc::sif::Stdout is the correct way here
|
||||
#[macro_export]
|
||||
macro_rules! sifln {
|
||||
($(,)?) => (
|
||||
//let mut stdout = Outbytes {};
|
||||
writeln!(Stdout {});
|
||||
{let _alwaysok = writeln!(crate::fsrc::sif::Stdout {});}
|
||||
);
|
||||
($($arg:tt)*) => (
|
||||
let _alwaysok = writeln!(crate::fsrc::sif::Stdout {}, $($arg)*);
|
||||
{let _alwaysok = writeln!(crate::fsrc::sif::Stdout {}, $($arg)*);}
|
||||
);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sif {
|
||||
($(,)?) => (
|
||||
{let _alwaysok = writeln!(crate::fsrc::sif::Stdout {});}
|
||||
);
|
||||
($($arg:tt)*) => (
|
||||
let _alwaysok = write!(crate::fsrc::sif::Stdout {}, $($arg)*);
|
||||
);
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
use core::slice;
|
||||
|
||||
use super::objectmanager::ObjectManager;
|
||||
|
||||
// TODO find a way to report uxTaskGetStackHighWaterMarks during runtime as TM?
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn task_entry(task_object: *mut core::ffi::c_void) {
|
||||
let task: &mut dyn TaskIF;
|
||||
unsafe {
|
||||
let pointer = task_object as *mut PeriodicTask;
|
||||
task = &mut *pointer;
|
||||
}
|
||||
task.run();
|
||||
}
|
||||
|
||||
pub trait ExecutableObjectIF {
|
||||
fn perform(&mut self);
|
||||
}
|
||||
|
||||
pub trait TaskIF<'a> {
|
||||
fn run(&mut self);
|
||||
fn get_stack_size(&self) -> u32;
|
||||
fn set_handle(&mut self, task_handle: *const core::ffi::c_void);
|
||||
fn get_handle(&self) -> *const core::ffi::c_void;
|
||||
fn get_objects(&'a self) -> &'a [&'a mut dyn crate::objectmanager::SystemObjectIF];
|
||||
fn initialize(&mut self, object_manager: &dyn ObjectManager) -> Result<(), ()>;
|
||||
}
|
||||
|
||||
pub struct PeriodicTask<'a> {
|
||||
pub stack_size: u32, //TODO generic type and safety
|
||||
pub task_handle: *const core::ffi::c_void,
|
||||
pub period: u32,
|
||||
pub task_objects: &'a mut [&'a mut dyn crate::objectmanager::SystemObjectIF],
|
||||
}
|
||||
|
||||
impl<'a> PeriodicTask<'a> {
|
||||
pub fn new(
|
||||
objects: &'a mut [&'a mut dyn crate::objectmanager::SystemObjectIF],
|
||||
stack_size: u32,
|
||||
period: u32,
|
||||
) -> PeriodicTask<'a> {
|
||||
let instance: PeriodicTask<'a> = Self {
|
||||
stack_size: stack_size,
|
||||
task_handle: 0 as *const core::ffi::c_void,
|
||||
period: period,
|
||||
task_objects: objects,
|
||||
};
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TaskIF<'a> for PeriodicTask<'a> {
|
||||
fn run(&mut self) {
|
||||
loop {
|
||||
for object in self.task_objects.iter_mut() {
|
||||
object.perform();
|
||||
}
|
||||
//TODO make this exact
|
||||
unsafe {
|
||||
crate::fsrc::osal::task_delay(self.period); //TODO type of delay should be generic but safe (cap to max in C)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn get_stack_size(&self) -> u32 {
|
||||
self.stack_size
|
||||
}
|
||||
fn set_handle(&mut self, task_handle: *const core::ffi::c_void) {
|
||||
self.task_handle = task_handle;
|
||||
}
|
||||
fn get_handle(&self) -> *const core::ffi::c_void {
|
||||
self.task_handle
|
||||
}
|
||||
fn get_objects(&'a self) -> &'a [&'a mut dyn crate::objectmanager::SystemObjectIF] {
|
||||
self.task_objects
|
||||
}
|
||||
fn initialize(&mut self, object_manager: &dyn ObjectManager) -> Result<(), ()> {
|
||||
for object in self.task_objects.iter_mut() {
|
||||
let result = object.initialize(object_manager);
|
||||
match result {
|
||||
Ok(()) => continue,
|
||||
Err(()) => return Err(()),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskExecutor<'a> {
|
||||
pub tasks: &'a mut [&'a mut dyn TaskIF<'a>],
|
||||
}
|
||||
|
||||
impl<'a> TaskExecutor<'a> {
|
||||
/// Initializes all tasks (and the objects contained within them)
|
||||
/// and starts them.
|
||||
///
|
||||
/// It also enables global threading, allowing threading APIs to be used (queues, mutexes etc)
|
||||
/// See TODO about threading
|
||||
///
|
||||
///# Arguments
|
||||
///
|
||||
/// * `delete_init_task` - If true, will delete the init task (the task this functions is called in) which means the function will not return
|
||||
///
|
||||
pub fn init_and_run(&mut self, delete_init_task: bool) {
|
||||
let object_manager = TaskObjectManager {
|
||||
tasks: unsafe { slice::from_raw_parts(self.tasks.as_ptr(), self.tasks.len()) },
|
||||
};
|
||||
// init uses unsafe methods and checks against the lock, so we need to enable it here
|
||||
crate::fsrc::osal::enable_global_threading();
|
||||
for task in self.tasks.iter_mut() {
|
||||
let _ = task.initialize(&object_manager).unwrap();
|
||||
}
|
||||
drop(object_manager);
|
||||
for task in self.tasks.iter_mut() {
|
||||
// we give away a raw pointer, to be called by an OS task
|
||||
// while this is generally very broken, we use a reference tied
|
||||
// to our own lifetime and destroy the task when we get dropped.
|
||||
// this way, the reference is guaranteed to be valid over our
|
||||
// lifetime while the task is deleted at the end of our lifetime
|
||||
let task_pointer: *const core::ffi::c_void =
|
||||
*task as *mut _ as *const core::ffi::c_void; //TODO this does work without the "*" in front of the task -> Why??
|
||||
let handle;
|
||||
unsafe {
|
||||
handle = crate::fsrc::osal::create_task(
|
||||
task_entry,
|
||||
task_pointer,
|
||||
u32::try_from(task.get_stack_size()).unwrap(),
|
||||
);
|
||||
}
|
||||
if handle == 0 as *mut core::ffi::c_void {
|
||||
panic!("could not create Task");
|
||||
} else {
|
||||
task.set_handle(handle);
|
||||
}
|
||||
}
|
||||
if delete_init_task {
|
||||
crate::osal::task_delete_self();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskObjectManager<'a> {
|
||||
tasks: &'a [&'a mut dyn TaskIF<'a>],
|
||||
}
|
||||
|
||||
impl<'a> crate::objectmanager::ObjectManager<'a> for TaskObjectManager<'a> {
|
||||
fn get_object(
|
||||
&self,
|
||||
id: crate::objectmanager::ObjectId,
|
||||
) -> Result<&'a dyn crate::objectmanager::SystemObjectIF, ()> {
|
||||
for task in self.tasks.iter() {
|
||||
for object in task.get_objects().iter() {
|
||||
if object.get_id() == id {
|
||||
return Ok(*object);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TaskExecutor<'a> {
|
||||
fn drop(&mut self) {
|
||||
crate::fsrc::osal::disable_global_threading();
|
||||
for task in self.tasks.iter_mut() {
|
||||
unsafe {
|
||||
// TODO print uxTaskGetStackHighWaterMark() for each stack
|
||||
crate::fsrc::osal::delete_task(task.get_handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+194
-207
@@ -1,242 +1,229 @@
|
||||
#![no_std]
|
||||
|
||||
#![feature(never_type)]
|
||||
#![feature(c_size_t)] // for ffi, tracking issue [88345]
|
||||
//TODO os errors in API calls
|
||||
//TODO look into a pattern for late initialized stuff, currently using Option (can we make it compile time safe?)
|
||||
|
||||
mod dh;
|
||||
pub mod fsrc;
|
||||
mod panic;
|
||||
|
||||
use core::fmt::Write;
|
||||
use core::panic::PanicInfo;
|
||||
use core::time::Duration;
|
||||
|
||||
use fsrc::objectmanager::SystemObjectIF;
|
||||
use fsrc::*;
|
||||
use osal::{
|
||||
queue::{MessageQueue, MessageQueueSender},
|
||||
sync::{Mutex, MutexClone, StaticInit, StaticReadOnceLock},
|
||||
thread,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
fn done();
|
||||
}
|
||||
use crate::{
|
||||
dh::EchoHandler,
|
||||
fsrc::{
|
||||
dh::debug::DeviceHandlerDebugger,
|
||||
osal::io::HardwareInterface,
|
||||
},
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(panic: &PanicInfo<'_>) -> ! {
|
||||
// unsafe { this breaks in ISR
|
||||
// osal::stop_it();
|
||||
// }
|
||||
// TODO: Make this unicode-safe
|
||||
_ = writeln!(crate::fsrc::sif::Stderr {},"");
|
||||
_ = write!(crate::fsrc::sif::Stderr {},"in task \"");
|
||||
unsafe {
|
||||
//TODO this breaks all the time...
|
||||
let task_name = core::ffi::CStr::from_ptr(osal::get_task_name());
|
||||
let task_name_utf8 = core::str::from_utf8(task_name.to_bytes());
|
||||
match task_name_utf8 {
|
||||
Ok(string) => {
|
||||
sif!("{}", string);
|
||||
}
|
||||
Err(_) => {
|
||||
_ = writeln!(crate::fsrc::sif::Stderr {},"Schei� Encoding");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = writeln!(crate::fsrc::sif::Stderr {},"\":");
|
||||
_ = writeln!(crate::fsrc::sif::Stderr {},"{}", panic);
|
||||
//TODO: stop RTOS, exit if hosted
|
||||
unsafe { done() };
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn rust_assert_called(ptr: *const core::ffi::c_char, line: core::ffi::c_ulong) {
|
||||
let file_name = unsafe {
|
||||
//TODO is from_ptr safe enough?
|
||||
let file_name = core::ffi::CStr::from_ptr(ptr);
|
||||
let file_name_utf8 = core::str::from_utf8(file_name.to_bytes());
|
||||
match file_name_utf8 {
|
||||
Ok(string) => {
|
||||
string
|
||||
}
|
||||
Err(_) => {
|
||||
"Schei� Encoding"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
panic!("assertion failed at {file_name}:{line}");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn rust_alloc_failed(){
|
||||
panic!("allocation failed!");
|
||||
}
|
||||
static THREAD_INIT: StaticReadOnceLock<
|
||||
thread::StaticThread<{ thread::Sizes::MINIMAL_STACK_SIZE + 2024 }>,
|
||||
> = StaticReadOnceLock::new(thread::StaticThread::new("Init", u32::MAX));
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn rust_main() {
|
||||
// we are already in a task (init task started by C), so we can use all APIs safely,
|
||||
// but start a new init task anyway to be able to control that task's stack here
|
||||
sifln!("Rust startup 🚀");
|
||||
mission();
|
||||
sifln!("Mission done");
|
||||
THREAD_INIT.take_no_init().unwrap().spawn(init_task);
|
||||
// delete self, init task will take over
|
||||
thread::current().delete();
|
||||
}
|
||||
|
||||
struct Test {
|
||||
a: u8,
|
||||
block: Duration,
|
||||
mutex: Mutex<u8>,
|
||||
}
|
||||
|
||||
struct Test2 {
|
||||
a: u8,
|
||||
period: Duration,
|
||||
}
|
||||
|
||||
impl Test {
|
||||
fn run(&mut self) {
|
||||
sifln!("Test: {} taking mutex", self.a);
|
||||
let mut guard = self.mutex.lock();
|
||||
let data: u8 = *guard;
|
||||
sifln!("Test: {} took mutex, was {}", self.a, data);
|
||||
osal::thread::current().delay(self.block);
|
||||
*guard = self.a;
|
||||
sifln!("Test: {} giving mutex, is now {}", self.a, *guard);
|
||||
drop(guard);
|
||||
sifln!("Test: {} gave mutex", self.a);
|
||||
}
|
||||
}
|
||||
|
||||
impl Test2 {
|
||||
pub fn run(&mut self, mutex: MutexClone<u8>, block: Duration) -> ! {
|
||||
sifln!(
|
||||
"Stack watermark {}",
|
||||
osal::thread::current().stack_watermark()
|
||||
);
|
||||
loop {
|
||||
sifln!("Test2: {} taking mutex", self.a);
|
||||
let mut guard = mutex.lock();
|
||||
let data: u8 = *guard;
|
||||
sifln!("{} took mutex, was {}", self.a, data);
|
||||
sifln!(
|
||||
"Test2: Stack watermark {}",
|
||||
osal::thread::current().stack_watermark()
|
||||
);
|
||||
osal::thread::current().delay(block);
|
||||
*guard = self.a;
|
||||
sifln!("Test2: {} giving mutex, is now {}", self.a, *guard);
|
||||
drop(guard);
|
||||
sifln!("Test2: {} gave mutex", self.a);
|
||||
osal::thread::current().delay(self.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticInit for Test {
|
||||
fn static_init(&'static mut self) {
|
||||
self.mutex.static_init();
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticInit for Test2 {
|
||||
fn static_init(&'static mut self) {}
|
||||
}
|
||||
|
||||
static TEST1: StaticReadOnceLock<Test> = StaticReadOnceLock::new(Test {
|
||||
a: 1,
|
||||
block: Duration::from_millis(1000),
|
||||
mutex: Mutex::new(13),
|
||||
});
|
||||
static THREAD_1: StaticReadOnceLock<
|
||||
thread::StaticThreadPeriodic<{ thread::Sizes::MINIMAL_STACK_SIZE + 512 }>,
|
||||
> = StaticReadOnceLock::new(thread::StaticThreadPeriodic::new(
|
||||
"Thread 1",
|
||||
2,
|
||||
Duration::from_millis(1000),
|
||||
));
|
||||
|
||||
static TEST2: StaticReadOnceLock<Test2> = StaticReadOnceLock::new(Test2 {
|
||||
a: 2,
|
||||
period: Duration::from_millis(5000),
|
||||
});
|
||||
|
||||
static THREAD_2: StaticReadOnceLock<
|
||||
thread::StaticThread<{ thread::Sizes::MINIMAL_STACK_SIZE + 512 }>,
|
||||
> = StaticReadOnceLock::new(thread::StaticThread::new("Thread 2", 2));
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct HandlerData {
|
||||
x: u32,
|
||||
y: f32,
|
||||
pub enum Message {
|
||||
OK,
|
||||
#[default]
|
||||
NONE,
|
||||
DATA(u8),
|
||||
}
|
||||
|
||||
struct Handler {
|
||||
id: objectmanager::ObjectId,
|
||||
command_queue: queues::MessageQueue<10>,
|
||||
data: datasets::OwnedDataset<HandlerData>,
|
||||
struct Receiver {
|
||||
queue: MessageQueue<Message, 100>,
|
||||
}
|
||||
|
||||
struct HandlerSender {
|
||||
id: objectmanager::ObjectId,
|
||||
other_handler: objectmanager::ObjectId,
|
||||
cycle: u8,
|
||||
other_handler_queue: queues::MessageQueueSender,
|
||||
other_data: datasets::ReferencedDataset<HandlerData>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
fn handle_message(&self, message: queues::Message) {
|
||||
match message {
|
||||
queues::Message::OK => {
|
||||
sifln!("OK");
|
||||
}
|
||||
queues::Message::FAILED => {
|
||||
sifln!("FAILED");
|
||||
}
|
||||
queues::Message::DATA(data) => {
|
||||
sifln!("p1: {}, p2 {}", data.p1, data.p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tasks::ExecutableObjectIF for Handler {
|
||||
fn perform(&mut self) {
|
||||
sifln!("Handler {} performs", self.id);
|
||||
let result = self.command_queue.receive();
|
||||
match result {
|
||||
Some(message) => self.handle_message(message),
|
||||
None => {
|
||||
sifln!("Handler {} got nothing", self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tasks::ExecutableObjectIF for HandlerSender {
|
||||
fn perform(&mut self) {
|
||||
sifln!("HandlerSender {} performs step {}", self.id, self.cycle);
|
||||
match self.cycle {
|
||||
0 => {
|
||||
let _ = self.other_handler_queue.send(queues::Message::OK);
|
||||
}
|
||||
1 => {
|
||||
let _ = self.other_handler_queue.send(queues::Message::FAILED);
|
||||
}
|
||||
2 => {
|
||||
let _ = self.other_handler_queue.send(queues::Message::DATA(
|
||||
queues::GenericMessageData { p1: 13, p2: 2 },
|
||||
));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
self.cycle += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemObjectIF for Handler {
|
||||
fn get_command_queue(&self) -> crate::fsrc::queues::MessageQueueSender {
|
||||
self.command_queue.get_sender()
|
||||
}
|
||||
fn get_id(&self) -> objectmanager::ObjectId {
|
||||
self.id
|
||||
}
|
||||
fn initialize(&mut self, _object_manager: &dyn objectmanager::ObjectManager) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl introspection::Introspection for HandlerSender {
|
||||
fn for_each_member(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> ()) {}
|
||||
|
||||
fn for_each_member_return(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> Option<()>) {}
|
||||
}
|
||||
|
||||
impl introspection::Introspection for Handler {
|
||||
fn for_each_member(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> ()) {
|
||||
(*f)(&self.command_queue, "command_queue");
|
||||
(*f)(&self.data, "data");
|
||||
}
|
||||
|
||||
fn for_each_member_return(&self, f: &mut dyn FnMut(&dyn core::any::Any, &str) -> Option<()>) {
|
||||
(*f)(&self.command_queue, "command_queue");
|
||||
(*f)(&self.data, "data");
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemObjectIF for HandlerSender {
|
||||
fn get_command_queue(&self) -> crate::fsrc::queues::MessageQueueSender {
|
||||
queues::MessageQueueSender::new() //TODO
|
||||
}
|
||||
|
||||
fn get_id(&self) -> objectmanager::ObjectId {
|
||||
self.id
|
||||
}
|
||||
fn initialize(&mut self, object_manager: &dyn objectmanager::ObjectManager) -> Result<(), ()> {
|
||||
let other_handler_maybe = object_manager.get_object(self.other_handler);
|
||||
let other_handler = match other_handler_maybe {
|
||||
Ok(other) => other,
|
||||
Err(_) => return Err(()),
|
||||
impl Receiver {
|
||||
fn run(&self) {
|
||||
let message = self.queue.receive();
|
||||
let message = if let Ok(message) = message {
|
||||
message
|
||||
} else {
|
||||
sifln!("receiver: got nothing");
|
||||
return;
|
||||
};
|
||||
self.other_handler_queue = other_handler.get_command_queue();
|
||||
|
||||
self.other_data
|
||||
.initialize(object_manager, self.other_handler)?;
|
||||
Ok(())
|
||||
match message {
|
||||
Message::OK => {
|
||||
sifln!("receiver: ok");
|
||||
}
|
||||
Message::NONE => {
|
||||
sifln!("receiver: NONE");
|
||||
}
|
||||
Message::DATA(data) => {
|
||||
sifln!("receiver: {}", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mission() {
|
||||
impl StaticInit for Receiver {
|
||||
fn static_init(&'static mut self) {
|
||||
self.queue.static_init();
|
||||
}
|
||||
}
|
||||
|
||||
struct Sender {
|
||||
count: u8,
|
||||
}
|
||||
|
||||
impl Sender {
|
||||
fn run(&mut self, queue: MessageQueueSender<Message>) {
|
||||
let _ = queue.send(Message::DATA(self.count));
|
||||
self.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static SENDER: StaticReadOnceLock<Sender> = StaticReadOnceLock::new(Sender { count: 0 });
|
||||
static RECEIVER: StaticReadOnceLock<Receiver> = StaticReadOnceLock::new(Receiver {
|
||||
queue: MessageQueue::new(),
|
||||
});
|
||||
static THREAD_3: StaticReadOnceLock<
|
||||
thread::StaticThreadPeriodic<{ thread::Sizes::MINIMAL_STACK_SIZE + 512 }>,
|
||||
> = StaticReadOnceLock::new(thread::StaticThreadPeriodic::new(
|
||||
"Thread 3",
|
||||
2,
|
||||
Duration::from_millis(200),
|
||||
));
|
||||
|
||||
fn init_task() -> ! {
|
||||
sifln!("Mission enter");
|
||||
|
||||
let mut h1 = Handler {
|
||||
id: 1,
|
||||
command_queue: queues::MessageQueue::new(),
|
||||
data: datasets::OwnedDataset::new(),
|
||||
};
|
||||
let mut h2 = HandlerSender {
|
||||
id: 2,
|
||||
other_handler: 3,
|
||||
cycle: 0,
|
||||
other_handler_queue: queues::MessageQueueSender::new(),
|
||||
other_data: datasets::ReferencedDataset::new(),
|
||||
};
|
||||
let commands = [dh::Command(0), dh::Command(1), dh::Command(2)];
|
||||
let mut debugger = DeviceHandlerDebugger::new(
|
||||
EchoHandler { buffer: [0; 10] },
|
||||
HardwareInterface::new("debug/uart🚀").unwrap(),
|
||||
&commands,
|
||||
&[],
|
||||
Duration::from_millis(500),
|
||||
0.5,
|
||||
);
|
||||
|
||||
let array: &mut [&mut dyn objectmanager::SystemObjectIF] = &mut [&mut h1];
|
||||
debugger.run().unwrap();
|
||||
|
||||
let mut t1 = tasks::PeriodicTask::new(array, 512, 200);
|
||||
let test1 = TEST1.take().unwrap();
|
||||
let test2 = TEST2.take().unwrap();
|
||||
let receiver = RECEIVER.take().unwrap();
|
||||
let sender_handle = receiver.queue.sender();
|
||||
let clone = sender_handle.clone();
|
||||
let sender = SENDER.take_no_init().unwrap();
|
||||
|
||||
let mut t2: tasks::PeriodicTask = tasks::PeriodicTask {
|
||||
task_objects: &mut [&mut h2],
|
||||
stack_size: 512,
|
||||
period: 400,
|
||||
task_handle: 0 as *const core::ffi::c_void,
|
||||
};
|
||||
let mutex_copy = test1.mutex.clone();
|
||||
let a = Duration::from_millis(10);
|
||||
|
||||
let mut task_executor = tasks::TaskExecutor {
|
||||
tasks: &mut [&mut t1, &mut t2],
|
||||
};
|
||||
THREAD_2.take_no_init().unwrap().spawn(move || {
|
||||
test2.run(mutex_copy, a);
|
||||
});
|
||||
|
||||
task_executor.init_and_run(false);
|
||||
THREAD_1.take_no_init().unwrap().spawn(move || {
|
||||
test1.run();
|
||||
sender.run(clone);
|
||||
});
|
||||
|
||||
sifln!("Mission delay");
|
||||
unsafe {
|
||||
osal::task_delay(2000);
|
||||
}
|
||||
sifln!("executor dropped");
|
||||
drop(task_executor);
|
||||
unsafe {
|
||||
osal::task_delay(2000);
|
||||
}
|
||||
sifln!("Mission delay done");
|
||||
THREAD_3.take_no_init().unwrap().spawn(move || {
|
||||
receiver.run();
|
||||
});
|
||||
|
||||
sifln!("=====================Mission delete");
|
||||
osal::thread::current().delete();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use core::panic::PanicInfo;
|
||||
use core::fmt::Write;
|
||||
|
||||
extern "C" {
|
||||
fn done_error(); // exit code != 0
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[panic_handler]
|
||||
fn panic(panic: &PanicInfo<'_>) -> ! {
|
||||
// unsafe { this breaks in ISR
|
||||
// osal::stop_it();
|
||||
// }
|
||||
_ = writeln!(crate::fsrc::sif::Stderr {}, "");
|
||||
_ = writeln!(
|
||||
crate::fsrc::sif::Stderr {},
|
||||
"Thread '{}' {}",
|
||||
crate::fsrc::osal::thread::current().name(),
|
||||
panic
|
||||
);
|
||||
//TODO: stop RTOS, exit if hosted
|
||||
unsafe { done_error() };
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn rust_assert_called(ptr: *const core::ffi::c_char, line: core::ffi::c_ulong) {
|
||||
let file_name = unsafe {
|
||||
//TODO is from_ptr safe enough?
|
||||
let file_name = core::ffi::CStr::from_ptr(ptr);
|
||||
let file_name_utf8 = core::str::from_utf8(file_name.to_bytes());
|
||||
match file_name_utf8 {
|
||||
Ok(string) => string,
|
||||
Err(_) => "Schei� Encoding",
|
||||
}
|
||||
};
|
||||
|
||||
panic!("assertion failed at {file_name}:{line}");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn rust_alloc_failed() {
|
||||
panic!("allocation failed!");
|
||||
}
|
||||
Reference in New Issue
Block a user