diff --git a/boards/boards.rst b/boards/boards.rst index b7e8cb0020..a888a594ac 100644 --- a/boards/boards.rst +++ b/boards/boards.rst @@ -18,3 +18,4 @@ under :file:`doc/templates/board.tmpl` arc/index.rst nios2/index.rst xtensa/index.rst + posix/index.rst diff --git a/boards/posix/index.rst b/boards/posix/index.rst new file mode 100644 index 0000000000..a8881cbaf7 --- /dev/null +++ b/boards/posix/index.rst @@ -0,0 +1,10 @@ +.. _boards_posix: + +Native POSIX Boards +################### + +.. toctree:: + :maxdepth: 1 + :glob: + + **/* diff --git a/boards/posix/native_posix/CMakeLists.txt b/boards/posix/native_posix/CMakeLists.txt new file mode 100644 index 0000000000..2f6ec4ab5f --- /dev/null +++ b/boards/posix/native_posix/CMakeLists.txt @@ -0,0 +1,10 @@ +zephyr_library() +zephyr_library_compile_definitions(_POSIX_CHEATS_H) +zephyr_library_sources( + hw_models_top.c + timer_model.c + irq_handler.c + irq_ctrl.c + main.c + tracing.c + ) diff --git a/boards/posix/native_posix/Kconfig b/boards/posix/native_posix/Kconfig new file mode 100644 index 0000000000..14ea1829ac --- /dev/null +++ b/boards/posix/native_posix/Kconfig @@ -0,0 +1,18 @@ + +if BOARD_NATIVE_POSIX + +comment "Simple process (POSIX) Options" + +config NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME + bool "Slow down exectution to real time" + depends on BOARD_NATIVE_POSIX + default y + help + When selected the execution of the process will be slowed down to real time. + (if there is a lot of load it may be slower than real time) + If deselected, the process will run as fast as possible. + Note that this only decouples simulated time from real/wall time. In either + case the zephyr kernel and application cannot tell the diffence unless they + interact with some other driver/device which runs at real time. + +endif diff --git a/boards/posix/native_posix/Kconfig.board b/boards/posix/native_posix/Kconfig.board new file mode 100644 index 0000000000..fc0f198800 --- /dev/null +++ b/boards/posix/native_posix/Kconfig.board @@ -0,0 +1,10 @@ + +config BOARD_NATIVE_POSIX + bool "Posix simple process" + depends on SOC_POSIX + select NATIVE_POSIX_TIMER + help + Will produce a console Linux process which can be executed natively. + It provides some minimal needed models: + An interrupt controller, timer (system tick), and redirects kernel prints to + stdout. diff --git a/boards/posix/native_posix/board.h b/boards/posix/native_posix/board.h new file mode 100644 index 0000000000..39d984a6c1 --- /dev/null +++ b/boards/posix/native_posix/board.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2015 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INC_BOARD_H +#define __INC_BOARD_H + +#include + +#endif /* __INC_BOARD_H */ diff --git a/boards/posix/native_posix/board_irq.h b/boards/posix/native_posix/board_irq.h new file mode 100644 index 0000000000..12ea70240c --- /dev/null +++ b/boards/posix/native_posix/board_irq.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013-2014 Wind River Systems, Inc. + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BOARD_IRQ_H +#define _BOARD_IRQ_H + +#include "sw_isr_table.h" +#include "zephyr/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void _isr_declare(unsigned int irq_p, int flags, void isr_p(void *), + void *isr_param_p); +void _irq_priority_set(unsigned int irq, unsigned int prio, u32_t flags); + +/** + * Configure a static interrupt. + * + * @param irq_p IRQ line number + * @param priority_p Interrupt priority + * @param isr_p Interrupt service routine + * @param isr_param_p ISR parameter + * @param flags_p IRQ options + * + * @return The vector assigned to this interrupt + */ +#define _ARCH_IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p) \ +({ \ + _isr_declare(irq_p, 0, isr_p, isr_param_p); \ + _irq_priority_set(irq_p, priority_p, flags_p); \ + irq_p; \ +}) + + +/** + * Configure a 'direct' static interrupt. + * + * See include/irq.h for details. + */ +#define _ARCH_IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p) \ +({ \ + _isr_declare(irq_p, ISR_FLAG_DIRECT, (void (*)(void *))isr_p, NULL); \ + _irq_priority_set(irq_p, priority_p, flags_p); \ + irq_p; \ +}) + + +/** + * The return of "name(void)" is the indicaton of the interrupt + * (maybe) having caused a kernel decision to context switch + * + * Note that this convention is changed relative to the ARM and x86 archs + */ +#define _ARCH_ISR_DIRECT_DECLARE(name) \ + static inline int name##_body(void); \ + int name(void) \ + { \ + int check_reschedule; \ + check_reschedule = name##_body(); \ + return check_reschedule; \ + } \ + static inline int name##_body(void) + +#define _ARCH_ISR_DIRECT_PM() + +#ifdef __cplusplus +} +#endif + +#endif /* _BOARD_IRQ_H */ diff --git a/boards/posix/native_posix/board_soc.h b/boards/posix/native_posix/board_soc.h new file mode 100644 index 0000000000..d11f119fe1 --- /dev/null +++ b/boards/posix/native_posix/board_soc.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ +/** + * @file Extra definitions provided by the board to soc.h + * + * Background: + * The POSIC ARCH/SOC/board layering is different than in normal archs + * The "SOC" does not provide almost any of the typical SOC functionality + * but that is left for the "board" to define it + * Device code may rely on the soc.h defining some things (like the interrupts + * numbers) + * Therefore this file is included from the inf_clock soc.h to allow a board + * to define that kind of SOC related snippets + */ + +#ifndef _POSIX_SP_BOARD_SOC_H +#define _POSIX_SP_BOARD_SOC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define TIMER_TICK_IRQ 0 + +/* + * This interrupt will awake the CPU if IRQs are not locked, + * This interrupt does not have an associated status bit or handler + */ +#define PHONY_WEAK_IRQ 0xFFFE +/* + * This interrupt will awake the CPU even if IRQs are locked, + * This interrupt does not have an associated status bit or handler + * (the lock is only ignored when the interrupt is raised from the HW models, + * SW threads should not try to use this) + */ +#define PHONY_HARD_IRQ 0xFFFF + + +#ifdef __cplusplus +} +#endif + +#endif /* _POSIX_SP_BOARD_SOC_H */ diff --git a/boards/posix/native_posix/doc/board.rst b/boards/posix/native_posix/doc/board.rst new file mode 100644 index 0000000000..7c5893990a --- /dev/null +++ b/boards/posix/native_posix/doc/board.rst @@ -0,0 +1,179 @@ + +.. _native_posix: + +Native POSIX execution (native_posix) +####################################### + +Overview +******** + +Using this board, a Zephyr application can be compiled together with +the Zephyr kernel, creating a normal console executable that runs as +a native application on the host OS, without emulation. Instead, +you use native host tools for compiling, debugging, and analyzing your +Zephyr application, eliminating the need for architecture-specific +target hardware in the early phases of development. + +Host system dependencies +======================== + +This port is designed to run in POSIX compatible operating systems. +It has only been tested on Linux, but should also be compatible with macOS. + +.. note:: + + You must have the 32-bit C library installed in your system + (in Ubuntu 16.04 install the gcc-multilib package) + + +Architecture +************ + +This board is based on the POSIX architecture port of Zephyr. +In this architecture each Zephyr thread is mapped to one POSIX pthread, +but only one of these pthreads executes at a time. +This architecture provides the same interface to the Kernel as other +architectures and is therefore transparent for the application. + +This board does not try to emulate any particular embedded CPU or SOC. +The code is compiled natively for the host x86 system, as a 32-bit +binary assuming pointer and integer types are 32-bits wide. + +To ensure determinism when the Zephyr code is running, +and to ease application debugging, +the issue of code execution speed is ignored. +The board uses a different time than real time: simulated time. +This simulated time is, in principle, not linked to the host time. + +The Zephyr application sees the code executing as if the CPU were running at +an infinitely high clock, and fully decoupled from the underlying host CPU +speed. +No simulated time passes while the application or kernel code execute. + +The CPU boot is emulated by creating the Zephyr initialization thread and +letting it run. This in turn may spawn more Zephyr threads. +Eventually the SW will run to completion, that is, it will set the CPU +back to sleep. + +At this point, control is transferred back to the HW models and the simulation +time can be advanced. + +When the HW models raise an interrupt, the CPU wakes back up: the interrupt +is handled, the SW runs until completion again, and control is +transferred back to the HW models, all in zero simulated time. + +If the SW unmasks a pending interrupt while running, or triggers a SW +interrupt, the interrupt controller may raise the interrupt immediately +depending on interrupt priorities, masking, and locking state. + +Normally the resulting executable runs fully decoupled from the real host time. +That is, simulated time will advance as fast as it can. This is desirable when +running in a debuger or testing in batch, but not if one wants to interact +with external interfaces which are based on the real host time. + +Peripherals +*********** + +The following peripherals are currently provided with this board: + +**Console/printk driver**: + +A driver is provided that redirects any printk write to the native +host application's stdout. + +**Simple timer**: + +A simple timer provides the kernel with a 10ms tick. +This peripheral driver also provides the needed functionality for this +architecture-specific k_busy_wait(). + +This timer, may also be configured with NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME +to slow down the execution to real host time. +This will provide the illusion that the simulated time is running at the same +speed as the real host time. +In reality, the timer will monitor how much real host time +has actually passed since boot, and when needed, the timer will pause +the execution before raising each timer interrupt. +Normally the Zephyr application and HW models run in very little time +on the host CPU, so this is a good enough approach. + + +**Interrupt controller** + +A simple yet generic interrupt controller is provided. It can nest interrupts +and provides interrupt priorities. Interrupts can be individually masked or +unmasked. SW interrupts are also supported. + + +Important limitations +===================== + +The assumption that simulated time can only pass while the CPU is sleeping +means that there can not be busy wait loops in the application code that +wait for something to happen without letting the CPU sleep. +If busy wait loops do exist, they will behave as infinite loops and +will stall the execution. + +As simulated time does not pass while the CPU is running, it also means no HW +interrupts will interrupt the threads' execution unless the SW enables or +unmasks them. +This is intentional, as it provides a deterministic environment to develop and +debug. +But note that this may hide issues in the SW that may only be triggered in the +real platform. + +This native port of Zephyr is not meant to, and could not possibly +help debug races between HW and SW, or similar timing related issues. + + +How to use it +************* + +Compiling +========= + +Specify the native_posix board target to build a native POSIX application: + +.. zephyr-app-commands:: + :zephyr-app: samples/hello_world + :board: native_posix + :goals: build + :compact: + + +Running +======= + +The result of the compilation is an executable (zephyr.exe) placed in the +zephyr/ subdirectory of the build folder. +Run the zephyr.exe executable as you would any other Linux console application. + +.. code-block:: console + + $ zephyr/zephyr.exe + # Press Ctrl+C to exit + +Note that the Zephyr kernel does not actually exit once the application is +finished. It simply goes into the idle loop forever. +Therefore you must stop the application manually (Ctrl+C in Linux). + +Application tests using the ``ztest`` framework will exit after all +tests have completed. + +If you want your application to gracefully finish when it reaches some point, +you may add a conditionally compiled (CONFIG_BOARD_NATIVE_POSIX) call to +``main_clean_up(exit_code)`` at that point. + + +Debugging +========= + +Since the Zephyr executable is a native application, it can be debuged and +instrumented as any other native program. The program is compiled with debug +information, so it can be run directly in, for example, ``gdb`` or instrumented +with ``valgrind``. + +Because the execution of your Zephyr application is fully deterministic +(there are no asynchronous or random components), you can execute the +code multiple times and get the exact same result and instrumenting the +code does not affect the execution of the code. \ No newline at end of file diff --git a/boards/posix/native_posix/hw_models_top.c b/boards/posix/native_posix/hw_models_top.c new file mode 100644 index 0000000000..fe8f571a75 --- /dev/null +++ b/boards/posix/native_posix/hw_models_top.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Barebones HW model sufficient to run some of the sample apps + */ + +#include +#include "hw_models_top.h" +#include "timer_model.h" +#include "irq_ctrl.h" +#include "posix_board_if.h" +#include "posix_soc_if.h" + + +static u64_t device_time; /* The actual time as known by the device */ +static u64_t end_of_time = NEVER; /* When will this device stop */ + +/* List of HW model timers: */ +extern u64_t hw_timer_timer; /* When should this timer_model be called */ +extern u64_t irq_ctrl_timer; + +static enum { HWTIMER = 0, IRQCNT, NUMBER_OF_TIMERS, NONE } + next_timer_index = NONE; + +static u64_t *Timer_list[NUMBER_OF_TIMERS] = { + &hw_timer_timer, + &irq_ctrl_timer +}; + +static u64_t next_timer_time; + + +static void hwm_sleep_until_next_timer(void) +{ + if (next_timer_time >= device_time) { + device_time = next_timer_time; + } else { + posix_print_warning("next_timer_time corrupted (%"PRIu64"<= %" + PRIu64", timer idx=%i)\n", + next_timer_time, + device_time, + next_timer_index); + } + + if (device_time >= end_of_time) { + posix_print_trace("\n\n\n\n\n\nAutostopped after %.3Lfs\n", + ((long double)end_of_time)/1.0e6); + + main_clean_up(0); + } +} + + +/** + * Find in between all timers which is the next one + * and update next_timer_* accordingly + */ +void hwm_find_next_timer(void) +{ + next_timer_index = 0; + next_timer_time = *Timer_list[0]; + + for (unsigned int i = 1; i < NUMBER_OF_TIMERS ; i++) { + if (next_timer_time > *Timer_list[i]) { + next_timer_index = i; + next_timer_time = *Timer_list[i]; + } + } +} + +/** + * Entry point for the HW models + * The HW models execute in an infinite loop until terminated + */ +void hwm_main_loop(void) +{ + while (1) { + hwm_sleep_until_next_timer(); + + switch (next_timer_index) { + case HWTIMER: + hwtimer_timer_reached(); + break; + case IRQCNT: + hw_irq_ctrl_timer_triggered(); + break; + default: + posix_print_error_and_exit( + "next_timer_index corrupted\n"); + break; + } + + hwm_find_next_timer(); + } +} + +/** + * Set the simulated time when the process will stop + */ +void hwm_set_end_of_time(u64_t new_end_of_time) +{ + end_of_time = new_end_of_time; +} + +/** + * Return the current time as known by the device + */ +u64_t hwm_get_time(void) +{ + return device_time; +} + + +/** + * Function to initialize the HW models + */ +void hwm_init(void) +{ + hwtimer_init(); + hw_irq_ctrl_init(); + + hwm_find_next_timer(); +} + +/** + * Function to free any resources allocated by the HW models + * Note that this function needs to be designed so it is possible + * to call it more than once during cleanup + */ +void hwm_cleanup(void) +{ + hwtimer_cleanup(); + hw_irq_ctrl_cleanup(); +} + + diff --git a/boards/posix/native_posix/hw_models_top.h b/boards/posix/native_posix/hw_models_top.h new file mode 100644 index 0000000000..2430811015 --- /dev/null +++ b/boards/posix/native_posix/hw_models_top.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NATIVE_POSIX_HW_MODELS_H +#define _NATIVE_POSIX_HW_MODELS_H + +#include "zephyr/types.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NEVER UINT64_MAX + +void hwm_main_loop(void); +void hwm_init(void); +void hwm_cleanup(void); +void hwm_set_end_of_time(u64_t new_end_of_time); +u64_t hwm_get_time(void); +void hwm_find_next_timer(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _NATIVE_POSIX_HW_MODELS_H */ diff --git a/boards/posix/native_posix/irq_ctrl.c b/boards/posix/native_posix/irq_ctrl.c new file mode 100644 index 0000000000..1da5b7ae7a --- /dev/null +++ b/boards/posix/native_posix/irq_ctrl.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + * + * HW IRQ controller model + */ + +#include +#include "hw_models_top.h" +#include "irq_ctrl.h" +#include "irq_handler.h" +#include "arch/posix/arch.h" /* for find_lsb_set() */ +#include "board_soc.h" +#include "posix_soc.h" +#include "zephyr/types.h" + +u64_t irq_ctrl_timer = NEVER; + + +static u64_t irq_status; /* pending interrupts */ +static u64_t irq_premask; /* interrupts before the mask */ + +/* + * Mask of which interrupts will actually cause the cpu to vector into its + * irq handler + * If an interrupt is masked in this way, it will be pending in the premask in + * case it is enabled later before clearing it. + * If the irq_mask enables and interrupt pending in irq_premask, it will cause + * the controller to raise the interrupt immediately + */ +static u64_t irq_mask; + +/* + * Interrupts lock/disable. When set, interrupts are registered + * (in the irq_status) but do not awake the cpu. if when unlocked, + * irq_status != 0 an interrupt will be raised immediately + */ +static bool irqs_locked; +static bool lock_ignore; /* For the hard fake IRQ, temporarily ignore lock */ + +static u8_t irq_prio[N_IRQS]; /* Priority of each interrupt */ +/* note that prio = 0 == highest, prio=255 == lowest */ + +static int currently_running_prio = 256; /* 255 is the lowest prio interrupt */ + +void hw_irq_ctrl_init(void) +{ + irq_mask = 0; /* Let's assume all interrupts are disable at boot */ + irq_premask = 0; + irqs_locked = false; + lock_ignore = false; + + for (int i = 0 ; i < N_IRQS; i++) { + irq_prio[i] = 255; + } +} + +void hw_irq_ctrl_cleanup(void) +{ + /* Nothing to be done */ +} + +void hw_irq_ctrl_set_cur_prio(int new) +{ + currently_running_prio = new; +} + +int hw_irq_ctrl_get_cur_prio(void) +{ + return currently_running_prio; +} + +void hw_irq_ctrl_prio_set(unsigned int irq, unsigned int prio) +{ + irq_prio[irq] = prio; +} + +u8_t hw_irq_ctrl_get_prio(unsigned int irq) +{ + return irq_prio[irq]; +} + +/** + * Get the currently pending highest priority interrupt which has a priority + * higher than a possibly currently running interrupt + * + * If none, return -1 + */ +int hw_irq_ctrl_get_highest_prio_irq(void) +{ + if (irqs_locked) { + return -1; + } + + u64_t irq_status = hw_irq_ctrl_get_irq_status(); + int winner = -1; + int winner_prio = 256; + + while (irq_status != 0) { + int irq_nbr = find_lsb_set(irq_status) - 1; + + irq_status &= ~((u64_t) 1 << irq_nbr); + if ((winner_prio > (int)irq_prio[irq_nbr]) + && (currently_running_prio > (int)irq_prio[irq_nbr])) { + winner = irq_nbr; + winner_prio = irq_prio[irq_nbr]; + } + } + return winner; +} + + +u32_t hw_irq_ctrl_get_current_lock(void) +{ + return irqs_locked; +} + +u32_t hw_irq_ctrl_change_lock(u32_t new_lock) +{ + u32_t previous_lock = irqs_locked; + + irqs_locked = new_lock; + + if ((previous_lock == true) && (new_lock == false)) { + if (irq_status != 0) { + posix_irq_handler_im_from_sw(); + } + } + return previous_lock; +} + +u64_t hw_irq_ctrl_get_irq_status(void) +{ + return irq_status; +} + +void hw_irq_ctrl_clear_all_enabled_irqs(void) +{ + irq_status = 0; + irq_premask &= ~irq_mask; +} + +void hw_irq_ctrl_clear_all_irqs(void) +{ + irq_status = 0; + irq_premask = 0; +} + +void hw_irq_ctrl_disable_irq(unsigned int irq) +{ + irq_mask &= ~((u64_t)1< we assume the CPU is always idling in a WFE() like + * instruction and the CPU is allowed to awake just with the irq + * being marked as pending + */ + irq_ctrl_timer = hwm_get_time(); + hwm_find_next_timer(); + } +} + + + +static void irq_raising_from_hw_now(void) +{ + /* + * We always awake the CPU even if the IRQ was masked, + * but not if irqs are locked unless this is due to a + * PHONY_HARD_IRQ + */ + if ((irqs_locked == false) || (lock_ignore)) { + lock_ignore = false; + posix_interrupt_raised(); + } +} + +/** + * Set/Raise an interrupt inmediately. + * Like hw_irq_ctrl_set_irq() but awake immediately the CPU instead of in + * 1 delta cycle + * + * Call only from HW threads + */ +void hw_irq_ctrl_raise_im(unsigned int irq) +{ + hw_irq_ctrl_irq_raise_prefix(irq); + irq_raising_from_hw_now(); +} + +/** + * Like hw_irq_ctrl_raise_im() but for SW threads + * + * Call only from SW threads + */ +void hw_irq_ctrl_raise_im_from_sw(unsigned int irq) +{ + hw_irq_ctrl_irq_raise_prefix(irq); + + if (irqs_locked == false) { + posix_irq_handler_im_from_sw(); + } +} + +void hw_irq_ctrl_timer_triggered(void) +{ + irq_ctrl_timer = NEVER; + irq_raising_from_hw_now(); +} + + diff --git a/boards/posix/native_posix/irq_ctrl.h b/boards/posix/native_posix/irq_ctrl.h new file mode 100644 index 0000000000..cd5dcbea06 --- /dev/null +++ b/boards/posix/native_posix/irq_ctrl.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NATIVE_POSIX_IRQ_CTRL_H +#define _NATIVE_POSIX_IRQ_CTRL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void hw_irq_ctrl_init(void); +void hw_irq_ctrl_cleanup(void); +void hw_irq_ctrl_timer_triggered(void); + +void hw_irq_ctrl_set_cur_prio(int new); +int hw_irq_ctrl_get_cur_prio(void); + +void hw_irq_ctrl_prio_set(unsigned int irq, unsigned int prio); +uint8_t hw_irq_ctrl_get_prio(unsigned int irq); + +int hw_irq_ctrl_get_highest_prio_irq(void); +uint32_t hw_irq_ctrl_get_current_lock(void); +uint32_t hw_irq_ctrl_change_lock(uint32_t new_lock); +uint64_t hw_irq_ctrl_get_irq_status(void); +void hw_irq_ctrl_disable_irq(unsigned int irq); +int hw_irq_ctrl_is_irq_enabled(unsigned int irq); +void hw_irq_ctrl_clear_irq(unsigned int irq); +void hw_irq_ctrl_enable_irq(unsigned int irq); +void hw_irq_ctrl_set_irq(unsigned int irq); +void hw_irq_ctrl_raise_im(unsigned int irq); +void hw_irq_ctrl_raise_im_from_sw(unsigned int irq); + + +#define N_IRQS 32 + +#ifdef __cplusplus +} +#endif + +#endif /* _NATIVE_POSIX_IRQ_CTRL_H */ diff --git a/boards/posix/native_posix/irq_handler.c b/boards/posix/native_posix/irq_handler.c new file mode 100644 index 0000000000..2eeefb03fe --- /dev/null +++ b/boards/posix/native_posix/irq_handler.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2014 Wind River Systems, Inc. + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + * + * SW side of the IRQ handling + */ + +#include +#include "irq_handler.h" +#include "irq_offload.h" +#include "kernel_structs.h" +#include "irq_ctrl.h" +#include "posix_core.h" +#include "board_soc.h" +#include "sw_isr_table.h" +#include "soc.h" + + +typedef void (*normal_irq_f_ptr)(void *); +typedef int (*direct_irq_f_ptr)(void); + +typedef struct _isr_list isr_table_entry_t; +static isr_table_entry_t irq_vector_table[N_IRQS] = { { 0 } }; + +static int currently_running_irq = -1; + + +static inline void vector_to_irq(int irq_nbr, int *may_swap) +{ + if (irq_vector_table[irq_nbr].func == NULL) { + posix_print_error_and_exit("Received irq %i without a " + "registered handler\n", + irq_nbr); + } else { + if (irq_vector_table[irq_nbr].flags & ISR_FLAG_DIRECT) { + *may_swap |= ((direct_irq_f_ptr) + irq_vector_table[irq_nbr].func)(); + } else { + ((normal_irq_f_ptr)irq_vector_table[irq_nbr].func) + (irq_vector_table[irq_nbr].param); + *may_swap = 1; + /* + * TODO: look into how this kind of ISRs are meant + * to be wrapped + */ + } + } +} + +/** + * When an interrupt is raised, this function is called to handle it and, if + * needed, swap to a re-enabled thread + * + * Note that even that this function is executing in a Zephyr thread, it is + * effectively the model of the interrupt controller passing context to the IRQ + * handler and therefore its priority handling + */ +void posix_irq_handler(void) +{ + uint64_t irq_lock; + int irq_nbr; + int may_swap = 0; + + irq_lock = hw_irq_ctrl_get_current_lock(); + + if (irq_lock) { + /* "spurious" wakes can happen with interrupts locked */ + return; + } + + _kernel.nested++; + + while ((irq_nbr = hw_irq_ctrl_get_highest_prio_irq()) != -1) { + int last_current_running_prio = hw_irq_ctrl_get_cur_prio(); + int last_running_irq = currently_running_irq; + + hw_irq_ctrl_set_cur_prio(hw_irq_ctrl_get_prio(irq_nbr)); + hw_irq_ctrl_clear_irq(irq_nbr); + + currently_running_irq = irq_nbr; + vector_to_irq(irq_nbr, &may_swap); + currently_running_irq = last_running_irq; + + hw_irq_ctrl_set_cur_prio(last_current_running_prio); + } + + _kernel.nested--; + + /* Call swap if all the following is true: + * 1) may_swap was enabled + * 2) We are not nesting irq_handler calls (interrupts) + * 3) Current thread is preemptible + * 4) Next thread to run in the ready queue is not this thread + */ + if (may_swap && + (hw_irq_ctrl_get_cur_prio() == 256) && + (_current->base.preempt < _NON_PREEMPT_THRESHOLD) && + (_kernel.ready_q.cache != _current)) { + + __swap(irq_lock); + } +} + + +/** + * Thru this function the IRQ controller can raise an immediate interrupt which + * will interrupt the SW itself + * (this function should only be called from the HW model code, from SW threads) + */ +void posix_irq_handler_im_from_sw(void) +{ + /* + * if a higher priority interrupt than the possibly currently running is + * pending we go immediately into irq_handler() to vector into its + * handler + */ + if (hw_irq_ctrl_get_highest_prio_irq() != -1) { + if (!posix_is_cpu_running()) { + posix_print_error_and_exit("programming error: " + "nrf_irq_cont_handler_irq_im_from_sw() " + "called from a HW model thread\n"); + } + posix_irq_handler(); + } +} + + +/** + * @brief Disable all interrupts on the CPU + * + * This routine disables interrupts. It can be called from either interrupt, + * task or fiber level. This routine returns an architecture-dependent + * lock-out key representing the "interrupt disable state" prior to the call; + * this key can be passed to irq_unlock() to re-enable interrupts. + * + * The lock-out key should only be used as the argument to the irq_unlock() + * API. It should never be used to manually re-enable interrupts or to inspect + * or manipulate the contents of the source register. + * + * This function can be called recursively: it will return a key to return the + * state of interrupt locking to the previous level. + * + * WARNINGS + * Invoking a kernel routine with interrupts locked may result in + * interrupts being re-enabled for an unspecified period of time. If the + * called routine blocks, interrupts will be re-enabled while another + * thread executes, or while the system is idle. + * + * The "interrupt disable state" is an attribute of a thread. Thus, if a + * fiber or task disables interrupts and subsequently invokes a kernel + * routine that causes the calling thread to block, the interrupt + * disable state will be restored when the thread is later rescheduled + * for execution. + * + * @return An architecture-dependent lock-out key representing the + * "interrupt disable state" prior to the call. + * + */ +unsigned int posix_irq_lock(void) +{ + return hw_irq_ctrl_change_lock(true); +} + +unsigned int _arch_irq_lock(void) +{ + return posix_irq_lock(); +} + +/** + * + * @brief Enable all interrupts on the CPU + * + * This routine re-enables interrupts on the CPU. The @a key parameter is a + * board-dependent lock-out key that is returned by a previous invocation of + * board_irq_lock(). + * + * This routine can be called from either interrupt, task or fiber level. + * + * @return N/A + * + */ +void posix_irq_unlock(unsigned int key) +{ + hw_irq_ctrl_change_lock(key); +} + +void _arch_irq_unlock(unsigned int key) +{ + posix_irq_unlock(key); +} + + +void posix_irq_full_unlock(void) +{ + hw_irq_ctrl_change_lock(false); +} + +void _arch_irq_enable(unsigned int irq) +{ + hw_irq_ctrl_enable_irq(irq); +} + +void _arch_irq_disable(unsigned int irq) +{ + hw_irq_ctrl_disable_irq(irq); +} + +int _arch_irq_is_enabled(unsigned int irq) +{ + return hw_irq_ctrl_is_irq_enabled(irq); +} + +void _arch_isr_direct_header(void) +{ + /* Nothing to be done */ +} + +int posix_get_current_irq(void) +{ + return currently_running_irq; +} + + + +/** + * Configure a static interrupt. + * + * _isr_declare will populate the interrupt table table with the interrupt's + * parameters, the vector table and the software ISR table. + * + * We additionally set the priority in the interrupt controller at + * runtime. + * + * @param irq_p IRQ line number + * @param flags [plug it directly (1), or as a SW managed interrupt (0)] + * @param isr_p Interrupt service routine + * @param isr_param_p ISR parameter + * @param flags_p IRQ options + */ +void _isr_declare(unsigned int irq_p, int flags, void isr_p(void *), + void *isr_param_p) +{ + irq_vector_table[irq_p].irq = irq_p; + irq_vector_table[irq_p].func = isr_p; + irq_vector_table[irq_p].param = isr_param_p; + irq_vector_table[irq_p].flags = flags; +} + + +/* + * @internal + * + * @brief Set an interrupt's priority + * + * Lower values take priority over higher values. + * + * @return N/A + */ +void _irq_priority_set(unsigned int irq, unsigned int prio, uint32_t flags) +{ + hw_irq_ctrl_prio_set(irq, prio); +} + + + +/** + * Similar to ARM's NVIC_SetPendingIRQ + * set a pending IRQ from SW + * + * Note that this will interrupt immediately if the interrupt is not masked and + * IRQs are not locked, and this interrupt has higher priority than a possibly + * currently running interrupt + */ +void posix_sw_set_pending_IRQ(unsigned int IRQn) +{ + hw_irq_ctrl_raise_im_from_sw(IRQn); +} + + +/** + * Similar to ARM's NVIC_ClearPendingIRQ + * clear a pending irq from SW + */ +void posix_sw_clear_pending_IRQ(unsigned int IRQn) +{ + hw_irq_ctrl_clear_irq(IRQn); +} + +/** + * @brief Run a function in interrupt context + * + * In this simple board, the function can just be run directly + */ +void irq_offload(irq_offload_routine_t routine, void *parameter) +{ + _kernel.nested++; + routine(parameter); + _kernel.nested--; +} diff --git a/boards/posix/native_posix/irq_handler.h b/boards/posix/native_posix/irq_handler.h new file mode 100644 index 0000000000..3425182369 --- /dev/null +++ b/boards/posix/native_posix/irq_handler.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NATIVE_POSIX_IRQ_HANDLER_H +#define _NATIVE_POSIX_IRQ_HANDLER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +void posix_irq_handler_im_from_sw(void); +void posix_sw_set_pending_IRQ(unsigned int IRQn); +void posix_sw_clear_pending_IRQ(unsigned int IRQn); + + +#ifdef __cplusplus +} +#endif + +#endif /* _NATIVE_POSIX_IRQ_HANDLER_H */ diff --git a/boards/posix/native_posix/main.c b/boards/posix/native_posix/main.c new file mode 100644 index 0000000000..24d0772591 --- /dev/null +++ b/boards/posix/native_posix/main.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * The basic principle of operation is: + * No asynchronous behavior, no undeterminism. + * If you run the same thing 20 times, you get exactly the same result 20 + * times. + * It does not matter if you are running from console, or in a debugger + * and you go for lunch in the middle of the debug session. + * + * This is achieved as follows: + * The HW models run in their own simulated time. We do really not attempt + * to link ourselves to the actual real time / wall time of the machine as this + * would make execution undeterministic and debugging or instrumentation not + * really possible. Although we may slow the run to real time. + */ + +#include +#include "hw_models_top.h" +#include +#include "misc/util.h" + +#define STOP_AFTER_5_SECONDS 0 + + +void main_clean_up(int exit_code) +{ + static int max_exit_code; + + max_exit_code = max(exit_code, max_exit_code); + /* + * posix_soc_clean_up may not return if this is called from a SW thread, + * but instead it would get main_clean_up() recalled again + * ASAP from the HW thread + */ + posix_soc_clean_up(); + hwm_cleanup(); + exit(exit_code); +} + +/** + * This is the actual main for the Linux process, + * the Zephyr application main is renamed something else thru a define. + * + * Note that normally one wants to use this POSIX arch to be part of a + * simulation engine, with some proper HW models and whatnot + * + * This is just a very simple demo which is able to run some of the sample + * apps (hello world, synchronization, philosophers) and run the sanity-check + * regression + */ +int main(void) +{ + + hwm_init(); + +#if (STOP_AFTER_5_SECONDS) + hwm_set_end_of_time(5e6); +#endif + + posix_boot_cpu(); + + hwm_main_loop(); + + return 0; + +} diff --git a/boards/posix/native_posix/native_posix.yaml b/boards/posix/native_posix/native_posix.yaml new file mode 100644 index 0000000000..87ec02b478 --- /dev/null +++ b/boards/posix/native_posix/native_posix.yaml @@ -0,0 +1,8 @@ +identifier: native_posix +name: Native POSIX port +type: native +arch: posix +toolchain: + - zephyr +testing: + default: true diff --git a/boards/posix/native_posix/native_posix_defconfig b/boards/posix/native_posix/native_posix_defconfig new file mode 100644 index 0000000000..1c15ddd340 --- /dev/null +++ b/boards/posix/native_posix/native_posix_defconfig @@ -0,0 +1,6 @@ +CONFIG_ARCH_POSIX=y +CONFIG_SOC_INF_CLOCK=y +CONFIG_BOARD_NATIVE_POSIX=y +CONFIG_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=1000000 diff --git a/boards/posix/native_posix/timer_model.c b/boards/posix/native_posix/timer_model.c new file mode 100644 index 0000000000..3d74b701ce --- /dev/null +++ b/boards/posix/native_posix/timer_model.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2017 Oticon A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This file provides both a model of a simple HW timer and its driver + * + * If you want this timer model to slow down the execution to real time + * set (CONFIG_)NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME + */ + +#include +#include "hw_models_top.h" +#include "irq_ctrl.h" +#include "board_soc.h" +#include "zephyr/types.h" +#include "posix_soc_if.h" +#include "misc/util.h" + + +u64_t hw_timer_timer; + +u64_t hw_timer_tick_timer; +u64_t hw_timer_awake_timer; + +static u64_t tick_p = 10000; /* period of the ticker */ +static unsigned int silent_ticks; + +#if (CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME) +#include + +static u64_t Boot_time; +static struct timespec tv; +#endif + +static void hwtimer_update_timer(void) +{ + hw_timer_timer = min(hw_timer_tick_timer, hw_timer_awake_timer); +} + + +void hwtimer_init(void) +{ + silent_ticks = 0; + hw_timer_tick_timer = tick_p; + hw_timer_awake_timer = NEVER; + hwtimer_update_timer(); +#if (CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME) + clock_gettime(CLOCK_MONOTONIC, &tv); + Boot_time = tv.tv_sec*1e6 + tv.tv_nsec/1000; +#endif +} + + +void hwtimer_cleanup(void) +{ + +} + +static void hwtimer_tick_timer_reached(void) +{ +#if (CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME) + u64_t expected_realtime = Boot_time + hw_timer_tick_timer; + + clock_gettime(CLOCK_MONOTONIC, &tv); + u64_t actual_real_time = tv.tv_sec*1e6 + tv.tv_nsec/1000; + + int64_t diff = expected_realtime - actual_real_time; + + if (diff > 0) { /* we need to slow down */ + struct timespec requested_time; + struct timespec remaining; + + requested_time.tv_sec = diff / 1e6; + requested_time.tv_nsec = (diff - requested_time.tv_sec*1e6)*1e3; + + int s = nanosleep(&requested_time, &remaining); + + if (s == -1) { + posix_print_trace("Interrupted or error\n"); + } + } +#endif + + hw_timer_tick_timer += tick_p; + hwtimer_update_timer(); + + if (silent_ticks > 0) { + silent_ticks -= 1; + } else { + hw_irq_ctrl_set_irq(TIMER_TICK_IRQ); + } +} + + +static void hwtimer_awake_timer_reached(void) +{ + hw_timer_awake_timer = NEVER; + hwtimer_update_timer(); + hw_irq_ctrl_set_irq(PHONY_HARD_IRQ); +} + +void hwtimer_timer_reached(void) +{ + u64_t Now = hw_timer_timer; + + if (hw_timer_awake_timer == Now) { + hwtimer_awake_timer_reached(); + } + + if (hw_timer_tick_timer == Now) { + hwtimer_tick_timer_reached(); + } +} + + +/** + * The timer HW will awake the CPU (without an interrupt) at least when