├── .dockerignore ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── add-ccm.repl ├── docker-test.sh ├── environment.yml ├── include ├── clock.h ├── gpio.h └── usart.h ├── load-save.sh ├── renode-config.resc ├── run_tests.sh ├── src ├── app.c ├── app_shell_commands.c ├── clock.c ├── gpio.c ├── shell │ ├── include │ │ └── shell │ │ │ └── shell.h │ └── src │ │ └── shell.c ├── syscalls.c └── usart.c ├── start-headless.sh ├── start.sh ├── stm32f429i-discovery.ld ├── tasks.py └── tests ├── common.robot ├── test-basic.robot └── tests.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | libopencm3 2 | renode 3 | memfault-firmware-sdk 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Renode Automated Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build_and_test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | submodules: true 17 | 18 | # Get the arm-non-eabi-gcc toolchain 19 | - name: Install arm-none-eabi-gcc 20 | uses: fiam/arm-none-eabi-gcc@v1 21 | with: 22 | # The arm-none-eabi-gcc release to use. 23 | release: '9-2019-q4' 24 | 25 | - name: Build Firmware 26 | run: make -j4 27 | 28 | - name: Upload ELF 29 | uses: actions/upload-artifact@v2 30 | with: 31 | name: renode-example.elf 32 | path: build/renode-example.elf 33 | 34 | - name: Renode Tests 35 | run: ./docker-test.sh 36 | 37 | - name: Upload Output Dir 38 | uses: actions/upload-artifact@v2 39 | with: 40 | name: Renode Test Results 41 | path: test_results/ 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | *.elf 3 | *.bin 4 | generated.*.ld 5 | test_results/ 6 | build/ 7 | renode/ 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libopencm3"] 2 | path = libopencm3 3 | url = https://github.com/libopencm3/libopencm3.git 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM antmicro/renode:latest 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Memfault 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SECONDARY: 2 | 3 | PROJECT = renode-example 4 | BUILD_DIR = build 5 | Q ?= @ 6 | 7 | CC = arm-none-eabi-gcc 8 | LD = arm-none-eabi-ld 9 | OCPY = arm-none-eabi-objcopy 10 | MKDIR = mkdir 11 | GIT=git 12 | ECHO=@echo 13 | CAT=cat 14 | PYTHON ?= python 15 | 16 | GIT_SHA := \"$(shell $(GIT) rev-parse --short HEAD)\" 17 | 18 | 19 | SRCS_APP = \ 20 | src/app.c \ 21 | src/app_shell_commands.c \ 22 | src/shell/src/shell.c \ 23 | src/clock.c \ 24 | src/gpio.c \ 25 | src/usart.c \ 26 | src/syscalls.c 27 | 28 | INCLUDES = \ 29 | include \ 30 | src/shell/include 31 | 32 | RENODE_REPO = renode 33 | 34 | DEFINES += \ 35 | STM32F4 \ 36 | GIT_SHA=$(GIT_SHA) \ 37 | 38 | CFLAGS += \ 39 | -mcpu=cortex-m4 \ 40 | -mfloat-abi=hard \ 41 | -mfpu=fpv4-sp-d16 \ 42 | -mthumb \ 43 | -Wall \ 44 | -Werror \ 45 | -std=gnu11 \ 46 | -O0 \ 47 | -g \ 48 | -ffunction-sections \ 49 | -fdata-sections 50 | 51 | LDFLAGS += \ 52 | -static \ 53 | -nostartfiles \ 54 | -specs=nano.specs \ 55 | -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group \ 56 | -Wl,-Map=$(BUILD_DIR)/$(PROJECT).map \ 57 | 58 | LDFLAGS_APP = $(LDFLAGS) -T stm32f429i-discovery.ld 59 | 60 | OPENCM3_PATH = ./libopencm3 61 | OPENCM3_INCLUDES = $(OPENCM3_PATH)/include 62 | OPENCM3_LIB = $(OPENCM3_PATH)/lib/libopencm3_stm32f4.a 63 | 64 | INCLUDES += $(OPENCM3_INCLUDES) 65 | CFLAGS += $(foreach i,$(INCLUDES),-I$(i)) 66 | CFLAGS += $(foreach d,$(DEFINES),-D$(d)) 67 | LDSCRIPT = stm32f429i-discovery.ld 68 | 69 | .PHONY: all test_docker test_local renode 70 | all: $(BUILD_DIR)/$(PROJECT).elf 71 | 72 | $(BUILD_DIR)/$(PROJECT).elf: $(SRCS_APP) $(OPENCM3_LIB) 73 | $(ECHO) " LD $@" 74 | $(Q)$(MKDIR) -p $(BUILD_DIR) 75 | $(Q)$(CC) $(CFLAGS) $(LDFLAGS_APP) $^ -o $@ 76 | 77 | $(RENODE_REPO): 78 | $(ECHO) "renode not found, cloning it..." 79 | $(Q)$(GIT) clone https://github.com/renode/renode.git 2>1 80 | 81 | $(OPENCM3_LIB): 82 | $(ECHO) "Building libopencm3" 83 | $(Q)$(MAKE) -s -C $(OPENCM3_PATH) TARGETS=stm32/f4 84 | 85 | test_docker: 86 | ./docker-test.sh 87 | 88 | test_local: $(RENODE_REPO) 89 | ./run_tests.sh 90 | 91 | start_renode: 92 | ./start.sh 93 | 94 | .PHONY: clean 95 | clean: 96 | $(ECHO) " CLEAN rm -rf $(BUILD_DIR)" 97 | $(Q)rm -rf $(BUILD_DIR) 98 | $(Q)make -C $(OPENCM3_PATH) TARGETS=stm32/f4 clean 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Test Automation with Renode 2 | 3 | The example repo that compliments the Interrupt blog post, [Firmware Testing with Renode and Github Actions](https://interrupt.memfault.com/blog/test-automation-renode) 4 | -------------------------------------------------------------------------------- /add-ccm.repl: -------------------------------------------------------------------------------- 1 | ccm: Memory.MappedMemory @ sysbus 0x10000000 2 | size: 0x10000 3 | -------------------------------------------------------------------------------- /docker-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Originally written by TensorFlow at 4 | # https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/testing/test_stm32f4_binary.sh 5 | # 6 | # ------------------------------------------------------- 7 | # 8 | # Runs Robot Framework tests in Docker and saves results locally 9 | 10 | declare -r HOST_ROOT_DIR=`pwd` 11 | declare -r HOST_TEST_RESULTS_PATH=${HOST_ROOT_DIR}/test_results 12 | declare -r HOST_LOG_PATH=${HOST_TEST_RESULTS_PATH} 13 | declare -r HOST_LOG_FILENAME=${HOST_LOG_PATH}/logs.txt 14 | 15 | declare -r DOCKER_TAG=renode_stm32f4 16 | declare -r DOCKER_WORKSPACE=/workspace 17 | declare -r DOCKER_TEST_RESULTS_PATH=/tmp/test_results 18 | 19 | mkdir -p ${HOST_LOG_PATH} 20 | 21 | docker build -t ${DOCKER_TAG} -f ${HOST_ROOT_DIR}/Dockerfile . 22 | 23 | # running in `if` to avoid setting +e 24 | 25 | exit_code=0 26 | if ! docker run \ 27 | --log-driver=none -a stdout -a stderr \ 28 | --volume ${HOST_ROOT_DIR}:${DOCKER_WORKSPACE} \ 29 | --volume ${HOST_TEST_RESULTS_PATH}:${DOCKER_TEST_RESULTS_PATH} \ 30 | --env SCRIPT=${DOCKER_WORKSPACE}/renode-config.resc \ 31 | --env RENODE_CHECKOUT=/home/developer/renode \ 32 | --workdir ${DOCKER_WORKSPACE} \ 33 | ${DOCKER_TAG} \ 34 | /bin/bash -c "./run_tests.sh 2>&1 > ${DOCKER_TEST_RESULTS_PATH}/logs.txt" 35 | then 36 | echo "FAILED" 37 | exit_code=1 38 | fi 39 | 40 | echo -e "\n----- LOGS -----\n" 41 | cat ${HOST_LOG_FILENAME} 42 | 43 | if [ $exit_code -eq 0 ] 44 | then 45 | echo "$1: PASS" 46 | else 47 | echo "$1: FAIL" 48 | fi 49 | 50 | # Raise the exit 51 | exit $exit_code 52 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - tyhoff17 3 | - memfault 4 | - conda-forge 5 | - nodefaults 6 | dependencies: 7 | - gcc-arm-none-eabi=8.2019.q3.update 8 | - python=2.7.15 9 | - pip=20.1.1 10 | - pip: 11 | - robotframework==3.1 12 | - netifaces 13 | - requests 14 | - psutil 15 | - pyyaml 16 | 17 | -------------------------------------------------------------------------------- /include/clock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void clock_setup(void); 4 | -------------------------------------------------------------------------------- /include/gpio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void gpio_setup(void); 4 | -------------------------------------------------------------------------------- /include/usart.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void usart_setup(void); 4 | void usart_teardown(void); 5 | int usart_putc(char c); 6 | char usart_getc(void); 7 | 8 | -------------------------------------------------------------------------------- /load-save.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Loads a Renode Save image and starts a GDB server 4 | # https://renode.readthedocs.io/en/latest/basic/saving.html 5 | 6 | sh /Applications/Renode.app/Contents/MacOS/macos_run.command --disable-xwt \ 7 | -e "Load @$1" \ 8 | -e 'mach set 0' \ 9 | -e 'machine StartGdbServer 3333' 10 | -------------------------------------------------------------------------------- /renode-config.resc: -------------------------------------------------------------------------------- 1 | :name: STM32F4 Discovery Printf 2 | :description: This script runs the usart_printf example on stm32f4 discovery 3 | 4 | $name?="STM32F4_Discovery" 5 | $bin?=@build/renode-example.elf 6 | 7 | # Create Machine & Load config 8 | mach create $name 9 | machine LoadPlatformDescription @platforms/cpus/stm32f429.repl 10 | machine LoadPlatformDescription @add-ccm.repl 11 | 12 | # Create a terminal window showing the output of UART2 13 | showAnalyzer sysbus.uart2 14 | 15 | # Create a Telnet connection to the UART 16 | emulation CreateServerSocketTerminal 33335 "externalUART" 17 | connector Connect sysbus.uart2 externalUART 18 | 19 | # Enable GDB 20 | machine StartGdbServer 3333 21 | 22 | macro reset 23 | """ 24 | sysbus LoadELF $bin 25 | """ 26 | 27 | runMacro $reset 28 | 29 | # Start without user confirmation 30 | start 31 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Runs Robot Framework tests locally 4 | 5 | RENODE_CHECKOUT=${RENODE_CHECKOUT:-~/code/renode} 6 | 7 | ${RENODE_CHECKOUT}/test.sh -t "${PWD}/tests/tests.yaml" --variable PWD_PATH:"${PWD}" -r "${PWD}/test_results" 8 | -------------------------------------------------------------------------------- /src/app.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "clock.h" 9 | #include "gpio.h" 10 | #include "usart.h" 11 | 12 | 13 | int main(void) { 14 | clock_setup(); 15 | gpio_setup(); 16 | usart_setup(); 17 | 18 | printf("App STARTED\n"); 19 | 20 | // Configure shell 21 | sShellImpl shell_impl = { 22 | .send_char = usart_putc, 23 | }; 24 | shell_boot(&shell_impl); 25 | 26 | char c; 27 | while (1) { 28 | c = usart_getc(); 29 | shell_receive_char(c); 30 | } 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/app_shell_commands.c: -------------------------------------------------------------------------------- 1 | #include "shell/shell.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) 9 | 10 | int cli_command_ping(int argc, char *argv[]) { 11 | shell_put_line("PONG"); 12 | return 0; 13 | } 14 | 15 | int cli_command_greet(int argc, char *argv[]) { 16 | char buf[64]; 17 | snprintf(buf, sizeof(buf), "Hello %s!", argv[1]); 18 | shell_put_line(buf); 19 | return 0; 20 | } 21 | 22 | int cli_command_fault(int argc, char *argv[]) { 23 | void (*g_bad_func_call)(void) = (void (*)(void))0x20000002; 24 | g_bad_func_call(); 25 | return 0; 26 | } 27 | 28 | int cli_command_heap_free(int argc, char *argv[]) { 29 | char buf[64]; 30 | snprintf(buf, sizeof(buf), "2000"); 31 | shell_put_line(buf); 32 | return 0; 33 | } 34 | 35 | static const sShellCommand s_shell_commands[] = { 36 | {"help", shell_help_handler, "Lists all commands"}, 37 | {"ping", cli_command_ping, "Prints PONG"}, 38 | {"fault", cli_command_fault, "Crash"}, 39 | {"greet", cli_command_greet, "Greet"}, 40 | {"heap_free", cli_command_heap_free, "Heap Free"}, 41 | }; 42 | 43 | const sShellCommand *const g_shell_commands = s_shell_commands; 44 | const size_t g_num_shell_commands = ARRAY_SIZE(s_shell_commands); 45 | -------------------------------------------------------------------------------- /src/clock.c: -------------------------------------------------------------------------------- 1 | #include "clock.h" 2 | #include 3 | 4 | void clock_setup(void) 5 | { 6 | /* Enable GPIOD clock for LED & USARTs. */ 7 | rcc_periph_clock_enable(RCC_GPIOD); 8 | rcc_periph_clock_enable(RCC_GPIOA); 9 | 10 | /* Enable clocks for USART2. */ 11 | rcc_periph_clock_enable(RCC_USART2); 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/gpio.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gpio.h" 4 | 5 | void gpio_setup(void) 6 | { 7 | /* Setup GPIO pin GPIO12 on GPIO port D for LED. */ 8 | gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO12); 9 | 10 | /* Setup GPIO pins for USART2 transmit. */ 11 | gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2); 12 | 13 | /* Setup USART2 TX pin as alternate function. */ 14 | gpio_set_af(GPIOA, GPIO_AF7, GPIO2); 15 | } 16 | -------------------------------------------------------------------------------- /src/shell/include/shell/shell.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct ShellCommand { 6 | const char *command; 7 | int (*handler)(int argc, char *argv[]); 8 | const char *help; 9 | } sShellCommand; 10 | 11 | extern const sShellCommand *const g_shell_commands; 12 | extern const size_t g_num_shell_commands; 13 | 14 | typedef struct ShellImpl { 15 | //! Function to call whenever a character needs to be sent out. 16 | int (*send_char)(char c); 17 | } sShellImpl; 18 | 19 | //! Initializes the demo shell. To be called early at boot. 20 | void shell_boot(const sShellImpl *impl); 21 | 22 | //! Call this when a character is received. The character is processed synchronously. 23 | void shell_receive_char(char c); 24 | 25 | //! Print help command 26 | int shell_help_handler(int argc, char *argv[]); 27 | 28 | //! Prints a line then a newline 29 | void shell_put_line(const char *str); 30 | -------------------------------------------------------------------------------- /src/shell/src/shell.c: -------------------------------------------------------------------------------- 1 | 2 | #include "shell/shell.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define SHELL_RX_BUFFER_SIZE (256) 9 | #define SHELL_MAX_ARGS (16) 10 | #define SHELL_PROMPT "shell> " 11 | 12 | #define SHELL_FOR_EACH_COMMAND(command) \ 13 | for (const sShellCommand *command = g_shell_commands; \ 14 | command < &g_shell_commands[g_num_shell_commands]; \ 15 | ++command) 16 | 17 | static struct ShellContext { 18 | int (*send_char)(char c); 19 | size_t rx_size; 20 | char rx_buffer[SHELL_RX_BUFFER_SIZE]; 21 | } s_shell; 22 | 23 | static bool prv_booted(void) { 24 | return s_shell.send_char != NULL; 25 | } 26 | 27 | static void prv_send_char(char c) { 28 | if (!prv_booted()) { 29 | return; 30 | } 31 | s_shell.send_char(c); 32 | } 33 | 34 | static void prv_echo(char c) { 35 | if ('\n' == c || '\r' == c) { 36 | prv_send_char('\r'); 37 | prv_send_char('\n'); 38 | } else if ('\b' == c) { 39 | prv_send_char('\b'); 40 | prv_send_char(' '); 41 | prv_send_char('\b'); 42 | } else { 43 | prv_send_char(c); 44 | } 45 | } 46 | 47 | static char prv_last_char(void) { 48 | return s_shell.rx_buffer[s_shell.rx_size - 1]; 49 | } 50 | 51 | static bool prv_is_rx_buffer_full(void) { 52 | return s_shell.rx_size >= SHELL_RX_BUFFER_SIZE; 53 | } 54 | 55 | static void prv_reset_rx_buffer(void) { 56 | memset(s_shell.rx_buffer, 0, sizeof(s_shell.rx_buffer)); 57 | s_shell.rx_size = 0; 58 | } 59 | 60 | static void prv_echo_str(const char *str) { 61 | for (const char *c = str; *c != '\0'; ++c) { 62 | prv_echo(*c); 63 | } 64 | } 65 | 66 | static void prv_send_prompt(void) { 67 | prv_echo_str(SHELL_PROMPT); 68 | } 69 | 70 | static const sShellCommand *prv_find_command(const char *name) { 71 | SHELL_FOR_EACH_COMMAND(command) { 72 | if (strcmp(command->command, name) == 0) { 73 | return command; 74 | } 75 | } 76 | return NULL; 77 | } 78 | 79 | static void prv_process(void) { 80 | if (prv_last_char() != '\r' && !prv_is_rx_buffer_full()) { 81 | return; 82 | } 83 | 84 | char *argv[SHELL_MAX_ARGS] = {0}; 85 | int argc = 0; 86 | 87 | char *next_arg = NULL; 88 | for (size_t i = 0; i < s_shell.rx_size && argc < SHELL_MAX_ARGS; ++i) { 89 | char *const c = &s_shell.rx_buffer[i]; 90 | if (*c == ' ' || *c == '\n' || i == s_shell.rx_size - 1) { 91 | *c = '\0'; 92 | if (next_arg) { 93 | argv[argc++] = next_arg; 94 | next_arg = NULL; 95 | } 96 | } else if (!next_arg) { 97 | next_arg = c; 98 | } 99 | } 100 | 101 | if (s_shell.rx_size == SHELL_RX_BUFFER_SIZE) { 102 | prv_echo('\n'); 103 | } 104 | 105 | if (argc >= 1) { 106 | const sShellCommand *command = prv_find_command(argv[0]); 107 | if (!command) { 108 | prv_echo_str("Unknown command: "); 109 | prv_echo_str(argv[0]); 110 | prv_echo('\n'); 111 | prv_echo_str("Type 'help' to list all commands\n"); 112 | } else { 113 | command->handler(argc, argv); 114 | } 115 | } 116 | prv_reset_rx_buffer(); 117 | prv_send_prompt(); 118 | } 119 | 120 | void shell_boot(const sShellImpl *impl) { 121 | s_shell.send_char = impl->send_char; 122 | prv_reset_rx_buffer(); 123 | prv_echo_str("\n" SHELL_PROMPT); 124 | } 125 | 126 | void shell_receive_char(char c) { 127 | if (prv_is_rx_buffer_full() || !prv_booted()) { 128 | return; 129 | } 130 | 131 | if (c == '\n') { 132 | c = '\r'; 133 | if (prv_last_char() == c) { 134 | return; 135 | } 136 | } 137 | 138 | prv_echo(c); 139 | 140 | if (c == '\b') { 141 | s_shell.rx_buffer[--s_shell.rx_size] = '\0'; 142 | return; 143 | } 144 | 145 | s_shell.rx_buffer[s_shell.rx_size++] = c; 146 | 147 | prv_process(); 148 | } 149 | 150 | void shell_put_line(const char *str) { 151 | prv_echo_str(str); 152 | prv_echo('\n'); 153 | } 154 | 155 | 156 | int shell_help_handler(int argc, char *argv[]) { 157 | SHELL_FOR_EACH_COMMAND(command) { 158 | prv_echo_str(command->command); 159 | prv_echo_str(": "); 160 | prv_echo_str(command->help); 161 | prv_echo('\n'); 162 | } 163 | return 0; 164 | } 165 | -------------------------------------------------------------------------------- /src/syscalls.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // LIBC SYSCALLS 9 | ///////////////////// 10 | 11 | extern int end; 12 | 13 | caddr_t _sbrk(int incr) { 14 | static unsigned char *heap = NULL; 15 | unsigned char *prev_heap; 16 | 17 | if (heap == NULL) { 18 | heap = (unsigned char *)&end; 19 | } 20 | prev_heap = heap; 21 | 22 | heap += incr; 23 | 24 | return (caddr_t) prev_heap; 25 | } 26 | 27 | int _close(int file) { 28 | return -1; 29 | } 30 | 31 | int _fstat(int file, struct stat *st) { 32 | st->st_mode = S_IFCHR; 33 | 34 | return 0; 35 | } 36 | 37 | int _isatty(int file) { 38 | return 1; 39 | } 40 | 41 | int _lseek(int file, int ptr, int dir) { 42 | return 0; 43 | } 44 | 45 | void _exit(int status) { 46 | __asm("BKPT #0"); 47 | while (1) {} 48 | } 49 | 50 | void _kill(int pid, int sig) { 51 | return; 52 | } 53 | 54 | int _getpid(void) { 55 | return -1; 56 | } 57 | 58 | int _write(int file, char *ptr, int len) 59 | { 60 | int i; 61 | 62 | if (file == STDOUT_FILENO || file == STDERR_FILENO) { 63 | for (i = 0; i < len; i++) { 64 | if (ptr[i] == '\n') { 65 | usart_send_blocking(USART2, '\r'); 66 | } 67 | usart_send_blocking(USART2, ptr[i]); 68 | } 69 | return i; 70 | } 71 | errno = EIO; 72 | return -1; 73 | } 74 | 75 | int _read (int file, char * ptr, int len) { 76 | return -1; 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/usart.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "usart.h" 4 | 5 | void usart_setup(void) 6 | { 7 | /* Setup USART2 parameters. */ 8 | usart_set_baudrate(USART2, 115200); 9 | usart_set_databits(USART2, 8); 10 | usart_set_stopbits(USART2, USART_STOPBITS_1); 11 | usart_set_mode(USART2, USART_MODE_TX); 12 | usart_set_parity(USART2, USART_PARITY_NONE); 13 | usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE); 14 | 15 | /* Finally enable the USART. */ 16 | usart_enable(USART2); 17 | } 18 | 19 | void usart_teardown(void) 20 | { 21 | usart_disable(USART2); 22 | } 23 | 24 | int usart_putc(char c) { 25 | usart_send_blocking(USART2, c); 26 | return 0; 27 | } 28 | 29 | char usart_getc(void) { 30 | // Blocks until a character is captured from the UART 31 | uint16_t cr = usart_recv_blocking(USART2); 32 | return (char)cr; 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /start-headless.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Starts the built Renode image without a GUI 4 | # To attach to it, use Telnet 5 | 6 | sh /Applications/Renode.app/Contents/MacOS/macos_run.command --disable-xwt renode-config.resc --port 33334 7 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sh /Applications/Renode.app/Contents/MacOS/macos_run.command renode-config.resc 4 | -------------------------------------------------------------------------------- /stm32f429i-discovery.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K 4 | coredump (rwx) : ORIGIN = 0x20010000, LENGTH = 80K 5 | reboot (rwx) : ORIGIN = 0x20024000, LENGTH = 1K 6 | rom (rx) : ORIGIN = 0x08000000, LENGTH = 1024K 7 | } 8 | 9 | /* Enforce emmition of the vector table. */ 10 | EXTERN (vector_table) 11 | 12 | /* Define the entry point of the output file. */ 13 | ENTRY(reset_handler) 14 | 15 | /* Define sections. */ 16 | SECTIONS 17 | { 18 | /* Vector table must be 128-bit aligned */ 19 | . = ALIGN(128); 20 | 21 | .text : { 22 | *(.vectors) /* Vector table */ 23 | *(.text*) /* Program code */ 24 | . = ALIGN(4); 25 | *(.rodata*) /* Read-only data */ 26 | . = ALIGN(4); 27 | } >rom 28 | 29 | /* C++ Static constructors/destructors, also used for __attribute__ 30 | * ((constructor)) and the likes */ 31 | .preinit_array : { 32 | . = ALIGN(4); 33 | __preinit_array_start = .; 34 | KEEP (*(.preinit_array)) 35 | __preinit_array_end = .; 36 | } >rom 37 | .init_array : { 38 | . = ALIGN(4); 39 | __init_array_start = .; 40 | KEEP (*(SORT(.init_array.*))) 41 | KEEP (*(.init_array)) 42 | __init_array_end = .; 43 | } >rom 44 | .fini_array : { 45 | . = ALIGN(4); 46 | __fini_array_start = .; 47 | KEEP (*(.fini_array)) 48 | KEEP (*(SORT(.fini_array.*))) 49 | __fini_array_end = .; 50 | } >rom 51 | 52 | /* 53 | * Another section used by C++ stuff, appears when using newlib with 54 | * 64bit (long long) printf support 55 | */ 56 | .ARM.extab : { 57 | *(.ARM.extab*) 58 | } >rom 59 | .ARM.exidx : { 60 | __exidx_start = .; 61 | *(.ARM.exidx*) 62 | __exidx_end = .; 63 | } >rom 64 | 65 | . = ALIGN(4); 66 | _etext = .; 67 | 68 | .data : { 69 | _data = .; 70 | *(.data*) /* Read-write initialized data */ 71 | . = ALIGN(4); 72 | _edata = .; 73 | } >ram AT >rom 74 | _data_loadaddr = LOADADDR(.data); 75 | 76 | .bss : { 77 | *(.bss*) /* Read-write zero initialized data */ 78 | *(COMMON) 79 | . = ALIGN(4); 80 | _ebss = .; 81 | } >ram 82 | 83 | .noinit (NOLOAD): { KEEP(*(*.mflt_coredump)) } > coredump 84 | 85 | /* 86 | * The .eh_frame section appears to be used for C++ exception handling. 87 | * You may need to fix this if you're using C++. 88 | */ 89 | /DISCARD/ : { *(.eh_frame) } 90 | 91 | . = ALIGN(4); 92 | end = .; 93 | } 94 | 95 | PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram)); 96 | 97 | 98 | __rom_start__ = ORIGIN(rom); 99 | __rom_size__ = LENGTH(rom); 100 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from invoke import Context, Collection, task 4 | from telnetlib import Telnet 5 | from datetime import datetime, timedelta 6 | 7 | @task() 8 | def renode(ctx): 9 | """Spawn Renode and attach to its monitor""" 10 | ctx.run("./start-headless.sh", asynchronous=True) 11 | 12 | print("Letting Renode boot...") 13 | time.sleep(3) 14 | 15 | retry_until = datetime.now() + timedelta(seconds=3) 16 | while datetime.now() < retry_until: 17 | try: 18 | ctx.run('telnet 127.0.0.1 33334', pty=True) 19 | except Exception as e: 20 | time.sleep(0.5) 21 | 22 | @task() 23 | def console(ctx): 24 | """Connect to Renode's UART""" 25 | ctx.run('telnet 127.0.0.1 33335', pty=True) 26 | 27 | @task() 28 | def gdb(ctx): 29 | """Connect to Renode's GDB connection""" 30 | ctx.run("arm-none-eabi-gdb-py " 31 | "--eval-command=\"target remote :3333\" " 32 | "--se build/renode-example.elf", 33 | pty=True) 34 | 35 | @task() 36 | def test(ctx): 37 | """Run tests locally""" 38 | ctx.run("./run_tests.sh", pty=True) 39 | -------------------------------------------------------------------------------- /tests/common.robot: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | ${CREATE_SNAPSHOT_ON_FAIL} True 3 | 4 | *** Keywords *** 5 | 6 | # These were committed after the 1.9.0 release, so I've copied them here 7 | # https://github.com/renode/renode/commit/196d4139616ae4ad095d79c9926940832c1068c1 8 | # If you ever get the error: 9 | # 10 | # Multiple keywords with name 'Test Teardown' found. Give the full name of the keyword you want to use: 11 | # common.Test Teardown 12 | # renode-keywords.Test Teardown 13 | # 14 | # You can remove these! 15 | 16 | Create Snapshot Of Failed Test 17 | ${test_name}= Set Variable ${SUITE NAME}-${TEST NAME}.fail.save 18 | ${test_name}= Replace String ${test_name} ${SPACE} _ 19 | 20 | ${snapshots_dir}= Set Variable ${PWD_PATH}/test_results/snapshots 21 | Create Directory ${snapshots_dir} 22 | 23 | ${snapshot_path}= Set Variable "${snapshots_dir}/${test_name}" 24 | Execute Command Save ${snapshot_path} 25 | Log To Console Failed emulation's state saved to ${snapshot_path} 26 | 27 | Test Teardown 28 | Run Keyword If ${CREATE_SNAPSHOT_ON_FAIL} 29 | ... Run Keyword If Test Failed 30 | ... common.Create Snapshot Of Failed Test 31 | Reset Emulation 32 | -------------------------------------------------------------------------------- /tests/test-basic.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ${RENODEKEYWORDS} 3 | Resource common.robot 4 | Suite Setup Setup 5 | Suite Teardown Teardown 6 | Test Setup Reset Emulation 7 | Test Teardown common.Test Teardown 8 | 9 | *** Variables *** 10 | ${SHELL_PROMPT} shell> 11 | 12 | *** Keywords *** 13 | Start Test 14 | Execute Command mach create 15 | Execute Command machine LoadPlatformDescription @platforms/boards/stm32f4_discovery-kit.repl 16 | Execute Command machine LoadPlatformDescription @${PWD_PATH}/add-ccm.repl 17 | Execute Command sysbus LoadELF @${PWD_PATH}/build/renode-example.elf 18 | Create Terminal Tester sysbus.uart2 19 | Start Emulation 20 | 21 | *** Test Cases *** 22 | Ping 23 | [Documentation] Ping Pong 24 | [Tags] non_critical factory uart 25 | 26 | Start Test 27 | 28 | Wait For Prompt On Uart ${SHELL_PROMPT} 29 | Write Line To Uart ping 30 | Wait For Line On Uart PONG 31 | 32 | 33 | Help Menu 34 | [Documentation] Prints help menu of the command prompt 35 | [Tags] critical uart 36 | 37 | Start Test 38 | 39 | Wait For Prompt On Uart ${SHELL_PROMPT} 40 | Write Line To Uart help 41 | Wait For Line On Uart help: Lists all commands 42 | Wait For Line On Uart ping: Prints PONG 43 | 44 | 45 | Greet 46 | [Documentation] Greets given name 47 | [Tags] non_critical uart input 48 | 49 | Start Test 50 | 51 | Wait For Prompt On Uart ${SHELL_PROMPT} 52 | Write Line To Uart greet Tyler 53 | 54 | ${p}= Wait For Line On Uart Hello (\\w+)! treatAsRegex=true 55 | Should Be True 'Tyler' == """${p.groups[0]}""" 56 | 57 | 58 | Trigger Fault 59 | [Documentation] Should fail, but fine since non_critical 60 | [Tags] non_critical uart input 61 | 62 | Start Test 63 | 64 | Wait For Prompt On Uart ${SHELL_PROMPT} 65 | Write Line To Uart fault 66 | 67 | # By now we've crashed 68 | Wait For Line On Uart Nope timeout=2 69 | -------------------------------------------------------------------------------- /tests/tests.yaml: -------------------------------------------------------------------------------- 1 | - robot: 2 | - tests/test-basic.robot 3 | 4 | --------------------------------------------------------------------------------