diff --git a/examples/pic32/basic/.vimprj/.indexer_files b/examples/basic/arch/pic32/.vimprj/.indexer_files similarity index 100% rename from examples/pic32/basic/.vimprj/.indexer_files rename to examples/basic/arch/pic32/.vimprj/.indexer_files diff --git a/examples/pic32/basic/.vimprj/my.vim b/examples/basic/arch/pic32/.vimprj/my.vim similarity index 100% rename from examples/pic32/basic/.vimprj/my.vim rename to examples/basic/arch/pic32/.vimprj/my.vim diff --git a/examples/pic32/basic/src/tn_cfg_appl.h b/examples/basic/arch/pic32/tn_cfg_appl.h similarity index 100% rename from examples/pic32/basic/src/tn_cfg_appl.h rename to examples/basic/arch/pic32/tn_cfg_appl.h diff --git a/examples/pic32/basic/tn_pic32_example_basic.X/Makefile b/examples/basic/arch/pic32/tn_pic32_example_basic.X/Makefile similarity index 100% rename from examples/pic32/basic/tn_pic32_example_basic.X/Makefile rename to examples/basic/arch/pic32/tn_pic32_example_basic.X/Makefile diff --git a/examples/pic32/basic/tn_pic32_example_basic.X/nbproject/configurations.xml b/examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/configurations.xml similarity index 95% rename from examples/pic32/basic/tn_pic32_example_basic.X/nbproject/configurations.xml rename to examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/configurations.xml index 9fe8435..9306e2f 100644 --- a/examples/pic32/basic/tn_pic32_example_basic.X/nbproject/configurations.xml +++ b/examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/configurations.xml @@ -12,8 +12,8 @@ - ../../../../src/arch/pic32/tn_arch_pic32_int_vec1.S - ../src/tn_pic32_example_basic.c + ../../../../../src/arch/pic32/tn_arch_pic32_int_vec1.S + ../tn_pic32_example_basic.c - ../src ../../../../src/arch/pic32 + ../../../../../src/arch/pic32 + .. Makefile @@ -42,12 +43,12 @@ - - + @@ -182,7 +183,7 @@ - + @@ -216,10 +217,10 @@ - + - + @@ -228,10 +229,8 @@ - - @@ -241,7 +240,7 @@ - + diff --git a/examples/pic32/basic/tn_pic32_example_basic.X/nbproject/project.properties b/examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/project.properties similarity index 100% rename from examples/pic32/basic/tn_pic32_example_basic.X/nbproject/project.properties rename to examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/project.properties diff --git a/examples/pic32/basic/tn_pic32_example_basic.X/nbproject/project.xml b/examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/project.xml similarity index 88% rename from examples/pic32/basic/tn_pic32_example_basic.X/nbproject/project.xml rename to examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/project.xml index 3eeb33a..a14bf5d 100644 --- a/examples/pic32/basic/tn_pic32_example_basic.X/nbproject/project.xml +++ b/examples/basic/arch/pic32/tn_pic32_example_basic.X/nbproject/project.xml @@ -11,7 +11,7 @@ UTF-8 - ../../../../src/arch/pic32/tneokernel.X + ../../../../../src/arch/pic32/tneokernel.X diff --git a/examples/pic32/basic/src/tn_pic32_example_basic.c b/examples/basic/arch/pic32/tn_pic32_example_basic.c similarity index 96% rename from examples/pic32/basic/src/tn_pic32_example_basic.c rename to examples/basic/arch/pic32/tn_pic32_example_basic.c index cddfc2f..661fafc 100644 --- a/examples/pic32/basic/src/tn_pic32_example_basic.c +++ b/examples/basic/arch/pic32/tn_pic32_example_basic.c @@ -92,14 +92,14 @@ // and for interrupts are the requirement of the kernel; // others are application-dependent. // -// We use convenience macro TN_TASK_STACK_DEF() for that. +// We use convenience macro TN_STACK_ARR_DEF() for that. -TN_TASK_STACK_DEF(idle_task_stack, IDLE_TASK_STACK_SIZE); -TN_TASK_STACK_DEF(interrupt_stack, INTERRUPT_STACK_SIZE); +TN_STACK_ARR_DEF(idle_task_stack, IDLE_TASK_STACK_SIZE); +TN_STACK_ARR_DEF(interrupt_stack, INTERRUPT_STACK_SIZE); -TN_TASK_STACK_DEF(task_a_stack, TASK_A_STK_SIZE); -TN_TASK_STACK_DEF(task_b_stack, TASK_B_STK_SIZE); -TN_TASK_STACK_DEF(task_c_stack, TASK_C_STK_SIZE); +TN_STACK_ARR_DEF(task_a_stack, TASK_A_STK_SIZE); +TN_STACK_ARR_DEF(task_b_stack, TASK_B_STK_SIZE); +TN_STACK_ARR_DEF(task_c_stack, TASK_C_STK_SIZE); @@ -202,10 +202,6 @@ void hw_init(void) INTClearFlag(INT_CS0); INTEnable(INT_CS0, INT_ENABLED); - //-- configure LED port pins - mPORTESetPinsDigitalOut(BIT_0 | BIT_1 | BIT_2); - mPORTEClearBits(BIT_0 | BIT_1 | BIT_2); - //-- enable multi-vectored interrupt mode INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR); } @@ -215,6 +211,10 @@ void hw_init(void) */ void appl_init(void) { + //-- configure LED port pins + mPORTESetPinsDigitalOut(BIT_0 | BIT_1 | BIT_2); + mPORTEClearBits(BIT_0 | BIT_1 | BIT_2); + //-- initialize various on-board peripherals, such as // flash memory, displays, etc. // (in this sample project there's nothing to init) diff --git a/examples/common/arch/pic32/example_arch.h b/examples/common/arch/pic32/example_arch.h new file mode 100644 index 0000000..1c0d38d --- /dev/null +++ b/examples/common/arch/pic32/example_arch.h @@ -0,0 +1,41 @@ +/******************************************************************************* + * Description: TODO + * + ******************************************************************************/ + +#ifndef _EXAMPLE_ARCH_H +#define _EXAMPLE_ARCH_H + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC TYPES + ******************************************************************************/ + +/******************************************************************************* + * GLOBAL VARIABLES + ******************************************************************************/ + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +//-- instruction that causes debugger to halt +#define SOFTWARE_BREAK() __asm__ volatile ("sdbbp 0") + + +/******************************************************************************* + * PUBLIC FUNCTION PROTOTYPES + ******************************************************************************/ + + +#endif // _EXAMPLE_ARCH_H + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + diff --git a/examples/common/arch/pic32/pic32_arch.c b/examples/common/arch/pic32/pic32_arch.c new file mode 100644 index 0000000..318343d --- /dev/null +++ b/examples/common/arch/pic32/pic32_arch.c @@ -0,0 +1,198 @@ +/** + * TNeoKernel PIC32 basic example + */ + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +#include +#include +#include +#include "tn.h" + +#include "example_arch.h" + + + +/******************************************************************************* + * PIC32 HARDWARE CONFIGURATION + ******************************************************************************/ + +#pragma config FNOSC = PRIPLL // Oscillator Selection +#pragma config FPLLIDIV = DIV_4 // PLL Input Divider (PIC32 Starter Kit: use divide by 2 only) +#pragma config FPLLMUL = MUL_20 // PLL Multiplier +#pragma config FPLLODIV = DIV_1 // PLL Output Divider +#pragma config FPBDIV = DIV_2 // Peripheral Clock divisor +#pragma config FWDTEN = OFF // Watchdog Timer +#pragma config WDTPS = PS1 // Watchdog Timer Postscale +#pragma config FCKSM = CSDCMD // Clock Switching & Fail Safe Clock Monitor +#pragma config OSCIOFNC = OFF // CLKO Enable +#pragma config POSCMOD = HS // Primary Oscillator +#pragma config IESO = OFF // Internal/External Switch-over +#pragma config FSOSCEN = OFF // Secondary Oscillator Enable +#pragma config CP = OFF // Code Protect +#pragma config BWP = OFF // Boot Flash Write Protect +#pragma config PWP = OFF // Program Flash Write Protect +#pragma config ICESEL = ICS_PGx2 // ICE/ICD Comm Channel Select +#pragma config DEBUG = OFF // Debugger Disabled for Starter Kit + + + + +/******************************************************************************* + * MACROS + ******************************************************************************/ + +//-- system frequency +#define SYS_FREQ 80000000UL + +//-- peripheral bus frequency +#define PB_FREQ 40000000UL + +//-- kernel ticks (system timer) frequency +#define SYS_TMR_FREQ 1000 + +//-- system timer prescaler +#define SYS_TMR_PRESCALER T5_PS_1_8 +#define SYS_TMR_PRESCALER_VALUE 8 + +//-- system timer period (auto-calculated) +#define SYS_TMR_PERIOD \ + (PB_FREQ / SYS_TMR_PRESCALER_VALUE / SYS_TMR_FREQ) + + + + +//-- idle task stack size, in words +#define IDLE_TASK_STACK_SIZE (TN_MIN_STACK_SIZE + 16) + +//-- interrupt stack size, in words +#define INTERRUPT_STACK_SIZE (TN_MIN_STACK_SIZE + 64) + + + +/******************************************************************************* + * EXTERN FUNCTION PROTOTYPE + ******************************************************************************/ + +//-- defined by particular example: create first application task(s) +extern void init_task_create(void); + + + +/******************************************************************************* + * DATA + ******************************************************************************/ + +//-- Allocate arrays for stacks: stack for idle task +// and for interrupts are the requirement of the kernel; +// others are application-dependent. +// +// We use convenience macro TN_STACK_ARR_DEF() for that. + +TN_STACK_ARR_DEF(idle_task_stack, IDLE_TASK_STACK_SIZE); +TN_STACK_ARR_DEF(interrupt_stack, INTERRUPT_STACK_SIZE); + + + +/******************************************************************************* + * ISRs + ******************************************************************************/ + +/** + * system timer ISR + */ +tn_soft_isr(_TIMER_5_VECTOR) +{ + INTClearFlag(INT_T5); + tn_tick_int_processing(); +} + + + +/******************************************************************************* + * FUNCTIONS + ******************************************************************************/ + +/** + * Hardware init: called from main() with interrupts disabled + */ +void hw_init(void) +{ + SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE); + + //turn off ADC function for all pins + AD1PCFG = 0xffffffff; + + //-- enable timer5 interrupt + OpenTimer5((0 + | T5_ON + | T5_IDLE_STOP + | SYS_TMR_PRESCALER + | T5_SOURCE_INT + ), + (SYS_TMR_PERIOD - 1) + ); + + //-- set timer5 interrupt priority to 2, enable it + INTSetVectorPriority(INT_TIMER_5_VECTOR, INT_PRIORITY_LEVEL_2); + INTSetVectorSubPriority(INT_TIMER_5_VECTOR, INT_SUB_PRIORITY_LEVEL_0); + INTClearFlag(INT_T5); + INTEnable(INT_T5, INT_ENABLED); + + //-- TNeoKernel PIC32 requirement: + // set up the software interrupt 0 with a priority of 1, subpriority 0 + // + // NOTE: the ISR is declared in kernel-provided file + // tn_arch_pic32_int_vec1.S, which should be included in the application + // project itself, in order to dispatch vector correctly. + INTSetVectorPriority(INT_CORE_SOFTWARE_0_VECTOR, INT_PRIORITY_LEVEL_1); + INTSetVectorSubPriority(INT_CORE_SOFTWARE_0_VECTOR, INT_SUB_PRIORITY_LEVEL_0); + INTClearFlag(INT_CS0); + INTEnable(INT_CS0, INT_ENABLED); + + //-- enable multi-vectored interrupt mode + INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR); +} + +//-- idle callback that is called periodically from idle task +void idle_task_callback (void) +{ +} + +int32_t main(void) +{ +#ifndef PIC32_STARTER_KIT + /*The JTAG is on by default on POR. A PIC32 Starter Kit uses the JTAG, but + for other debug tool use, like ICD 3 and Real ICE, the JTAG should be off + to free up the JTAG I/O */ + DDPCONbits.JTAGEN = 0; +#endif + + //-- unconditionally disable interrupts + tn_arch_int_dis(); + + //-- init hardware + hw_init(); + + //-- call to tn_sys_start() never returns + tn_sys_start( + idle_task_stack, + IDLE_TASK_STACK_SIZE, + interrupt_stack, + INTERRUPT_STACK_SIZE, + init_task_create, + idle_task_callback + ); + + //-- unreachable + return 1; +} + +void __attribute__((naked, nomips16, noreturn)) _general_exception_handler(void) +{ + SOFTWARE_BREAK(); + for (;;) ; +} + diff --git a/examples/queue/arch/pic32/example_queue_pic32.X/Makefile b/examples/queue/arch/pic32/example_queue_pic32.X/Makefile new file mode 100644 index 0000000..fca8e2c --- /dev/null +++ b/examples/queue/arch/pic32/example_queue_pic32.X/Makefile @@ -0,0 +1,113 @@ +# +# There exist several targets which are by default empty and which can be +# used for execution of your targets. These targets are usually executed +# before and after some main targets. They are: +# +# .build-pre: called before 'build' target +# .build-post: called after 'build' target +# .clean-pre: called before 'clean' target +# .clean-post: called after 'clean' target +# .clobber-pre: called before 'clobber' target +# .clobber-post: called after 'clobber' target +# .all-pre: called before 'all' target +# .all-post: called after 'all' target +# .help-pre: called before 'help' target +# .help-post: called after 'help' target +# +# Targets beginning with '.' are not intended to be called on their own. +# +# Main targets can be executed directly, and they are: +# +# build build a specific configuration +# clean remove built files from a configuration +# clobber remove all built files +# all build all configurations +# help print help mesage +# +# Targets .build-impl, .clean-impl, .clobber-impl, .all-impl, and +# .help-impl are implemented in nbproject/makefile-impl.mk. +# +# Available make variables: +# +# CND_BASEDIR base directory for relative paths +# CND_DISTDIR default top distribution directory (build artifacts) +# CND_BUILDDIR default top build directory (object files, ...) +# CONF name of current configuration +# CND_ARTIFACT_DIR_${CONF} directory of build artifact (current configuration) +# CND_ARTIFACT_NAME_${CONF} name of build artifact (current configuration) +# CND_ARTIFACT_PATH_${CONF} path to build artifact (current configuration) +# CND_PACKAGE_DIR_${CONF} directory of package (current configuration) +# CND_PACKAGE_NAME_${CONF} name of package (current configuration) +# CND_PACKAGE_PATH_${CONF} path to package (current configuration) +# +# NOCDDL + + +# Environment +MKDIR=mkdir +CP=cp +CCADMIN=CCadmin +RANLIB=ranlib + + +# build +build: .build-post + +.build-pre: +# Add your pre 'build' code here... + +.build-post: .build-impl +# Add your post 'build' code here... + + +# clean +clean: .clean-post + +.clean-pre: +# Add your pre 'clean' code here... +# WARNING: the IDE does not call this target since it takes a long time to +# simply run make. Instead, the IDE removes the configuration directories +# under build and dist directly without calling make. +# This target is left here so people can do a clean when running a clean +# outside the IDE. + +.clean-post: .clean-impl +# Add your post 'clean' code here... + + +# clobber +clobber: .clobber-post + +.clobber-pre: +# Add your pre 'clobber' code here... + +.clobber-post: .clobber-impl +# Add your post 'clobber' code here... + + +# all +all: .all-post + +.all-pre: +# Add your pre 'all' code here... + +.all-post: .all-impl +# Add your post 'all' code here... + + +# help +help: .help-post + +.help-pre: +# Add your pre 'help' code here... + +.help-post: .help-impl +# Add your post 'help' code here... + + + +# include project implementation makefile +include nbproject/Makefile-impl.mk + +# include project make variables +include nbproject/Makefile-variables.mk diff --git a/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/configurations.xml b/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/configurations.xml new file mode 100644 index 0000000..cd8df9b --- /dev/null +++ b/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/configurations.xml @@ -0,0 +1,253 @@ + + + + + + + + + + ../pic32_arch.c + ../queue_example_arch.c + ../../../../../src/arch/pic32/tn_arch_pic32_int_vec1.S + + ../../../queue_example.c + ../../../task_consumer.c + ../../../task_producer.c + + + Makefile + + + + ../../../../common/queue + ../../../../../src/arch/pic32 + ../../.. + + Makefile + + + + localhost + PIC32MX440F512H + + + ICD3PlatformTool + XC32 + 1.21 + 2 + + + + + + + + + + + + false + false + + + + + false + + false + + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/project.properties b/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/project.properties new file mode 100644 index 0000000..e69de29 diff --git a/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/project.xml b/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/project.xml new file mode 100644 index 0000000..f6b4300 --- /dev/null +++ b/examples/queue/arch/pic32/example_queue_pic32.X/nbproject/project.xml @@ -0,0 +1,18 @@ + + com.microchip.mplab.nbide.embedded.makeproject + + + example_queue_pic32 + e6b3e7cb-7b49-4750-89c9-cf229d92ec7f + 0 + c + + + + UTF-8 + + ../../../../../src/arch/pic32/tneokernel.X + + + + diff --git a/examples/queue/arch/pic32/pic32_arch.c b/examples/queue/arch/pic32/pic32_arch.c new file mode 100644 index 0000000..63efb43 --- /dev/null +++ b/examples/queue/arch/pic32/pic32_arch.c @@ -0,0 +1,4 @@ + +//-- Include common pic32 source for all examples +#include "../../../common/arch/pic32/pic32_arch.c" + diff --git a/examples/queue/arch/pic32/queue_example_arch.c b/examples/queue/arch/pic32/queue_example_arch.c new file mode 100644 index 0000000..4178e5b --- /dev/null +++ b/examples/queue/arch/pic32/queue_example_arch.c @@ -0,0 +1,83 @@ +/** + * \file + * + * Data queue usage example. + * + * For general information about the pattern, refer to the top of + * queue_example.h file + */ + + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +#include +#include "queue_example_arch.h" +#include "task_consumer.h" + + + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + + +//-- gpio register to work with, you might want to switch it to different port +// NOTE: in real programs, it's better to use some peripheral library. +// I strongly dislike plib as well as "brand new" Harmony by Microchip, +// I use my own peripheral library, but for example such as this one, +// I decided to write it as simple as possible. +#define TRIS_REG TRISE +#define TRIS_REG_CLR TRISECLR +#define TRIS_REG_SET TRISESET +#define TRIS_REG_INV TRISEINV + +#define PORT_REG PORTE +#define PORT_REG_CLR PORTECLR +#define PORT_REG_SET PORTESET +#define PORT_REG_INV PORTEINV + + +#define TASK_CONS_PIN_MASK (0 \ + | (1 << TASK_CONS_PIN__0) \ + | (1 << TASK_CONS_PIN__1) \ + | (1 << TASK_CONS_PIN__2) \ + ) + + + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +/******************************************************************************* + * PRIVATE FUNCTIONS + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * See comments in the header file + */ +void queue_example_arch_init(void) +{ + //-- set output for needed pins + TRIS_REG_CLR = TASK_CONS_PIN_MASK; + + //-- clear current port value + PORT_REG_CLR = TASK_CONS_PIN_MASK; +} + +/** + * See comments in the header file + */ +void queue_example_arch_pins_toggle(int pin_mask) +{ + PORT_REG_INV = pin_mask; +} + + diff --git a/examples/queue/arch/pic32/queue_example_arch.h b/examples/queue/arch/pic32/queue_example_arch.h new file mode 100644 index 0000000..b3ee7a9 --- /dev/null +++ b/examples/queue/arch/pic32/queue_example_arch.h @@ -0,0 +1,26 @@ + +#ifndef _QUEUE_EXAMPLE_ARCH_H +#define _QUEUE_EXAMPLE_ARCH_H + + + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +//-- Include common pic32 header for all examples +#include "../../../common/arch/pic32/example_arch.h" + +/** + * At least, we need to initialize GPIO pins here + */ +void queue_example_arch_init(void); + +/** + * Toggle pins specified by pin_mask + */ +void queue_example_arch_pins_toggle(int pin_mask); + + +#endif // _QUEUE_EXAMPLE_ARCH_H + diff --git a/examples/queue/queue_example.c b/examples/queue/queue_example.c new file mode 100644 index 0000000..b8661eb --- /dev/null +++ b/examples/queue/queue_example.c @@ -0,0 +1,69 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +#include "queue_example.h" + +#include "task_consumer.h" +#include "task_producer.h" + +#include "tn.h" + + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +static struct TN_EventGrp que_example_events; + + + + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * Each example should define this funtion: it creates first application task + */ +void init_task_create(void) +{ + task_producer_create(); +} + + +/** + * See comments in the header file + */ +void queue_example_init(void) +{ + //-- create application events + // (see enum E_QueExampleFlag in the header) + SYSRETVAL_CHECK(tn_eventgrp_create(&que_example_events, (0))); + + //-- init architecture-dependent stuff + queue_example_arch_init(); +} + +/** + * See comments in the header file + */ +struct TN_EventGrp *queue_example_eventgrp_get(void) +{ + return &que_example_events; +} + +/******************************************************************************* + * end of file + ******************************************************************************/ + + + diff --git a/examples/queue/queue_example.h b/examples/queue/queue_example.h new file mode 100644 index 0000000..c45f248 --- /dev/null +++ b/examples/queue/queue_example.h @@ -0,0 +1,146 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + +#ifndef _QUEUE_EXAMPLE_H +#define _QUEUE_EXAMPLE_H + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +//-- include architecture-specific things, +// at least, there is SOFTWARE_BREAK macro +#include "queue_example_arch.h" + + +/******************************************************************************* + * EXTERN TYPES + ******************************************************************************/ + +/// Application common event group (see `enum E_QueExampleFlag`) +struct TN_EventGrp; + + + +/******************************************************************************* + * PUBLIC TYPES + ******************************************************************************/ + +enum E_QueExampleFlag { + /// + /// Flag indicating that consumer task is initialized + QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT = (1 << 0), + /// + /// Flag indicating that producer task is initialized + QUE_EXAMPLE_FLAG__TASK_PRODUCER_INIT = (1 << 1), +}; + + + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +/** + * Macro for checking value returned from system service. + * + * If you ignore the value returned by any system service, it is almost always + * a BAD idea: if something goes wrong, the sooner you know it, the better. + * But, checking error for every system service is a kind of annoying, so, + * simple macro was invented for that. + * + * In most situations, any values other than `#TN_RC_OK` or `#TN_RC_TIMEOUT` + * are not allowed (i.e. should never happen in normal device operation). + * + * So, if error value is neither `#TN_RC_OK` nor `#TN_RC_TIMEOUT`, this macro + * issues a software break. + * + * If you need to allow `#TN_RC_OK` only, use `SYSRETVAL_CHECK()` instead (see + * below) + * + * Usage is as follows: + * + * \code{.c} + * enum TN_RCode rc + * = SYSRETVAL_CHECK_TO(tn_queue_send(&my_queue, p_data, MY_TIMEOUT)); + * + * switch (rc){ + * case TN_RC_OK: + * //-- handle successfull operation + * break; + * case TN_RC_TIMEOUT: + * //-- handle timeout + * break; + * default: + * //-- should never be here + * break; + * } + * \endcode + * + */ +#define SYSRETVAL_CHECK_TO(x) \ + ({ \ + int __rv = (x); \ + if (__rv != TN_RC_OK && __rv != TN_RC_TIMEOUT){ \ + SOFTWARE_BREAK(); \ + } \ + /* like, return __rv */ \ + __rv; \ + }) + +/** + * The same as `SYSRETVAL_CHECK_TO()`, but it allows `#TN_RC_OK` only. + * + * Since there is only one return code allowed, usage is simple: + * + * \code{.c} + * SYSRETVAL_CHECK(tn_queue_send(&my_queue, p_data, MY_TIMEOUT)); + * \endcode + */ +#define SYSRETVAL_CHECK(x) \ + ({ \ + int __rv = (x); \ + if (__rv != TN_RC_OK){ \ + SOFTWARE_BREAK(); \ + } \ + /* like, return __rv */ \ + __rv; \ + }) + + + +/******************************************************************************* + * PUBLIC FUNCTION PROTOTYPES + ******************************************************************************/ + +/** + * Each example should define this funtion: it creates first application task + */ +void init_task_create(void); + +/** + * Initialization of application common objects, must be called once + * from the initial application task (which is created in init_task_create()) + */ +void queue_example_init(void); + +/** + * Returns pointer to the application event group. + * See `enum E_QueExampleFlag` + * + * Do note that you must call `queue_example_init()` before + * you can get eventgrp. + */ +struct TN_EventGrp *queue_example_eventgrp_get(void); + +#endif // _QUEUE_EXAMPLE_H + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + diff --git a/examples/queue/readme.txt b/examples/queue/readme.txt new file mode 100644 index 0000000..f8598b9 --- /dev/null +++ b/examples/queue/readme.txt @@ -0,0 +1,69 @@ + +This is an usage example of queues in TNeoKernel. + +When you need to build a queue of some data objects from one task to +another, the common pattern in the TNeoKernel is to use fixed memory pool +and queue together. Number of elements in the memory pool is equal to the +number of elements in the queue. So when you need to send the message, +you get block from the memory pool, fill it with data, and send pointer +to it by means of the queue. + +When you receive the message, you handle the data, and release the memory +back to the memory pool. + +This example project demonstrates this technique: there are two tasks: + +- producer task (which sends messages to the consumer) +- consumer task (which receives messages from the producer) + +Actual functionality is, of course, very simple: each message contains +the following: + +- command to perform: currently there is just a single command: toggle pin +- pin number: obviously, number of the pin for which command should + be performed. + +For the message structure, refer to `struct TaskConsumerMsg` in the +file `task_consumer.c`. Note, however, that it is a "private" structure: +producer doesn't touch it directly; instead, it uses special function: +`task_consumer_msg_send()`, which sends message to the queue. + +So the producer just sends new message each 100 ms, specifying +one of three pins. Consumer, consequently, toggles the pin specified +in the message. + +Before we can get to the business, we need to initialize things. +How it is done: + +- `init_task_create()`, which is called from the `tn_sys_start()`, + creates first application task: the producer task. +- producer task gets executed and first of all it initializes the + essential application stuff, by calling `appl_init()`: + + - `queue_example_init()` is called, in which common application + objects are created: in this project, there's just a single + object: event group that is used to synchronize task + initialization. See `enum E_QueExampleFlag`. + - `queue_example_arch_init()` is called, in which architecture + dependent things are initialized: at least, we need to setup + some GPIO pins so that consumer could use them. + - create consumer task + - wait until it is initialized (i.e. wait for the flag + QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT in the event group returned + by `queue_example_eventgrp_get()`) + +- consumer task gets executed + - gpio is configured: we need to set up certain pins to output + as well as initially clear them + - consumer initializes its own objects: + - fixed memory pool + - queue + - sets the flag QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT + - enters endless loop in which it will receive and + handle messages. Now, it is going to wait for first message. + +- producer task gets executed and enters its endless loop in which + it will send messages to the consumer. + +At this point, everything is initialized. + diff --git a/examples/queue/task_consumer.c b/examples/queue/task_consumer.c new file mode 100644 index 0000000..cc5ddd9 --- /dev/null +++ b/examples/queue/task_consumer.c @@ -0,0 +1,234 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +#include "task_consumer.h" +#include "task_producer.h" +#include "queue_example.h" +#include "tn.h" + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +//-- task stack size +#define TASK_CONSUMER_STACK_SIZE (TN_MIN_STACK_SIZE + 96) + +//-- priority of consumer task: the highest one +#define TASK_CONSUMER_PRIORITY 0 + +//-- number of items in the consumer message queue +#define CONS_QUE_BUF_SIZE 4 + +//-- max timeout for waiting for memory and free message +#define WAIT_TIMEOUT 10 + + + + + +/******************************************************************************* + * PRIVATE FUNCTION PROTOTYPES + ******************************************************************************/ + +/******************************************************************************* + * PRIVATE TYPES + ******************************************************************************/ + +/** + * Type of message that consumer receives. + */ +struct TaskConsumerMsg { + enum E_TaskConsCmd cmd; //-- command to perform + // (well, now there's just one command) + enum E_TaskConsPin pin_num; //-- number of pin for which command + // should be performed +}; + + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +//-- define array for task stack +TN_STACK_ARR_DEF(task_consumer_stack, TASK_CONSUMER_STACK_SIZE); + +//-- task descriptor: it's better to explicitly zero it +static struct TN_Task task_consumer = {}; + +//-- define queue and buffer for it +struct TN_DQueue cons_que; +void *cons_que_buf[ CONS_QUE_BUF_SIZE ]; + +//-- define fixed memory pool and buffer for it +struct TN_FMem cons_fmem; +TN_FMEM_BUF_DEF(cons_fmem_buf, struct TaskConsumerMsg, CONS_QUE_BUF_SIZE); + + + +/******************************************************************************* + * PUBLIC DATA + ******************************************************************************/ + +/******************************************************************************* + * EXTERNAL DATA + ******************************************************************************/ + +/******************************************************************************* + * EXTERNAL FUNCTION PROTOTYPES + ******************************************************************************/ + +/******************************************************************************* + * PRIVATE FUNCTIONS + ******************************************************************************/ + +static void task_consumer_body(void *par) +{ + //-- init things specific to consumer task + + //-- create memory pool + SYSRETVAL_CHECK( + tn_fmem_create( + &cons_fmem, + cons_fmem_buf, + TN_MAKE_ALIG_SIZE(sizeof(struct TaskConsumerMsg)), + CONS_QUE_BUF_SIZE + ) + ); + + //-- create queue + SYSRETVAL_CHECK( + tn_queue_create(&cons_que, (void *)cons_que_buf, CONS_QUE_BUF_SIZE) + ); + + //-- cry that consumer task has initialized + SYSRETVAL_CHECK( + tn_eventgrp_modify( + queue_example_eventgrp_get(), + TN_EVENTGRP_OP_SET, + QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT + ) + ); + + + struct TaskConsumerMsg *p_msg; + + //-- enter endless loop in which we will receive and handle messages + for (;;) + { + //-- wait for message, potentially can wait forever + // (if there are no messages) + enum TN_RCode rc = SYSRETVAL_CHECK( + tn_queue_receive(&cons_que, (void *)&p_msg, TN_WAIT_INFINITE) + ); + + if (rc == TN_RC_OK){ + + //-- message successfully received, let's check command + switch (p_msg->cmd){ + + case TASK_CONS_CMD__PIN_TOGGLE: + //-- toggle specified bit + queue_example_arch_pins_toggle(1 << p_msg->pin_num); + break; + + default: + //-- should never be here + SOFTWARE_BREAK(); + break; + } + + //-- free memory + SYSRETVAL_CHECK(tn_fmem_release(&cons_fmem, (void *)p_msg)); + } + + } +} + + + + + + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * See comments in the header file + */ +void task_consumer_create(void) +{ + + SYSRETVAL_CHECK( + tn_task_create( + &task_consumer, + task_consumer_body, + TASK_CONSUMER_PRIORITY, + task_consumer_stack, + TASK_CONSUMER_STACK_SIZE, + NULL, + (TN_TASK_CREATE_OPT_START) + ) + ); + +} + +/** + * See comments in the header file + */ +int task_consumer_msg_send(enum E_TaskConsCmd cmd, enum E_TaskConsPin pin_num) +{ + int ret = 0; + + struct TaskConsumerMsg *p_msg; + enum TN_RCode tn_rc; + + //-- get memory block from memory pool + tn_rc = SYSRETVAL_CHECK_TO( + tn_fmem_get(&cons_fmem, (void *)&p_msg, WAIT_TIMEOUT) + ); + if (tn_rc == TN_RC_OK){ + + //-- put correct data to the allocated memory + p_msg->cmd = cmd; + p_msg->pin_num = pin_num; + + //-- send it to the consumer task + tn_rc = SYSRETVAL_CHECK_TO( + tn_queue_send(&cons_que, (void *)p_msg, WAIT_TIMEOUT) + ); + + if (tn_rc == TN_RC_OK){ + ret = 1/*success*/; + } else { + //-- there was some error while sending the message, + // so, we should free buffer that we've allocated + SYSRETVAL_CHECK(tn_fmem_release(&cons_fmem, (void *)p_msg)); + } + } else { + //-- there was some error while allocating memory, + // nothing to do here: ret is still 0, and it is + // going to be returned + } + + return ret; +} + + + + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + + diff --git a/examples/queue/task_consumer.h b/examples/queue/task_consumer.h new file mode 100644 index 0000000..c21adb3 --- /dev/null +++ b/examples/queue/task_consumer.h @@ -0,0 +1,68 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +#ifndef _TASK_CONSUMER_H +#define _TASK_CONSUMER_H + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC TYPES + ******************************************************************************/ + +enum E_TaskConsCmd { + TASK_CONS_CMD__PIN_TOGGLE, +}; + +enum E_TaskConsPin { + TASK_CONS_PIN__0, + TASK_CONS_PIN__1, + TASK_CONS_PIN__2, +}; + +/******************************************************************************* + * GLOBAL VARIABLES + ******************************************************************************/ + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + + +/******************************************************************************* + * PUBLIC FUNCTION PROTOTYPES + ******************************************************************************/ + +/** + * Create consumer task. + */ +void task_consumer_create(void); + + +/** + * Send a message to the consumer. + * In this simple example, consumer will just toggle given pin number. + * + * @param pin_num + * pin number to toggle. + * + * @return + * - 1 on success + * - 0 on failure + */ +int task_consumer_msg_send(enum E_TaskConsCmd cmd, enum E_TaskConsPin pin_num); + +#endif // _TASK_CONSUMER_H + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + diff --git a/examples/queue/task_producer.c b/examples/queue/task_producer.c new file mode 100644 index 0000000..d3c4fcc --- /dev/null +++ b/examples/queue/task_producer.c @@ -0,0 +1,161 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +#include "task_producer.h" +#include "task_consumer.h" +#include "queue_example.h" +#include "tn.h" + + + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +//-- task stack size +#define TASK_PRODUCER_STACK_SIZE (TN_MIN_STACK_SIZE + 96) + +//-- highest priority +#define TASK_PRODUCER_PRIORITY 0 + + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +//-- define array for task stack +TN_STACK_ARR_DEF(task_producer_stack, TASK_PRODUCER_STACK_SIZE); + +//-- task descriptor: it's better to explicitly zero it +static struct TN_Task task_producer = {}; + + + +/******************************************************************************* + * PRIVATE FUNCTIONS + ******************************************************************************/ + +/** + * Application init: called from the first created application task + */ +static void appl_init(void) +{ + //-- init common application objects + queue_example_init(); + + //-- create all the rest application tasks: + + //-- create the consumer task {{{ + { + task_consumer_create(); + + //-- wait until consumer task initialized + SYSRETVAL_CHECK( + tn_eventgrp_wait( + queue_example_eventgrp_get(), + QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT, + TN_EVENTGRP_WMODE_AND, + NULL, + TN_WAIT_INFINITE + ) + ); + } + // }}} + +} + +/** + * Body function for producer task + */ +static void task_producer_body(void *par) +{ + //-- in this particular application, producer task is the first application + // task that is started, so, we should perform all the app initialization + // here, and then start other tasks. All of this is done in the appl_init(). + appl_init(); + + //-- cry that producer task has initialized + SYSRETVAL_CHECK( + tn_eventgrp_modify( + queue_example_eventgrp_get(), + TN_EVENTGRP_OP_SET, + QUE_EXAMPLE_FLAG__TASK_PRODUCER_INIT + ) + ); + + //-- at this point, application is completely initialized, and we can + // get to business: enter endless loop and repeatedly send + // messages to the consumer + for (;;) + { + int i; + + for (i = 0; i < 3/*pins count*/;i++){ + //-- Wait before sending message + SYSRETVAL_CHECK_TO( tn_task_sleep(100) ); + + enum E_TaskConsPin pin_num = 0; + + //-- determine pin_num + switch (i){ + case 0: + pin_num = TASK_CONS_PIN__0; + break; + case 1: + pin_num = TASK_CONS_PIN__1; + break; + case 2: + pin_num = TASK_CONS_PIN__2; + break; + default: + //-- should never be here + SOFTWARE_BREAK(); + break; + } + + //-- Send the message to consumer + if (!task_consumer_msg_send( + TASK_CONS_CMD__PIN_TOGGLE, + pin_num + ) + ) + { + //-- failed to send the message + } + } + + } +} + + + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * See comments in the header file + */ +void task_producer_create(void) +{ + + SYSRETVAL_CHECK( + tn_task_create( + &task_producer, + task_producer_body, + TASK_PRODUCER_PRIORITY, + task_producer_stack, + TASK_PRODUCER_STACK_SIZE, + NULL, + (TN_TASK_CREATE_OPT_START) + ) + ); + +} + + diff --git a/examples/queue/task_producer.h b/examples/queue/task_producer.h new file mode 100644 index 0000000..43e8ba0 --- /dev/null +++ b/examples/queue/task_producer.h @@ -0,0 +1,43 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +#ifndef _TASK_PRODUCER_H +#define _TASK_PRODUCER_H + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC TYPES + ******************************************************************************/ + +/******************************************************************************* + * GLOBAL VARIABLES + ******************************************************************************/ + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC FUNCTION PROTOTYPES + ******************************************************************************/ + +/** + * Create producer task. + */ +void task_producer_create(void); + +#endif // _TASK_PRODUCER_H + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + diff --git a/examples/queue_eventgrp_conn/.vimprj/.indexer_files b/examples/queue_eventgrp_conn/.vimprj/.indexer_files new file mode 100644 index 0000000..ecedcda --- /dev/null +++ b/examples/queue_eventgrp_conn/.vimprj/.indexer_files @@ -0,0 +1,6 @@ + +[queue_eventgrp_conn] +option:ctags_params = "--langmap=c:.c.h --languages=c" + +$INDEXER_PROJECT_ROOT + diff --git a/examples/queue_eventgrp_conn/.vimprj/my.vim b/examples/queue_eventgrp_conn/.vimprj/my.vim new file mode 100644 index 0000000..2e1fc79 --- /dev/null +++ b/examples/queue_eventgrp_conn/.vimprj/my.vim @@ -0,0 +1,68 @@ +" config file for Vim's plugin "vimprj" + +" path to .vimprj folder +let s:sPath = expand(':p:h') + +" set .indexer_file to use +let g:indexer_indexerListFilename = s:sPath.'/.indexer_files' + +let g:indexer_getAllSubdirsFromIndexerListFile = 1 + +" project-specific format settings +let &tabstop = 3 +let &shiftwidth = 3 + +let s:o_dir = $INDEXER_PROJECT_ROOT.'/output/tmp/compiled_in_vim' + +if !isdirectory(s:o_dir) + call mkdir(s:o_dir, "p") +endif + +let g:indexer_handlePath = 0 + +call CheckNeededSymbols( + \ "The following items needs to be defined in your vimfiles/machine_local_conf/current/variables.vim file to make things work: ", + \ "", + \ [ + \ '$MACHINE_LOCAL_CONF__PATH__MICROCHIP_XC32', + \ ] + \ ) + +let s:sCompilerExecutable = '' +if has('win32') || has('win64') + let s:sCompilerExecutable = 'xc32-gcc.exe' +else + let s:sCompilerExecutable = 'xc32-gcc' +endif + +" Path to MPLAB .mcp project +let s:sProject = 'arch/pic32/example_queue_pic32.X' +call envcontrol#set_project_file($INDEXER_PROJECT_ROOT.'/'.s:sProject, 'MPLAB_X', { + \ 'parser_params': { + \ 'compiler_command_without_includes' : '' + \ .'"'.s:sCompilerExecutable.'" ' + \ .' -g -D__DEBUG -x c -c -mprocessor=32MX440F512H -Wall -mips16' + \ .' -Os -MMD -MF "'.s:o_dir.'/%:t:r.o.d" -o "'.s:o_dir.'/%:t:r.o"', + \ }, + \ 'handle_clang' : 1, + \ 'add_paths' : [ + \ $MACHINE_LOCAL_CONF__PATH__MICROCHIP_XC32.'/lib/gcc/pic32mx/4.5.2/include', + \ $MACHINE_LOCAL_CONF__PATH__MICROCHIP_XC32.'/pic32mx/include', + \ ], + \ 'clang_add_params' : '' + \ .' -D __C32__' + \ .' -D __C32_VERSION__=1210' + \ .' -D __CLANG_FOR_COMPLETION__' + \ .' -D __LANGUAGE_C__' + \ .' -D __PIC32MX__' + \ .' -D __32MX440F512H__' + \ .' -D __PIC32_FEATURE_SET__=440' + \ .' -D __DEBUG' + \ .' -Wno-builtin-requires-header' + \ .' -Wno-unused-value' + \ .' -Wno-implicit-function-declaration' + \ .' -Wno-attributes' + \ .' -Wno-invalid-source-encoding' + \ , + \ }) + diff --git a/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/Makefile b/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/Makefile new file mode 100644 index 0000000..fca8e2c --- /dev/null +++ b/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/Makefile @@ -0,0 +1,113 @@ +# +# There exist several targets which are by default empty and which can be +# used for execution of your targets. These targets are usually executed +# before and after some main targets. They are: +# +# .build-pre: called before 'build' target +# .build-post: called after 'build' target +# .clean-pre: called before 'clean' target +# .clean-post: called after 'clean' target +# .clobber-pre: called before 'clobber' target +# .clobber-post: called after 'clobber' target +# .all-pre: called before 'all' target +# .all-post: called after 'all' target +# .help-pre: called before 'help' target +# .help-post: called after 'help' target +# +# Targets beginning with '.' are not intended to be called on their own. +# +# Main targets can be executed directly, and they are: +# +# build build a specific configuration +# clean remove built files from a configuration +# clobber remove all built files +# all build all configurations +# help print help mesage +# +# Targets .build-impl, .clean-impl, .clobber-impl, .all-impl, and +# .help-impl are implemented in nbproject/makefile-impl.mk. +# +# Available make variables: +# +# CND_BASEDIR base directory for relative paths +# CND_DISTDIR default top distribution directory (build artifacts) +# CND_BUILDDIR default top build directory (object files, ...) +# CONF name of current configuration +# CND_ARTIFACT_DIR_${CONF} directory of build artifact (current configuration) +# CND_ARTIFACT_NAME_${CONF} name of build artifact (current configuration) +# CND_ARTIFACT_PATH_${CONF} path to build artifact (current configuration) +# CND_PACKAGE_DIR_${CONF} directory of package (current configuration) +# CND_PACKAGE_NAME_${CONF} name of package (current configuration) +# CND_PACKAGE_PATH_${CONF} path to package (current configuration) +# +# NOCDDL + + +# Environment +MKDIR=mkdir +CP=cp +CCADMIN=CCadmin +RANLIB=ranlib + + +# build +build: .build-post + +.build-pre: +# Add your pre 'build' code here... + +.build-post: .build-impl +# Add your post 'build' code here... + + +# clean +clean: .clean-post + +.clean-pre: +# Add your pre 'clean' code here... +# WARNING: the IDE does not call this target since it takes a long time to +# simply run make. Instead, the IDE removes the configuration directories +# under build and dist directly without calling make. +# This target is left here so people can do a clean when running a clean +# outside the IDE. + +.clean-post: .clean-impl +# Add your post 'clean' code here... + + +# clobber +clobber: .clobber-post + +.clobber-pre: +# Add your pre 'clobber' code here... + +.clobber-post: .clobber-impl +# Add your post 'clobber' code here... + + +# all +all: .all-post + +.all-pre: +# Add your pre 'all' code here... + +.all-post: .all-impl +# Add your post 'all' code here... + + +# help +help: .help-post + +.help-pre: +# Add your pre 'help' code here... + +.help-post: .help-impl +# Add your post 'help' code here... + + + +# include project implementation makefile +include nbproject/Makefile-impl.mk + +# include project make variables +include nbproject/Makefile-variables.mk diff --git a/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/configurations.xml b/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/configurations.xml new file mode 100644 index 0000000..2e8a591 --- /dev/null +++ b/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/configurations.xml @@ -0,0 +1,254 @@ + + + + + + + + + + ../pic32_arch.c + ../queue_example_arch.c + ../../../../../src/arch/pic32/tn_arch_pic32_int_vec1.S + + ../../../queue_example.c + ../../../task_consumer.c + ../../../task_producer.c + + + Makefile + + + + ../../../../common/queue + ../../../../../src/arch/pic32 + ../../.. + + Makefile + + + + localhost + PIC32MX440F512H + + + ICD3PlatformTool + XC32 + 1.21 + 2 + + + + + + + + + + + + false + false + + + + + false + + false + + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/project.properties b/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/project.properties new file mode 100644 index 0000000..e69de29 diff --git a/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/project.xml b/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/project.xml new file mode 100644 index 0000000..c09924e --- /dev/null +++ b/examples/queue_eventgrp_conn/arch/pic32/example_queue_eventgrp_conn_pic32.X/nbproject/project.xml @@ -0,0 +1,18 @@ + + com.microchip.mplab.nbide.embedded.makeproject + + + example_queue_eventgrp_conn_pic32 + e6b3e7cb-7b49-4750-89c9-cf229d92ec7f + 0 + c + + + + UTF-8 + + ../../../../../src/arch/pic32/tneokernel.X + + + + diff --git a/examples/queue_eventgrp_conn/arch/pic32/pic32_arch.c b/examples/queue_eventgrp_conn/arch/pic32/pic32_arch.c new file mode 100644 index 0000000..63efb43 --- /dev/null +++ b/examples/queue_eventgrp_conn/arch/pic32/pic32_arch.c @@ -0,0 +1,4 @@ + +//-- Include common pic32 source for all examples +#include "../../../common/arch/pic32/pic32_arch.c" + diff --git a/examples/queue_eventgrp_conn/arch/pic32/queue_example_arch.c b/examples/queue_eventgrp_conn/arch/pic32/queue_example_arch.c new file mode 100644 index 0000000..d6bb138 --- /dev/null +++ b/examples/queue_eventgrp_conn/arch/pic32/queue_example_arch.c @@ -0,0 +1,94 @@ +/** + * \file + * + * Data queue usage example. + * + * For general information about the pattern, refer to the top of + * queue_example.h file + */ + + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +#include +#include "queue_example_arch.h" +#include "task_consumer.h" + + + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + + +//-- gpio register to work with, you might want to switch it to different port +// NOTE: in real programs, it's better to use some peripheral library. +// I strongly dislike plib as well as "brand new" Harmony by Microchip, +// I use my own peripheral library, but for example such as this one, +// I decided to write it as simple as possible. +#define TRIS_REG TRISE +#define TRIS_REG_CLR TRISECLR +#define TRIS_REG_SET TRISESET +#define TRIS_REG_INV TRISEINV + +#define PORT_REG PORTE +#define PORT_REG_CLR PORTECLR +#define PORT_REG_SET PORTESET +#define PORT_REG_INV PORTEINV + + + + + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +/******************************************************************************* + * PRIVATE FUNCTIONS + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * See comments in the header file + */ +void queue_example_arch_init(void) +{ + //-- set output for needed pins + TRIS_REG_CLR = TASK_CONS_PIN_MASK; + + //-- clear current port value + PORT_REG_CLR = TASK_CONS_PIN_MASK; +} + +/** + * See comments in the header file + */ +void queue_example_arch_pins_toggle(int pin_mask) +{ + PORT_REG_INV = pin_mask; +} + +/** + * See comments in the header file + */ +void queue_example_arch_pins_set(int pin_mask) +{ + PORT_REG_SET = pin_mask; +} + +/** + * See comments in the header file + */ +void queue_example_arch_pins_clear(int pin_mask) +{ + PORT_REG_CLR = pin_mask; +} + + diff --git a/examples/queue_eventgrp_conn/arch/pic32/queue_example_arch.h b/examples/queue_eventgrp_conn/arch/pic32/queue_example_arch.h new file mode 100644 index 0000000..657b5d1 --- /dev/null +++ b/examples/queue_eventgrp_conn/arch/pic32/queue_example_arch.h @@ -0,0 +1,36 @@ + +#ifndef _QUEUE_EXAMPLE_ARCH_H +#define _QUEUE_EXAMPLE_ARCH_H + + + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +//-- Include common pic32 header for all examples +#include "../../../common/arch/pic32/example_arch.h" + +/** + * At least, we need to initialize GPIO pins here + */ +void queue_example_arch_init(void); + +/** + * Toggle pins specified by pin_mask + */ +void queue_example_arch_pins_toggle(int pin_mask); + +/** + * Set pins specified by pin_mask + */ +void queue_example_arch_pins_set(int pin_mask); + +/** + * Clear pins specified by pin_mask + */ +void queue_example_arch_pins_clear(int pin_mask); + + +#endif // _QUEUE_EXAMPLE_ARCH_H + diff --git a/examples/queue_eventgrp_conn/queue_example.c b/examples/queue_eventgrp_conn/queue_example.c new file mode 100644 index 0000000..b8661eb --- /dev/null +++ b/examples/queue_eventgrp_conn/queue_example.c @@ -0,0 +1,69 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +#include "queue_example.h" + +#include "task_consumer.h" +#include "task_producer.h" + +#include "tn.h" + + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +static struct TN_EventGrp que_example_events; + + + + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * Each example should define this funtion: it creates first application task + */ +void init_task_create(void) +{ + task_producer_create(); +} + + +/** + * See comments in the header file + */ +void queue_example_init(void) +{ + //-- create application events + // (see enum E_QueExampleFlag in the header) + SYSRETVAL_CHECK(tn_eventgrp_create(&que_example_events, (0))); + + //-- init architecture-dependent stuff + queue_example_arch_init(); +} + +/** + * See comments in the header file + */ +struct TN_EventGrp *queue_example_eventgrp_get(void) +{ + return &que_example_events; +} + +/******************************************************************************* + * end of file + ******************************************************************************/ + + + diff --git a/examples/queue_eventgrp_conn/queue_example.h b/examples/queue_eventgrp_conn/queue_example.h new file mode 100644 index 0000000..2d6fc95 --- /dev/null +++ b/examples/queue_eventgrp_conn/queue_example.h @@ -0,0 +1,152 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + +#ifndef _QUEUE_EXAMPLE_H +#define _QUEUE_EXAMPLE_H + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +//-- include architecture-specific things, +// at least, there is SOFTWARE_BREAK macro +#include "queue_example_arch.h" + + +/******************************************************************************* + * EXTERN TYPES + ******************************************************************************/ + +/// Application common event group (see `enum E_QueExampleFlag`) +struct TN_EventGrp; + + + +/******************************************************************************* + * PUBLIC TYPES + ******************************************************************************/ + +enum E_QueExampleFlag { + /// + /// Flag indicating that consumer task is initialized + QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT = (1 << 0), + /// + /// Flag indicating that producer task is initialized + QUE_EXAMPLE_FLAG__TASK_PRODUCER_INIT = (1 << 1), + /// + /// Flag indicating that consumer's queue A is not empty + QUE_EXAMPLE_FLAG__MSG_A = (1 << 2), + /// + /// Flag indicating that consumer's queue B is not empty + QUE_EXAMPLE_FLAG__MSG_B = (1 << 3), +}; + + + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +/** + * Macro for checking value returned from system service. + * + * If you ignore the value returned by any system service, it is almost always + * a BAD idea: if something goes wrong, the sooner you know it, the better. + * But, checking error for every system service is a kind of annoying, so, + * simple macro was invented for that. + * + * In most situations, any values other than `#TN_RC_OK` or `#TN_RC_TIMEOUT` + * are not allowed (i.e. should never happen in normal device operation). + * + * So, if error value is neither `#TN_RC_OK` nor `#TN_RC_TIMEOUT`, this macro + * issues a software break. + * + * If you need to allow `#TN_RC_OK` only, use `SYSRETVAL_CHECK()` instead (see + * below) + * + * Usage is as follows: + * + * \code{.c} + * enum TN_RCode rc + * = SYSRETVAL_CHECK_TO(tn_queue_send(&my_queue, p_data, MY_TIMEOUT)); + * + * switch (rc){ + * case TN_RC_OK: + * //-- handle successfull operation + * break; + * case TN_RC_TIMEOUT: + * //-- handle timeout + * break; + * default: + * //-- should never be here + * break; + * } + * \endcode + * + */ +#define SYSRETVAL_CHECK_TO(x) \ + ({ \ + int __rv = (x); \ + if (__rv != TN_RC_OK && __rv != TN_RC_TIMEOUT){ \ + SOFTWARE_BREAK(); \ + } \ + /* like, return __rv */ \ + __rv; \ + }) + +/** + * The same as `SYSRETVAL_CHECK_TO()`, but it allows `#TN_RC_OK` only. + * + * Since there is only one return code allowed, usage is simple: + * + * \code{.c} + * SYSRETVAL_CHECK(tn_queue_send(&my_queue, p_data, MY_TIMEOUT)); + * \endcode + */ +#define SYSRETVAL_CHECK(x) \ + ({ \ + int __rv = (x); \ + if (__rv != TN_RC_OK){ \ + SOFTWARE_BREAK(); \ + } \ + /* like, return __rv */ \ + __rv; \ + }) + + + +/******************************************************************************* + * PUBLIC FUNCTION PROTOTYPES + ******************************************************************************/ + +/** + * Each example should define this funtion: it creates first application task + */ +void init_task_create(void); + +/** + * Initialization of application common objects, must be called once + * from the initial application task (which is created in init_task_create()) + */ +void queue_example_init(void); + +/** + * Returns pointer to the application event group. + * See `enum E_QueExampleFlag` + * + * Do note that you must call `queue_example_init()` before + * you can get eventgrp. + */ +struct TN_EventGrp *queue_example_eventgrp_get(void); + +#endif // _QUEUE_EXAMPLE_H + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + diff --git a/examples/queue_eventgrp_conn/readme.txt b/examples/queue_eventgrp_conn/readme.txt new file mode 100644 index 0000000..1df4a68 --- /dev/null +++ b/examples/queue_eventgrp_conn/readme.txt @@ -0,0 +1,37 @@ + +This is an usage example of waiting for messages from multiple queues +at once in TNeoKernel. + +This example is similar to the plain "queue" example (which you might +want to examine before this one), but in this project, task_consumer +waits for messages from two queues at a time. + +To this end, TNeoKernel offers a way to connect the event group to +another kernel object (currently, only to queue). When an event group +with specified flag pattern is connected to the queue, the queue maintains +specified flag pattern automatically: if queue contains is non-empty, +the flag is set, if queue becomes empty, the flag is clear. + +Please note that there's no need to clear the flag manually: queue +maintains it completely. + +So in this example project, task_producer creates and starts kernel timer as a +part of its initialization routine. Timer fires after 750 system ticks, and +timer callback sends the message to the second queue and restarts timer again. + +task_consumer waits for one of the two flags, each flag indicates whether +appropriate queue is non-empty. When either queue becomes non-empty, flag +is automatically set, so task_consumer wakes up, checks which flag is +set, receives message from the appropriate queue and handles it. + +In this example project, the message from the second queue could just make +task_consumer clear all the LEDs or, conversely, set them all at once. + +Event group connection is a perfectly fine solution. If the kernel doesn't +offer a mechanism for that, programmer usually have to use polling services on +these queues and sleep for a few system ticks. Obviously, this approach has +serious drawbacks: we have a lot of useless context switches, and response for +the message gets much slower. Actually, we lost the main goal of the preemtive +kernel when we use polling services like that. + + diff --git a/examples/queue_eventgrp_conn/task_consumer.c b/examples/queue_eventgrp_conn/task_consumer.c new file mode 100644 index 0000000..62720f5 --- /dev/null +++ b/examples/queue_eventgrp_conn/task_consumer.c @@ -0,0 +1,412 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +#include "task_consumer.h" +#include "task_producer.h" +#include "queue_example.h" +#include "tn.h" + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +//-- task stack size +#define TASK_CONSUMER_STACK_SIZE (TN_MIN_STACK_SIZE + 96) + +//-- priority of consumer task: the highest one +#define TASK_CONSUMER_PRIORITY 0 + +//-- number of items in the consumer message queue A +#define CONS_QUE_A_BUF_SIZE 4 + +//-- number of items in the consumer message queue B +#define CONS_QUE_B_BUF_SIZE 2 + +//-- max timeout for waiting for memory and free message +#define WAIT_TIMEOUT 10 + + + + + +/******************************************************************************* + * PRIVATE FUNCTION PROTOTYPES + ******************************************************************************/ + +/******************************************************************************* + * PRIVATE TYPES + ******************************************************************************/ + +/** + * Type of message that consumer receives. + */ +struct TaskConsumerMsgA { + enum E_TaskConsCmd cmd; //-- command to perform + // (well, now there's just one command) + enum E_TaskConsPin pin_num; //-- number of pin for which command + // should be performed +}; + +struct TaskConsumerMsgB { + int bool_on; //-- whether all pins should be turned on or off +}; + + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +//-- define array for task stack +TN_STACK_ARR_DEF(task_consumer_stack, TASK_CONSUMER_STACK_SIZE); + +//-- task descriptor: it's better to explicitly zero it +static struct TN_Task task_consumer = {}; + +//-- define queue A and buffer for it +struct TN_DQueue cons_que_a; +void *cons_que_a_buf[ CONS_QUE_A_BUF_SIZE ]; + +//-- define fixed memory pool for queue A and buffer for it +struct TN_FMem cons_fmem_a; +TN_FMEM_BUF_DEF(cons_fmem_a_buf, struct TaskConsumerMsgA, CONS_QUE_A_BUF_SIZE); + +//-- define queue B and buffer for it +struct TN_DQueue cons_que_b; +void *cons_que_b_buf[ CONS_QUE_B_BUF_SIZE ]; + +//-- define fixed memory pool for queue B and buffer for it +struct TN_FMem cons_fmem_b; +TN_FMEM_BUF_DEF(cons_fmem_b_buf, struct TaskConsumerMsgB, CONS_QUE_B_BUF_SIZE); + + + + + +/******************************************************************************* + * PUBLIC DATA + ******************************************************************************/ + +/******************************************************************************* + * EXTERNAL DATA + ******************************************************************************/ + +/******************************************************************************* + * EXTERNAL FUNCTION PROTOTYPES + ******************************************************************************/ + +/******************************************************************************* + * PRIVATE FUNCTIONS + ******************************************************************************/ + +static void msg_a_handle(struct TaskConsumerMsgA *p_msg) +{ + //-- message successfully received, let's check command + switch (p_msg->cmd){ + + case TASK_CONS_CMD__PIN_TOGGLE: + //-- toggle specified bit + queue_example_arch_pins_toggle(1 << p_msg->pin_num); + break; + + default: + //-- should never be here + SOFTWARE_BREAK(); + break; + } +} + +static void msg_b_handle(struct TaskConsumerMsgB *p_msg) +{ + //-- message successfully received, let's check command + if (p_msg->bool_on){ + queue_example_arch_pins_set(TASK_CONS_PIN_MASK); + } else { + queue_example_arch_pins_clear(TASK_CONS_PIN_MASK); + } +} + +static void task_consumer_body(void *par) +{ + //-- init things specific to consumer task + + //-- init queue A {{{ + //-- create memory pool for queue A + SYSRETVAL_CHECK( + tn_fmem_create( + &cons_fmem_a, + cons_fmem_a_buf, + TN_MAKE_ALIG_SIZE(sizeof(struct TaskConsumerMsgA)), + CONS_QUE_A_BUF_SIZE + ) + ); + + //-- create queue A + SYSRETVAL_CHECK( + tn_queue_create(&cons_que_a, (void *)cons_que_a_buf, CONS_QUE_A_BUF_SIZE) + ); + // }}} + + //-- init queue B {{{ + //-- create memory pool for queue B + SYSRETVAL_CHECK( + tn_fmem_create( + &cons_fmem_b, + cons_fmem_b_buf, + TN_MAKE_ALIG_SIZE(sizeof(struct TaskConsumerMsgB)), + CONS_QUE_B_BUF_SIZE + ) + ); + + //-- create queue B + SYSRETVAL_CHECK( + tn_queue_create(&cons_que_b, (void *)cons_que_b_buf, CONS_QUE_B_BUF_SIZE) + ); + // }}} + + + //-- connect application's common event group to both queues with + // appropriate flags + SYSRETVAL_CHECK( + tn_queue_eventgrp_connect( + &cons_que_a, + queue_example_eventgrp_get(), + QUE_EXAMPLE_FLAG__MSG_A + ) + ); + + SYSRETVAL_CHECK( + tn_queue_eventgrp_connect( + &cons_que_b, + queue_example_eventgrp_get(), + QUE_EXAMPLE_FLAG__MSG_B + ) + ); + + + + //-- cry that consumer task has initialized + SYSRETVAL_CHECK( + tn_eventgrp_modify( + queue_example_eventgrp_get(), + TN_EVENTGRP_OP_SET, + QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT + ) + ); + + //-- enter endless loop in which we will receive and handle messages + for (;;) + { + //-- flags pattern that is filled by tn_eventgrp_wait() when event happens + TN_UWord flags_pattern = 0; + + //-- wait for messages (i.e. for connected flags), potentially can wait forever + // (if there are no messages) + enum TN_RCode rc = SYSRETVAL_CHECK( + tn_eventgrp_wait( + queue_example_eventgrp_get(), + (QUE_EXAMPLE_FLAG__MSG_A | QUE_EXAMPLE_FLAG__MSG_B), + TN_EVENTGRP_WMODE_OR, + &flags_pattern, + TN_WAIT_INFINITE + ) + ); + + if (rc == TN_RC_OK){ + //-- At least, one of the queues contains new message. + // Let's check which flags are set and receive message + // from appropriate queue. + // + // We specify zero timeout here, because message + // should be already available: there aren't other + // customers that could "steal" that message from this task. + + if (flags_pattern & QUE_EXAMPLE_FLAG__MSG_A){ + //-- message A is ready to receive, let's receive it. + struct TaskConsumerMsgA *p_msg_a; + rc = SYSRETVAL_CHECK( + tn_queue_receive(&cons_que_a, (void *)&p_msg_a, 0) + ); + if (rc == TN_RC_OK){ + //-- handle the message + msg_a_handle(p_msg_a); + + //-- free memory + SYSRETVAL_CHECK(tn_fmem_release(&cons_fmem_a, (void *)p_msg_a)); + } else { + //-- failed to receive message: should never be here + SOFTWARE_BREAK(); + } + + } + + if (flags_pattern & QUE_EXAMPLE_FLAG__MSG_B){ + //-- message B is ready to receive, let's receive it. + struct TaskConsumerMsgB *p_msg_b; + rc = SYSRETVAL_CHECK( + tn_queue_receive(&cons_que_b, (void *)&p_msg_b, 0) + ); + if (rc == TN_RC_OK){ + //-- handle the message + msg_b_handle(p_msg_b); + + //-- free memory + SYSRETVAL_CHECK(tn_fmem_release(&cons_fmem_b, (void *)p_msg_b)); + } else { + //-- failed to receive message: should never be here + SOFTWARE_BREAK(); + } + + } + } else { + //-- failed to wait for events: should never be here, + // since we wait infinitely + SOFTWARE_BREAK(); + } + + } +} + + + + + + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * See comments in the header file + */ +void task_consumer_create(void) +{ + + SYSRETVAL_CHECK( + tn_task_create( + &task_consumer, + task_consumer_body, + TASK_CONSUMER_PRIORITY, + task_consumer_stack, + TASK_CONSUMER_STACK_SIZE, + NULL, + (TN_TASK_CREATE_OPT_START) + ) + ); + +} + +/** + * See comments in the header file + */ +int task_consumer_msg_a_send(enum E_TaskConsCmd cmd, enum E_TaskConsPin pin_num) +{ + int ret = 0; + + struct TaskConsumerMsgA *p_msg; + enum TN_RCode tn_rc; + + //-- get memory block from memory pool + tn_rc = SYSRETVAL_CHECK_TO( + tn_is_task_context() + ? tn_fmem_get(&cons_fmem_a, (void *)&p_msg, WAIT_TIMEOUT) + : tn_fmem_iget_polling(&cons_fmem_a, (void *)&p_msg) + ); + if (tn_rc == TN_RC_OK){ + + //-- put correct data to the allocated memory + p_msg->cmd = cmd; + p_msg->pin_num = pin_num; + + //-- send it to the consumer task + tn_rc = SYSRETVAL_CHECK_TO( + tn_is_task_context() + ? tn_queue_send(&cons_que_a, (void *)p_msg, WAIT_TIMEOUT) + : tn_queue_isend_polling(&cons_que_a, (void *)p_msg) + ); + + if (tn_rc == TN_RC_OK){ + ret = 1/*success*/; + } else { + //-- there was some error while sending the message, + // so, we should free buffer that we've allocated + SYSRETVAL_CHECK( + tn_is_task_context() + ? tn_fmem_release(&cons_fmem_a, (void *)p_msg) + : tn_fmem_irelease(&cons_fmem_a, (void *)p_msg) + ); + } + } else { + //-- there was some error while allocating memory, + // nothing to do here: ret is still 0, and it is + // going to be returned + } + + return ret; +} + +/** + * See comments in the header file + */ +int task_consumer_msg_b_send(int bool_on) +{ + int ret = 0; + + struct TaskConsumerMsgB *p_msg; + enum TN_RCode tn_rc; + + //-- get memory block from memory pool + tn_rc = SYSRETVAL_CHECK_TO( + tn_is_task_context() + ? tn_fmem_get(&cons_fmem_b, (void *)&p_msg, WAIT_TIMEOUT) + : tn_fmem_iget_polling(&cons_fmem_b, (void *)&p_msg) + ); + if (tn_rc == TN_RC_OK){ + + //-- put correct data to the allocated memory + p_msg->bool_on = bool_on; + + //-- send it to the consumer task + tn_rc = SYSRETVAL_CHECK_TO( + tn_is_task_context() + ? tn_queue_send(&cons_que_b, (void *)p_msg, WAIT_TIMEOUT) + : tn_queue_isend_polling(&cons_que_b, (void *)p_msg) + ); + + if (tn_rc == TN_RC_OK){ + ret = 1/*success*/; + } else { + //-- there was some error while sending the message, + // so, we should free buffer that we've allocated + SYSRETVAL_CHECK( + tn_is_task_context() + ? tn_fmem_release(&cons_fmem_b, (void *)p_msg) + : tn_fmem_irelease(&cons_fmem_b, (void *)p_msg) + ); + } + } else { + //-- there was some error while allocating memory, + // nothing to do here: ret is still 0, and it is + // going to be returned + } + + return ret; +} + + + + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + + diff --git a/examples/queue_eventgrp_conn/task_consumer.h b/examples/queue_eventgrp_conn/task_consumer.h new file mode 100644 index 0000000..06b2454 --- /dev/null +++ b/examples/queue_eventgrp_conn/task_consumer.h @@ -0,0 +1,81 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +#ifndef _TASK_CONSUMER_H +#define _TASK_CONSUMER_H + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC TYPES + ******************************************************************************/ + +enum E_TaskConsCmd { + TASK_CONS_CMD__PIN_TOGGLE, +}; + +enum E_TaskConsPin { + TASK_CONS_PIN__0, + TASK_CONS_PIN__1, + TASK_CONS_PIN__2, +}; + +#define TASK_CONS_PIN_MASK (0 \ + | (1 << TASK_CONS_PIN__0) \ + | (1 << TASK_CONS_PIN__1) \ + | (1 << TASK_CONS_PIN__2) \ + ) + +/******************************************************************************* + * GLOBAL VARIABLES + ******************************************************************************/ + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + + +/******************************************************************************* + * PUBLIC FUNCTION PROTOTYPES + ******************************************************************************/ + +/** + * Create consumer task. + */ +void task_consumer_create(void); + + +/** + * Send a message to the consumer. + * In this simple example, consumer will just toggle given pin number. + * + * This function can be called from either task or ISR. + * + * @param pin_num + * pin number to toggle. + * + * @return + * - 1 on success + * - 0 on failure + */ +int task_consumer_msg_a_send(enum E_TaskConsCmd cmd, enum E_TaskConsPin pin_num); + +/** + * TODO + */ +int task_consumer_msg_b_send(int bool_on); + +#endif // _TASK_CONSUMER_H + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + diff --git a/examples/queue_eventgrp_conn/task_producer.c b/examples/queue_eventgrp_conn/task_producer.c new file mode 100644 index 0000000..594158f --- /dev/null +++ b/examples/queue_eventgrp_conn/task_producer.c @@ -0,0 +1,194 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +#include "task_producer.h" +#include "task_consumer.h" +#include "queue_example.h" +#include "tn.h" + + + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +//-- task stack size +#define TASK_PRODUCER_STACK_SIZE (TN_MIN_STACK_SIZE + 96) + +//-- highest priority +#define TASK_PRODUCER_PRIORITY 0 + +//-- timer period, in system ticks +#define MY_TIMER_PERIOD 750 + + +/******************************************************************************* + * PRIVATE DATA + ******************************************************************************/ + +//-- define array for task stack +TN_STACK_ARR_DEF(task_producer_stack, TASK_PRODUCER_STACK_SIZE); + +//-- task descriptor: it's better to explicitly zero it +static struct TN_Task task_producer = {}; + +static struct TN_Timer my_timer = {}; + + +/******************************************************************************* + * PRIVATE FUNCTIONS + ******************************************************************************/ + +/** + * Callback function called by kernel timer (that is, from ISR context) + */ +static void my_timer_callback(struct TN_Timer *timer, void *p_user_data) +{ + //-- restart timer again + tn_timer_start(&my_timer, MY_TIMER_PERIOD); + + //-- whether we should turn all LEDs on or off + static int bool_all_on = 0; + bool_all_on = !bool_all_on; + + if (!task_consumer_msg_b_send(bool_all_on)){ + //-- failed to send the message + // in this partucilar application, it should never happen, + // so, halt the debugger + SOFTWARE_BREAK(); + } +} + +/** + * Application init: called from the first created application task + */ +static void appl_init(void) +{ + //-- init common application objects + queue_example_init(); + + //-- create all the rest application tasks: + + //-- create the consumer task {{{ + { + task_consumer_create(); + + //-- wait until consumer task initialized + SYSRETVAL_CHECK( + tn_eventgrp_wait( + queue_example_eventgrp_get(), + QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT, + TN_EVENTGRP_WMODE_AND, + NULL, + TN_WAIT_INFINITE + ) + ); + } + // }}} + + //-- create and start timer for sending message B {{{ + { + tn_timer_create(&my_timer, my_timer_callback, NULL); + tn_timer_start(&my_timer, MY_TIMER_PERIOD); + } + //}}} + +} + +/** + * Body function for producer task + */ +static void task_producer_body(void *par) +{ + //-- in this particular application, producer task is the first application + // task that is started, so, we should perform all the app initialization + // here, and then start other tasks. All of this is done in the appl_init(). + appl_init(); + + //-- cry that producer task has initialized + SYSRETVAL_CHECK( + tn_eventgrp_modify( + queue_example_eventgrp_get(), + TN_EVENTGRP_OP_SET, + QUE_EXAMPLE_FLAG__TASK_PRODUCER_INIT + ) + ); + + //-- at this point, application is completely initialized, and we can + // get to business: enter endless loop and repeatedly send + // messages to the consumer + for (;;) + { + int i; + + for (i = 0; i < 3/*pins count*/;i++){ + //-- Wait before sending message + SYSRETVAL_CHECK_TO( tn_task_sleep(100) ); + + enum E_TaskConsPin pin_num = 0; + + //-- determine pin_num + switch (i){ + case 0: + pin_num = TASK_CONS_PIN__0; + break; + case 1: + pin_num = TASK_CONS_PIN__1; + break; + case 2: + pin_num = TASK_CONS_PIN__2; + break; + default: + //-- should never be here + SOFTWARE_BREAK(); + break; + } + + //-- Send the message to consumer + if (!task_consumer_msg_a_send( + TASK_CONS_CMD__PIN_TOGGLE, + pin_num + ) + ) + { + //-- failed to send the message + // in this partucilar application, it should never happen, + // so, halt the debugger + SOFTWARE_BREAK(); + } + } + + } +} + + + +/******************************************************************************* + * PUBLIC FUNCTIONS + ******************************************************************************/ + +/** + * See comments in the header file + */ +void task_producer_create(void) +{ + + SYSRETVAL_CHECK( + tn_task_create( + &task_producer, + task_producer_body, + TASK_PRODUCER_PRIORITY, + task_producer_stack, + TASK_PRODUCER_STACK_SIZE, + NULL, + (TN_TASK_CREATE_OPT_START) + ) + ); + +} + + diff --git a/examples/queue_eventgrp_conn/task_producer.h b/examples/queue_eventgrp_conn/task_producer.h new file mode 100644 index 0000000..43e8ba0 --- /dev/null +++ b/examples/queue_eventgrp_conn/task_producer.h @@ -0,0 +1,43 @@ +/** + * \file + * + * Example project that demonstrates usage of queues in TNeoKernel. + */ + + +#ifndef _TASK_PRODUCER_H +#define _TASK_PRODUCER_H + +/******************************************************************************* + * INCLUDED FILES + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC TYPES + ******************************************************************************/ + +/******************************************************************************* + * GLOBAL VARIABLES + ******************************************************************************/ + +/******************************************************************************* + * DEFINITIONS + ******************************************************************************/ + +/******************************************************************************* + * PUBLIC FUNCTION PROTOTYPES + ******************************************************************************/ + +/** + * Create producer task. + */ +void task_producer_create(void); + +#endif // _TASK_PRODUCER_H + + +/******************************************************************************* + * end of file + ******************************************************************************/ + + diff --git a/src/arch/example/tn_arch_example.h b/src/arch/example/tn_arch_example.h index b861c35..f0960e8 100644 --- a/src/arch/example/tn_arch_example.h +++ b/src/arch/example/tn_arch_example.h @@ -46,6 +46,7 @@ /** * FFS - find first set bit. Used in `_find_next_task_to_run()` function. + * Say, for `0xa8` it should return `3`. * * May be not defined: in this case, naive algorithm will be used. */ @@ -103,13 +104,12 @@ typedef unsigned int TN_UWord; /** - * Number of priorities available, this value usually matches `#TN_INT_WIDTH`. - * For compatibility with all platforms, it's recommended to use only values - * from 1 to 14, inclusive. + * Maximum number of priorities available, this value usually matches + * `#TN_INT_WIDTH`. * - * @see `#TN_INT_WIDTH` + * @see TN_PRIORITIES_CNT */ -#define TN_PRIORITIES_CNT TN_INT_WIDTH +#define TN_PRIORITIES_MAX_CNT TN_INT_WIDTH /** * Value for infinite waiting, usually matches `UINT_MAX` @@ -117,7 +117,7 @@ typedef unsigned int TN_UWord; #define TN_WAIT_INFINITE 0xFFFFFFFF /** - * Value for initializing the task's stack + * Value for initializing the unused space of task's stack */ #define TN_FILL_STACK_VAL 0xFEEDFACE @@ -189,6 +189,11 @@ typedef unsigned int TN_UWord; */ #define TN_IS_INT_DISABLED() ((__builtin_mfc0(12, 0) & 1) == 0) +/** + * Pend context switch from interrupt. + */ +#define _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED() \ + _tn_context_switch_pend_if_needed() diff --git a/src/arch/pic32/tn_arch_pic32.c b/src/arch/pic32/tn_arch_pic32.c index 6bb3576..d15c942 100644 --- a/src/arch/pic32/tn_arch_pic32.c +++ b/src/arch/pic32/tn_arch_pic32.c @@ -38,6 +38,27 @@ extern unsigned long _gp; +/** + * Self-check for the application that uses TNeoKernel: + * + * PIC32 application must include the file + * src/arch/pic32/tn_arch_pic32_int_vec1.S to the main project, + * in order to dispatch vector_1 correctly, but if it is forgotten, + * no error is generated at the build time: we just get to the + * _DefaultInterrupt when we should switch context. + * + * Note that we can't include that file to the TNeoKernel library + * project: it doesn't work. + * + * So, dummy function was invented, and if we forgot to + * include that file, we got an error at the link time. + * + * That function merely returns 0. + */ +extern int +_you_should_add_file___tn_arch_pic32_int_vec1_S___to_the_project(void); + + //---------------------------------------------------------------------------- // Context layout // @@ -83,55 +104,69 @@ TN_UWord *_tn_arch_stack_top_get( return stack_low_address + stack_size; } - //---------------------------------------------------------------------------- // Processor specific routine - here for MIPS4K // // sizeof(void*) = sizeof(int) //---------------------------------------------------------------------------- -unsigned int *_tn_arch_stack_init( - TN_TaskBody *task_func, - TN_UWord *stack_top, - void *param +TN_UWord *_tn_arch_stack_init( + TN_TaskBody *task_func, + TN_UWord *stack_top, + void *param ) { + //-- if you got "undefined reference" error here, it means that you + // forgot to include the file + // + // /src/arch/pic32/tn_arch_pic32_int_vec1.S + // + // to the main project. + // This requirement is stated in the documentation, here: + // + // http://dfrank.bitbucket.org/tneokernel_api/latest/html/pic32_details.html + // + //-- see comments above of the following function for a bit more details: + _you_should_add_file___tn_arch_pic32_int_vec1_S___to_the_project(); + + + //-- filling register's position in the stack - for debugging only *(--stack_top) = 0; //-- ABI argument area *(--stack_top) = 0; *(--stack_top) = 0; *(--stack_top) = 0; - *(--stack_top) = (unsigned int)task_func; //-- EPC - *(--stack_top) = 3; //-- Status: EXL and IE bits are set - *(--stack_top) = (unsigned int)tn_task_exit; //-- ra - *(--stack_top) = 0x30303030L; //-- fp - *(--stack_top) = (unsigned int)&_gp; //-- gp - provided by linker - *(--stack_top) = 0x25252525L; //-- t9 - *(--stack_top) = 0x24242424L; //-- t8 - *(--stack_top) = 0x23232323L; //-- s7 - *(--stack_top) = 0x22222222L; //-- s6 - *(--stack_top) = 0x21212121L; //-- s5 - *(--stack_top) = 0x20202020L; //-- s4 - *(--stack_top) = 0x19191919L; //-- s3 - *(--stack_top) = 0x18181818L; //-- s2 - *(--stack_top) = 0x17171717L; //-- s1 - *(--stack_top) = 0x16161616L; //-- s0 - *(--stack_top) = 0x15151515L; //-- t7 - *(--stack_top) = 0x14141414L; //-- t6 - *(--stack_top) = 0x13131313L; //-- t5 - *(--stack_top) = 0x12121212L; //-- t4 - *(--stack_top) = 0x11111111L; //-- t3 - *(--stack_top) = 0x10101010L; //-- t2 - *(--stack_top) = 0x09090909L; //-- t1 - *(--stack_top) = 0x08080808L; //-- t0 - *(--stack_top) = 0x07070707L; //-- a3 - *(--stack_top) = 0x06060606L; //-- a2 - *(--stack_top) = 0x05050505L; //-- a1 - *(--stack_top) = (unsigned int)param; //-- a0 - task's function argument - *(--stack_top) = 0x03030303L; //-- v1 - *(--stack_top) = 0x02020202L; //-- v0 - *(--stack_top) = 0x01010101L; //-- at - *(--stack_top) = 0x33333333L; //-- hi - *(--stack_top) = 0x32323232L; //-- lo + *(--stack_top) = (TN_UWord)task_func; //-- EPC + *(--stack_top) = 3; //-- Status: EXL and IE bits are set + *(--stack_top) = (TN_UWord)tn_task_exit; //-- ra + *(--stack_top) = 0x30303030L; //-- fp + *(--stack_top) = (TN_UWord)&_gp; //-- gp - provided by linker + *(--stack_top) = 0x25252525L; //-- t9 + *(--stack_top) = 0x24242424L; //-- t8 + *(--stack_top) = 0x23232323L; //-- s7 + *(--stack_top) = 0x22222222L; //-- s6 + *(--stack_top) = 0x21212121L; //-- s5 + *(--stack_top) = 0x20202020L; //-- s4 + *(--stack_top) = 0x19191919L; //-- s3 + *(--stack_top) = 0x18181818L; //-- s2 + *(--stack_top) = 0x17171717L; //-- s1 + *(--stack_top) = 0x16161616L; //-- s0 + *(--stack_top) = 0x15151515L; //-- t7 + *(--stack_top) = 0x14141414L; //-- t6 + *(--stack_top) = 0x13131313L; //-- t5 + *(--stack_top) = 0x12121212L; //-- t4 + *(--stack_top) = 0x11111111L; //-- t3 + *(--stack_top) = 0x10101010L; //-- t2 + *(--stack_top) = 0x09090909L; //-- t1 + *(--stack_top) = 0x08080808L; //-- t0 + *(--stack_top) = 0x07070707L; //-- a3 + *(--stack_top) = 0x06060606L; //-- a2 + *(--stack_top) = 0x05050505L; //-- a1 + *(--stack_top) = (TN_UWord)param; //-- a0 - task's function argument + *(--stack_top) = 0x03030303L; //-- v1 + *(--stack_top) = 0x02020202L; //-- v0 + *(--stack_top) = 0x01010101L; //-- at + *(--stack_top) = 0x33333333L; //-- hi + *(--stack_top) = 0x32323232L; //-- lo return stack_top; } diff --git a/src/arch/pic32/tn_arch_pic32.h b/src/arch/pic32/tn_arch_pic32.h index 682cf33..4623517 100644 --- a/src/arch/pic32/tn_arch_pic32.h +++ b/src/arch/pic32/tn_arch_pic32.h @@ -71,6 +71,7 @@ extern "C" { /*}*/ /** * FFS - find first set bit. Used in `_find_next_task_to_run()` function. + * Say, for `0xa8` it should return `3`. * * May be not defined: in this case, naive algorithm will be used. */ @@ -136,11 +137,12 @@ typedef unsigned int TN_UWord; /** - * Number of priorities available, this value usually matches `#TN_INT_WIDTH`. - * For compatibility with all platforms, it's recommended to use only values - * from 1 to 14, inclusive. + * Maximum number of priorities available, this value usually matches + * `#TN_INT_WIDTH`. + * + * @see TN_PRIORITIES_CNT */ -#define TN_PRIORITIES_CNT TN_INT_WIDTH +#define TN_PRIORITIES_MAX_CNT TN_INT_WIDTH /** * Value for infinite waiting, usually matches `UINT_MAX` @@ -241,6 +243,13 @@ typedef unsigned int TN_UWord; */ #define TN_IS_INT_DISABLED() ((__builtin_mfc0(12, 0) & 1) == 0) +/** + * Pend context switch from interrupt. + */ +#define _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED() \ + _tn_context_switch_pend_if_needed() + + #endif //-- DOXYGEN_SHOULD_SKIP_THIS @@ -344,17 +353,6 @@ void __attribute__((naked, nomips16)) \ asm volatile("jalr $t0"); \ asm volatile("sw $v1, 0($sp)"); \ \ - /* Pend context switch if needed */ \ - asm volatile("lw $t0, tn_curr_run_task"); \ - asm volatile("lw $t1, tn_next_task_to_run"); \ - asm volatile("lw $t0, 0($t0)"); \ - asm volatile("lw $t1, 0($t1)"); \ - asm volatile("lui $t2, %hi(IFS0SET)"); \ - asm volatile("beq $t0, $t1, 1f"); \ - asm volatile("ori $t1, $zero, 2"); \ - asm volatile("sw $t1, %lo(IFS0SET)($t2)"); \ - \ - asm volatile("1:"); \ /* Restore registers */ \ asm volatile("lw $v1, 0($sp)"); \ asm volatile("lw $v0, 4($sp)"); \ @@ -486,17 +484,6 @@ void __attribute__((naked, nomips16)) \ asm volatile("jalr $t0"); \ asm volatile("sw $v1, 0($sp)"); \ \ - /* Pend context switch if needed */ \ - asm volatile("lw $t0, tn_curr_run_task"); \ - asm volatile("lw $t1, tn_next_task_to_run"); \ - asm volatile("lw $t0, 0($t0)"); \ - asm volatile("lw $t1, 0($t1)"); \ - asm volatile("lui $t2, %hi(IFS0SET)"); \ - asm volatile("beq $t0, $t1, 1f"); \ - asm volatile("ori $t1, $zero, 2"); \ - asm volatile("sw $t1, %lo(IFS0SET)($t2)"); \ - \ - asm volatile("1:"); \ /* Restore registers */ \ asm volatile("lw $v1, 0($sp)"); \ asm volatile("lw $v0, 4($sp)"); \ diff --git a/src/arch/pic32/tn_arch_pic32_int_vec1.S b/src/arch/pic32/tn_arch_pic32_int_vec1.S index 6dbc45d..3205bbd 100644 --- a/src/arch/pic32/tn_arch_pic32_int_vec1.S +++ b/src/arch/pic32/tn_arch_pic32_int_vec1.S @@ -34,6 +34,49 @@ * ******************************************************************************/ +/** + * \file + * + * This file contains ISR for core software interrupt 0 (which is used by the + * kernel internally). + * + * **NOTE:** This file must be included in the main project itself, not in the + * library project. + * + */ + + + +/* + * Self-check for the application that uses TNeoKernel: + * + * PIC32 application must include the file tn_arch_pic32_int_vec1.S + * in order to dispatch vector_1 correctly, but if it is forgotten, + * no error is generated at the build time: we just get to the + * _DefaultInterrupt when we should switch context. + * + * Note that we can't include that file to the TNeoKernel library + * project: it doesn't work. + * + * So, dummy function was invented, and if we forgot to + * include that file, we got an error at the link time. + * + * That function merely returns 0. + */ + .global _you_should_add_file___tn_arch_pic32_int_vec1_S___to_the_project + .ent _you_should_add_file___tn_arch_pic32_int_vec1_S___to_the_project +_you_should_add_file___tn_arch_pic32_int_vec1_S___to_the_project: + + li $v0, 0 + jr $ra + nop + + .end _you_should_add_file___tn_arch_pic32_int_vec1_S___to_the_project + + + + + .section .vector_1,code .align 2 .set nomips16 @@ -42,7 +85,10 @@ .global __vector_dispatch_1 .ent __vector_dispatch_1 + __vector_dispatch_1: - j cs0_int_handler + + j cs0_int_handler .end __vector_dispatch_1 .size __vector_dispatch_1, .-__vector_dispatch_1 + diff --git a/src/arch/pic32/tn_arch_pic32mx_xc32.S b/src/arch/pic32/tn_arch_pic32mx_xc32.S index d3f3759..f6122e3 100644 --- a/src/arch/pic32/tn_arch_pic32mx_xc32.S +++ b/src/arch/pic32/tn_arch_pic32mx_xc32.S @@ -47,9 +47,8 @@ /* Public functions declared in this file */ - .global _tn_arch_context_switch_exit - .global _tn_arch_context_switch - .global _tn_arch_system_start + .global _tn_arch_context_switch_now_nosave + .global _tn_arch_context_switch_pend .global _tn_arch_inside_isr .global tn_arch_sr_save_int_dis .global tn_arch_sr_restore @@ -67,30 +66,9 @@ *----------------------------------------------------------------------------*/ .set noreorder .set noat - .ent _tn_arch_system_start + .ent _tn_arch_context_switch_now_nosave -_tn_arch_system_start: - - li $t0, 1 /* 1 -> TN_STATE_FLAG__SYS_RUNNING */ - la $t1, tn_sys_state - sb $t0, 0($t1) - - lw $t1, tn_next_task_to_run - lw $sp, 0($t1) /* get new task's sp */ - la $t0, tn_curr_run_task - j tn_restore_context - sw $t1, 0($t0) /* tn_curr_run_task = tn_next_task_to_run */ - - .end _tn_arch_system_start - -/*---------------------------------------------------------------------------- -* Interrupts should be disabled here -*----------------------------------------------------------------------------*/ - .set noreorder - .set noat - .ent _tn_arch_context_switch_exit - -_tn_arch_context_switch_exit: +_tn_arch_context_switch_now_nosave: /* get new task's sp */ @@ -102,31 +80,17 @@ _tn_arch_context_switch_exit: sw $t1, 0($t0) /* *t0 = t1; */ /* i.e. tn_curr_run_task = tn_next_task_to_run; */ - .end _tn_arch_context_switch_exit + .end _tn_arch_context_switch_now_nosave /*---------------------------------------------------------------------------- * *----------------------------------------------------------------------------*/ .set noreorder .set noat - .ent _tn_arch_context_switch - -_tn_arch_context_switch: - - /* check if task switch needed */ - - /* - la $t0, tn_curr_run_task - la $t1, tn_next_task_to_run - lw $t0, 0($t0) - lw $t1, 0($t1) - bne $t0, $t1, 1f - nop + .ent _tn_arch_context_switch_pend - sdbbp 0 +_tn_arch_context_switch_pend: -1: - */ /* pend CS0 interrupt */ lui $t0, %hi(IFS0SET) ori $t1, $zero, 2 @@ -136,7 +100,7 @@ _tn_arch_context_switch: jr $ra nop - .end _tn_arch_context_switch + .end _tn_arch_context_switch_pend /*---------------------------------------------------------------------------- * diff --git a/src/arch/tn_arch.h b/src/arch/tn_arch.h index 8e12c59..46d2bba 100644 --- a/src/arch/tn_arch.h +++ b/src/arch/tn_arch.h @@ -89,7 +89,7 @@ void tn_arch_int_en(void); * * @see `tn_arch_sr_restore()` */ -unsigned int tn_arch_sr_save_int_dis(void); +TN_UWord tn_arch_sr_save_int_dis(void); /** * Restore previously saved status register @@ -99,7 +99,7 @@ unsigned int tn_arch_sr_save_int_dis(void); * * @see `tn_arch_sr_save_int_dis()` */ -void tn_arch_sr_restore(unsigned int sr); +void tn_arch_sr_restore(TN_UWord sr); @@ -121,12 +121,22 @@ void tn_arch_sr_restore(unsigned int sr); * size of the stack in `#TN_UWord`-s, not in bytes. */ TN_UWord *_tn_arch_stack_top_get( - TN_UWord *stack_low_address, - int stack_size + TN_UWord *stack_low_address, + int stack_size ); /** - * Should initialize stack for new task and return current stack pointer. + * Should put initial CPU context to the provided stack pointer for new task + * and return current stack pointer. + * + * When resulting context gets restored by + * `_tn_arch_context_switch_now_nosave()` or `_tn_arch_context_switch_pend()`, + * the following conditions should be met: + * + * - Interrupts are enabled; + * - Return address is set to `tn_task_exit()`, so that when task body function + * returns, `tn_task_exit()` gets automatially called; + * - Argument 0 contains `param` pointer * * @param task_func * Pointer to task body function. @@ -137,7 +147,7 @@ TN_UWord *_tn_arch_stack_top_get( * * @return current stack pointer (top of the stack) */ -unsigned int *_tn_arch_stack_init( +TN_UWord *_tn_arch_stack_init( TN_TaskBody *task_func, TN_UWord *stack_top, void *param @@ -149,64 +159,55 @@ unsigned int *_tn_arch_stack_init( int _tn_arch_inside_isr(void); /** - * Called whenever we need to switch context to other task. - * - * **Preconditions:** - * - * * interrupts are enabled; - * * `tn_curr_run_task` points to currently running (preempted) task; - * * `tn_next_task_to_run` points to new task to run. - * - * **Actions to perform:** + * Called whenever we need to switch context from one task to another. * - * * save context of the preempted task to its stack; - * * set `tn_curr_run_task` to `tn_next_task_to_run`; - * * switch context to it. + * This function typically does NOT switch context; it merely pends it, + * that is, it sets appropriate interrupt flag. If current level is an + * application level, interrupt is fired immediately, and context gets + * switched. * - * @see `tn_curr_run_task` - * @see `tn_next_task_to_run` - */ -void _tn_arch_context_switch(void); - -/** - * Called when some task calls `tn_task_exit()`. + * But, if it's hard or impossible on particular platform to use dedicated + * interrupt flag, this function may just switch the context on its own. * * **Preconditions:** * - * * interrupts are disabled; - * * `tn_next_task_to_run` is already set to other task. + * - interrupts are enabled; + * - `tn_curr_run_task` points to currently running (preempted) task; + * - `tn_next_task_to_run` points to new task to run. * - * **Actions to perform:** + * **Actions to perform in actual context switching routine:** * - * * set `tn_curr_run_task` to `tn_next_task_to_run`; - * * switch context to it. + * - save context of the preempted task to its stack; + * - set `tn_curr_run_task` to `tn_next_task_to_run`; + * - restore context of the newly activated task from its stack. * * @see `tn_curr_run_task` * @see `tn_next_task_to_run` */ -void _tn_arch_context_switch_exit(void); +void _tn_arch_context_switch_pend(void); /** - * Should perform first context switch (to the task pointed to by - * `tn_next_task_to_run`). + * Called whenever we need to switch context to new task, but don't save + * current context. This happens: + * - At system start, inside `tn_sys_start()`; + * - At task exit, inside `tn_task_exit()` + * + * This function doesn't pend context switch, it switches context immediately. * * **Preconditions:** * - * * no interrupts are set up yet, so, it's like interrupts disabled - * * `tn_next_task_to_run` is already set to idle task. + * - interrupts are disabled; + * - `tn_next_task_to_run` is already set to needed task. * * **Actions to perform:** * - * * set `#TN_STATE_FLAG__SYS_RUNNING` flag in the `tn_sys_state` variable; - * * set `tn_curr_run_task` to `tn_next_task_to_run`; - * * switch context to it. + * - set `tn_curr_run_task` to `tn_next_task_to_run`; + * - restore context of the newly activated task from its stack. * - * @see `#TN_STATE_FLAG__SYS_RUNNING` - * @see `tn_sys_state` * @see `tn_curr_run_task` * @see `tn_next_task_to_run` */ -void _tn_arch_system_start(void); +void _tn_arch_context_switch_now_nosave(void); #ifdef __cplusplus } /* extern "C" */ diff --git a/src/core/tn_dqueue.c b/src/core/tn_dqueue.c index ea3d08f..15465fb 100644 --- a/src/core/tn_dqueue.c +++ b/src/core/tn_dqueue.c @@ -122,46 +122,51 @@ static enum TN_RCode _fifo_write(struct TN_DQueue *dque, void *p_data) if (dque->filled_items_cnt >= dque->items_cnt){ //-- no space for new data rc = TN_RC_TIMEOUT; - goto out; - } + } else { + + //-- write data + dque->data_fifo[dque->head_idx] = p_data; + dque->filled_items_cnt++; + dque->head_idx++; + if (dque->head_idx >= dque->items_cnt){ + dque->head_idx = 0; + } - //-- write data - dque->data_fifo[dque->head_idx] = p_data; - dque->filled_items_cnt++; - dque->head_idx++; - if (dque->head_idx >= dque->items_cnt){ - dque->head_idx = 0; + //-- set flag in the connected event group (if any), + // indicating that there are messages in the queue + _tn_eventgrp_link_manage(&dque->eventgrp_link, TRUE); } -out: return rc; } static enum TN_RCode _fifo_read(struct TN_DQueue *dque, void **pp_data) { - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_read(pp_data); - rc = _check_param_read(pp_data); if (rc != TN_RC_OK){ - goto out; - } - - if (dque->filled_items_cnt == 0){ + //-- just return rc as it is + } else if (dque->filled_items_cnt == 0){ //-- nothing to read rc = TN_RC_TIMEOUT; - goto out; - } + } else { + + //-- read data + *pp_data = dque->data_fifo[dque->tail_idx]; + dque->filled_items_cnt--; + dque->tail_idx++; + if (dque->tail_idx >= dque->items_cnt){ + dque->tail_idx = 0; + } - //-- read data - *pp_data = dque->data_fifo[dque->tail_idx]; - dque->filled_items_cnt--; - dque->tail_idx++; - if (dque->tail_idx >= dque->items_cnt){ - dque->tail_idx = 0; + if (dque->filled_items_cnt == 0){ + //-- clear flag in the connected event group (if any), + // indicating that there are no messages in the queue + _tn_eventgrp_link_manage(&dque->eventgrp_link, FALSE); + } } -out: return rc; } // }}} @@ -212,6 +217,15 @@ static enum TN_RCode _queue_send( { enum TN_RCode rc = TN_RC_OK; + //-- first of all, we check whether there are task(s) that + // waits for receive message from the queue. + // + // If yes, we just pass new message to the first task + // from the waiting tasks list, and don't modify messages + // fifo at all. + // + // Otherwise (no waiting tasks), we add new message to the fifo. + if ( !_tn_task_first_wait_complete( &dque->wait_receive_list, TN_RC_OK, _cb_before_task_wait_complete__send, p_data, NULL @@ -284,87 +298,83 @@ static enum TN_RCode _dqueue_job_perform( TN_Timeout timeout ) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; BOOL waited = FALSE; void **pp_data = (void **)p_data; + enum TN_RCode rc = _check_param_generic(dque); - rc = _check_param_generic(dque); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - switch (job_type){ - case _JOB_TYPE__SEND: - rc = _queue_send(dque, p_data); + switch (job_type){ + case _JOB_TYPE__SEND: + rc = _queue_send(dque, p_data); + + if (rc == TN_RC_TIMEOUT && timeout != 0){ + //-- save user-provided data in the dqueue.data_elem task field, + // and put current task to wait until there's room in the queue. + tn_curr_run_task->subsys_wait.dqueue.data_elem = p_data; + _tn_task_curr_to_wait_action( + &(dque->wait_send_list), + TN_WAIT_REASON_DQUE_WSEND, + timeout + ); + + waited = TRUE; + } - if (rc == TN_RC_TIMEOUT && timeout != 0){ - //-- save user-provided data in the dqueue.data_elem task field, - // and put current task to wait until there's room in the queue. - tn_curr_run_task->subsys_wait.dqueue.data_elem = p_data; - _tn_task_curr_to_wait_action( - &(dque->wait_send_list), - TN_WAIT_REASON_DQUE_WSEND, - timeout - ); + break; + case _JOB_TYPE__RECEIVE: + rc = _queue_receive(dque, pp_data); - waited = TRUE; - } + if (rc == TN_RC_TIMEOUT && timeout != 0){ + //-- put current task to wait until new data comes. + _tn_task_curr_to_wait_action( + &(dque->wait_receive_list), + TN_WAIT_REASON_DQUE_WRECEIVE, + timeout + ); - break; - case _JOB_TYPE__RECEIVE: - rc = _queue_receive(dque, pp_data); - - if (rc == TN_RC_TIMEOUT && timeout != 0){ - //-- put current task to wait until new data comes. - _tn_task_curr_to_wait_action( - &(dque->wait_receive_list), - TN_WAIT_REASON_DQUE_WRECEIVE, - timeout - ); - - waited = TRUE; - } - break; - } + waited = TRUE; + } + break; + } #if TN_DEBUG - if (!_tn_need_context_switch() && waited){ - _TN_FATAL_ERROR(""); - } + if (!_tn_need_context_switch() && waited){ + _TN_FATAL_ERROR(""); + } #endif - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); - if (waited){ - - //-- get wait result - rc = tn_curr_run_task->task_wait_rc; - - switch (job_type){ - case _JOB_TYPE__SEND: - //-- do nothing special - break; - case _JOB_TYPE__RECEIVE: - //-- if wait result is TN_RC_OK, copy received pointer to the - // user's location - if (rc == TN_RC_OK){ - //-- dqueue.data_elem should contain valid value now, - // return it to caller - *pp_data = tn_curr_run_task->subsys_wait.dqueue.data_elem; - } - break; + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + if (waited){ + + //-- get wait result + rc = tn_curr_run_task->task_wait_rc; + + switch (job_type){ + case _JOB_TYPE__SEND: + //-- do nothing special + break; + case _JOB_TYPE__RECEIVE: + //-- if wait result is TN_RC_OK, copy received pointer to the + // user's location + if (rc == TN_RC_OK){ + //-- dqueue.data_elem should contain valid value now, + // return it to caller + *pp_data = tn_curr_run_task->subsys_wait.dqueue.data_elem; + } + break; + } } - } -out: + } return rc; } @@ -374,34 +384,31 @@ static enum TN_RCode _dqueue_job_iperform( void *p_data //-- used for _JOB_TYPE__SEND ) { - TN_INTSAVE_DATA_INT; - enum TN_RCode rc = TN_RC_OK; void **pp_data = (void **)p_data; + enum TN_RCode rc = _check_param_generic(dque); - rc = _check_param_generic(dque); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_isr_context()){ + //-- just return rc as it is + } else if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA_INT; - TN_INT_IDIS_SAVE(); + TN_INT_IDIS_SAVE(); - switch (job_type){ - case _JOB_TYPE__SEND: - rc = _queue_send(dque, p_data); - break; - case _JOB_TYPE__RECEIVE: - rc = _queue_receive(dque, pp_data); - break; - } + switch (job_type){ + case _JOB_TYPE__SEND: + rc = _queue_send(dque, p_data); + break; + case _JOB_TYPE__RECEIVE: + rc = _queue_receive(dque, pp_data); + break; + } - TN_INT_IRESTORE(); + TN_INT_IRESTORE(); + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); + } -out: return rc; } @@ -426,25 +433,27 @@ enum TN_RCode tn_queue_create( rc = _check_param_create(dque, data_fifo, items_cnt); if (rc != TN_RC_OK){ - goto out; - } + //-- just return rc as it is + } else { + tn_list_reset(&(dque->wait_send_list)); + tn_list_reset(&(dque->wait_receive_list)); - tn_list_reset(&(dque->wait_send_list)); - tn_list_reset(&(dque->wait_receive_list)); + dque->data_fifo = data_fifo; + dque->items_cnt = items_cnt; - dque->data_fifo = data_fifo; - dque->items_cnt = items_cnt; - if (dque->data_fifo == NULL){ - dque->items_cnt = 0; - } + _tn_eventgrp_link_reset(&dque->eventgrp_link); + + if (dque->data_fifo == NULL){ + dque->items_cnt = 0; + } - dque->filled_items_cnt = 0; - dque->tail_idx = 0; - dque->head_idx = 0; + dque->filled_items_cnt = 0; + dque->tail_idx = 0; + dque->head_idx = 0; - dque->id_dque = TN_ID_DATAQUEUE; + dque->id_dque = TN_ID_DATAQUEUE; + } -out: return rc; } @@ -454,35 +463,33 @@ enum TN_RCode tn_queue_create( */ enum TN_RCode tn_queue_delete(struct TN_DQueue * dque) { - TN_INTSAVE_DATA; enum TN_RCode rc = TN_RC_OK; rc = _check_param_generic(dque); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - //-- notify waiting tasks that the object is deleted - // (TN_RC_DELETED is returned) - _tn_wait_queue_notify_deleted(&(dque->wait_send_list)); - _tn_wait_queue_notify_deleted(&(dque->wait_receive_list)); + //-- notify waiting tasks that the object is deleted + // (TN_RC_DELETED is returned) + _tn_wait_queue_notify_deleted(&(dque->wait_send_list)); + _tn_wait_queue_notify_deleted(&(dque->wait_receive_list)); - dque->id_dque = 0; //-- data queue does not exist now + dque->id_dque = 0; //-- data queue does not exist now - TN_INT_RESTORE(); + TN_INT_RESTORE(); - //-- we might need to switch context if _tn_wait_queue_notify_deleted() - // has woken up some high-priority task - _tn_switch_context_if_needed(); + //-- we might need to switch context if _tn_wait_queue_notify_deleted() + // has woken up some high-priority task + _tn_context_switch_pend_if_needed(); + + } -out: return rc; } @@ -549,5 +556,45 @@ enum TN_RCode tn_queue_ireceive_polling(struct TN_DQueue *dque, void **pp_data) return _dqueue_job_iperform(dque, _JOB_TYPE__RECEIVE, pp_data); } +/* + * See comments in the header file (tn_dqueue.h) + */ +enum TN_RCode tn_queue_eventgrp_connect( + struct TN_DQueue *dque, + struct TN_EventGrp *eventgrp, + TN_UWord pattern + ) +{ + int sr_saved; + enum TN_RCode rc = _check_param_generic(dque); + + if (rc == TN_RC_OK){ + sr_saved = tn_arch_sr_save_int_dis(); + rc = _tn_eventgrp_link_set(&dque->eventgrp_link, eventgrp, pattern); + tn_arch_sr_restore(sr_saved); + } + + return rc; +} + +/* + * See comments in the header file (tn_dqueue.h) + */ +enum TN_RCode tn_queue_eventgrp_disconnect( + struct TN_DQueue *dque + ) +{ + int sr_saved; + enum TN_RCode rc = _check_param_generic(dque); + + if (rc == TN_RC_OK){ + sr_saved = tn_arch_sr_save_int_dis(); + rc = _tn_eventgrp_link_reset(&dque->eventgrp_link); + tn_arch_sr_restore(sr_saved); + } + + return rc; +} + diff --git a/src/core/tn_dqueue.h b/src/core/tn_dqueue.h index 28a9820..d0a845a 100644 --- a/src/core/tn_dqueue.h +++ b/src/core/tn_dqueue.h @@ -44,7 +44,9 @@ * data element into the FIFO. If there is no space left in the FIFO, the task * is switched to the waiting state and placed in the data queue's `wait_send` * queue until space appears (another task gets a data element from the data - * queue). A task that receives a data element tries to get a data element + * queue). + * + * A task that receives a data element tries to get a data element * from the FIFO. If the FIFO is empty (there is no data in the data queue), * the task is switched to the waiting state and placed in the data queue's * `wait_receive` queue until data element arrive (another task puts some data @@ -53,6 +55,21 @@ * received can be interpreted as a pointer or an integer and may have value 0 * (`NULL`). * + * For the useful pattern on how to use queue together with \ref tn_fmem.h + * "fixed memory pool", refer to the example: `examples/queue`. Be sure + * to examine the readme there. + * + * TNeoKernel offers a way to wait for a message from multiple queues in just a + * single call, refer to the section \ref eventgrp_connect for details. Related + * queue services: + * + * - `tn_queue_eventgrp_connect()` + * - `tn_queue_eventgrp_disconnect()` + * + * There is an example project available that demonstrates event group + * connection technique: `examples/queue_eventgrp_conn`. Be sure to examine the + * readme there. + * */ #ifndef _TN_DQUEUE_H @@ -64,8 +81,13 @@ #include "tn_list.h" #include "tn_common.h" +#include "tn_eventgrp.h" + +/******************************************************************************* + * EXTERN TYPES + ******************************************************************************/ @@ -106,6 +128,9 @@ struct TN_DQueue { /// /// id for object validity verification enum TN_ObjId id_dque; + /// + /// connected event group + struct TN_EGrpLink eventgrp_link; }; /** @@ -304,6 +329,47 @@ enum TN_RCode tn_queue_ireceive_polling( ); +/** + * Connect an event group to the queue. + * Refer to the section \ref eventgrp_connect for details. + * + * Only one event group can be connected to the queue at a time. If you + * connect event group while another event group is already connected, + * the old link is discarded. + * + * @param dque + * queue to which event group should be connected + * @param eventgrp + * event groupt to connect + * @param pattern + * flags pattern that should be managed by the queue automatically + * + * $(TN_CALL_FROM_TASK) + * $(TN_CALL_FROM_ISR) + * $(TN_LEGEND_LINK) + */ +enum TN_RCode tn_queue_eventgrp_connect( + struct TN_DQueue *dque, + struct TN_EventGrp *eventgrp, + TN_UWord pattern + ); + + +/** + * Disconnect a connected event group from the queue. + * Refer to the section \ref eventgrp_connect for details. + * + * If there is no event group connected, nothing is changed. + * + * @param dque queue from which event group should be disconnected + * + * $(TN_CALL_FROM_TASK) + * $(TN_CALL_FROM_ISR) + * $(TN_LEGEND_LINK) + */ +enum TN_RCode tn_queue_eventgrp_disconnect( + struct TN_DQueue *dque + ); #ifdef __cplusplus diff --git a/src/core/tn_eventgrp.c b/src/core/tn_eventgrp.c index 9bee3d9..deaaccb 100644 --- a/src/core/tn_eventgrp.c +++ b/src/core/tn_eventgrp.c @@ -77,10 +77,10 @@ static inline enum TN_RCode _check_param_generic( static inline enum TN_RCode _check_param_job_perform( struct TN_EventGrp *eventgrp, - unsigned int pattern + TN_UWord pattern ) { - enum TN_RCode rc = _check_param_generic(eventgrp); + enum TN_RCode rc = TN_RC_OK; if (pattern == 0){ rc = TN_RC_WPARAM; @@ -111,11 +111,14 @@ static inline enum TN_RCode _check_param_create( static BOOL _cond_check( - struct TN_EventGrp *eventgrp, + struct TN_EventGrp *eventgrp, enum TN_EGrpWaitMode wait_mode, - int wait_pattern + TN_UWord wait_pattern ) { + //-- interrupts should be disabled here + _TN_BUG_ON( !TN_IS_INT_DISABLED() ); + BOOL cond = FALSE; switch (wait_mode){ @@ -140,7 +143,7 @@ static BOOL _cond_check( static void _clear_pattern_if_needed( struct TN_EventGrp *eventgrp, enum TN_EGrpWaitMode wait_mode, - unsigned int pattern + TN_UWord pattern ) { //-- probably, we should add one more wait mode flag, like @@ -149,6 +152,9 @@ static void _clear_pattern_if_needed( static void _scan_event_waitqueue(struct TN_EventGrp *eventgrp) { + //-- interrupts should be disabled here + _TN_BUG_ON( !TN_IS_INT_DISABLED() ); + struct TN_Task *task; struct TN_Task *tmp_task; @@ -182,46 +188,44 @@ static void _scan_event_waitqueue(struct TN_EventGrp *eventgrp) static enum TN_RCode _eventgrp_wait( struct TN_EventGrp *eventgrp, - unsigned int wait_pattern, + TN_UWord wait_pattern, enum TN_EGrpWaitMode wait_mode, - unsigned int *p_flags_pattern + TN_UWord *p_flags_pattern ) { - enum TN_RCode rc = TN_RC_OK; + //-- interrupts should be disabled here + _TN_BUG_ON( !TN_IS_INT_DISABLED() ); + + enum TN_RCode rc = _check_param_job_perform(eventgrp, wait_pattern); - rc = _check_param_job_perform(eventgrp, wait_pattern); if (rc != TN_RC_OK){ - goto out; - } + //-- just return rc as it is + } else { - //-- Check release condition + //-- Check release condition - if (_cond_check(eventgrp, wait_mode, wait_pattern)){ - if (p_flags_pattern != NULL){ - *p_flags_pattern = eventgrp->pattern; + if (_cond_check(eventgrp, wait_mode, wait_pattern)){ + if (p_flags_pattern != NULL){ + *p_flags_pattern = eventgrp->pattern; + } + _clear_pattern_if_needed(eventgrp, wait_mode, wait_pattern); + rc = TN_RC_OK; + } else { + rc = TN_RC_TIMEOUT; } - _clear_pattern_if_needed(eventgrp, wait_mode, wait_pattern); - rc = TN_RC_OK; - } else { - rc = TN_RC_TIMEOUT; - } -out: + } return rc; } static enum TN_RCode _eventgrp_modify( struct TN_EventGrp *eventgrp, enum TN_EGrpOp operation, - unsigned int pattern + TN_UWord pattern ) { - enum TN_RCode rc = TN_RC_OK; - - rc = _check_param_job_perform(eventgrp, pattern); - if (rc != TN_RC_OK){ - goto out; - } + //-- interrupts should be disabled here + _TN_BUG_ON( !TN_IS_INT_DISABLED() ); switch (operation){ case TN_EVENTGRP_OP_CLEAR: @@ -229,8 +233,10 @@ static enum TN_RCode _eventgrp_modify( break; case TN_EVENTGRP_OP_SET: - eventgrp->pattern |= pattern; - _scan_event_waitqueue(eventgrp); + if ((eventgrp->pattern & pattern) != pattern){ + eventgrp->pattern |= pattern; + _scan_event_waitqueue(eventgrp); + } break; case TN_EVENTGRP_OP_TOGGLE: @@ -239,8 +245,7 @@ static enum TN_RCode _eventgrp_modify( break; } -out: - return rc; + return TN_RC_OK; } @@ -256,23 +261,22 @@ static enum TN_RCode _eventgrp_modify( * See comments in the header file (tn_eventgrp.h) */ enum TN_RCode tn_eventgrp_create( - struct TN_EventGrp *eventgrp, - unsigned int initial_pattern //-- initial value of the pattern + struct TN_EventGrp *eventgrp, + TN_UWord initial_pattern //-- initial value of the pattern ) { - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_create(eventgrp); - rc = _check_param_create(eventgrp); if (rc != TN_RC_OK){ - goto out; - } + //-- just return rc as it is + } else { - tn_list_reset(&(eventgrp->wait_queue)); + tn_list_reset(&(eventgrp->wait_queue)); - eventgrp->pattern = initial_pattern; - eventgrp->id_event = TN_ID_EVENTGRP; + eventgrp->pattern = initial_pattern; + eventgrp->id_event = TN_ID_EVENTGRP; -out: + } return rc; } @@ -282,32 +286,28 @@ enum TN_RCode tn_eventgrp_create( */ enum TN_RCode tn_eventgrp_delete(struct TN_EventGrp *eventgrp) { - enum TN_RCode rc = TN_RC_OK; - TN_INTSAVE_DATA; + enum TN_RCode rc = _check_param_generic(eventgrp); - rc = _check_param_generic(eventgrp); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - _tn_wait_queue_notify_deleted(&(eventgrp->wait_queue)); + _tn_wait_queue_notify_deleted(&(eventgrp->wait_queue)); - eventgrp->id_event = 0; //-- event does not exist now + eventgrp->id_event = 0; //-- event does not exist now - TN_INT_RESTORE(); + TN_INT_RESTORE(); - //-- we might need to switch context if _tn_wait_queue_notify_deleted() - // has woken up some high-priority task - _tn_switch_context_if_needed(); + //-- we might need to switch context if _tn_wait_queue_notify_deleted() + // has woken up some high-priority task + _tn_context_switch_pend_if_needed(); -out: + } return rc; } @@ -317,57 +317,58 @@ enum TN_RCode tn_eventgrp_delete(struct TN_EventGrp *eventgrp) */ enum TN_RCode tn_eventgrp_wait( struct TN_EventGrp *eventgrp, - unsigned int wait_pattern, + TN_UWord wait_pattern, enum TN_EGrpWaitMode wait_mode, - unsigned int *p_flags_pattern, + TN_UWord *p_flags_pattern, TN_Timeout timeout ) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; BOOL waited_for_event = FALSE; + enum TN_RCode rc = _check_param_generic(eventgrp); - if (!tn_is_task_context()){ + if (rc != TN_RC_OK){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - rc = _eventgrp_wait(eventgrp, wait_pattern, wait_mode, p_flags_pattern); + rc = _eventgrp_wait(eventgrp, wait_pattern, wait_mode, p_flags_pattern); - if (rc == TN_RC_TIMEOUT && timeout != 0){ - tn_curr_run_task->subsys_wait.eventgrp.wait_mode = wait_mode; - tn_curr_run_task->subsys_wait.eventgrp.wait_pattern = wait_pattern; - _tn_task_curr_to_wait_action( - &(eventgrp->wait_queue), - TN_WAIT_REASON_EVENT, - timeout - ); - waited_for_event = TRUE; - } + if (rc == TN_RC_TIMEOUT && timeout != 0){ + tn_curr_run_task->subsys_wait.eventgrp.wait_mode = wait_mode; + tn_curr_run_task->subsys_wait.eventgrp.wait_pattern = wait_pattern; + _tn_task_curr_to_wait_action( + &(eventgrp->wait_queue), + TN_WAIT_REASON_EVENT, + timeout + ); + waited_for_event = TRUE; + } #if TN_DEBUG - if (!_tn_need_context_switch() && waited_for_event){ - _TN_FATAL_ERROR(""); - } + if (!_tn_need_context_switch() && waited_for_event){ + _TN_FATAL_ERROR(""); + } #endif - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); - if (waited_for_event){ - //-- get wait result - rc = tn_curr_run_task->task_wait_rc; - - //-- if wait result is TN_RC_OK, and p_flags_pattern is provided, - // copy actual_pattern there - if (rc == TN_RC_OK && p_flags_pattern != NULL ){ - *p_flags_pattern = - tn_curr_run_task->subsys_wait.eventgrp.actual_pattern; + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + if (waited_for_event){ + //-- get wait result + rc = tn_curr_run_task->task_wait_rc; + + //-- if wait result is TN_RC_OK, and p_flags_pattern is provided, + // copy actual_pattern there + if (rc == TN_RC_OK && p_flags_pattern != NULL ){ + *p_flags_pattern = + tn_curr_run_task->subsys_wait.eventgrp.actual_pattern; + } } - } -out: + } return rc; } @@ -377,26 +378,24 @@ enum TN_RCode tn_eventgrp_wait( */ enum TN_RCode tn_eventgrp_wait_polling( struct TN_EventGrp *eventgrp, - unsigned int wait_pattern, + TN_UWord wait_pattern, enum TN_EGrpWaitMode wait_mode, - unsigned int *p_flags_pattern + TN_UWord *p_flags_pattern ) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(eventgrp); - if (!tn_is_task_context()){ + if (rc != TN_RC_OK){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); - - rc = _eventgrp_wait(eventgrp, wait_pattern, wait_mode, p_flags_pattern); - - TN_INT_RESTORE(); + } else { + TN_INTSAVE_DATA; -out: + TN_INT_DIS_SAVE(); + rc = _eventgrp_wait(eventgrp, wait_pattern, wait_mode, p_flags_pattern); + TN_INT_RESTORE(); + } return rc; } @@ -406,26 +405,28 @@ enum TN_RCode tn_eventgrp_wait_polling( */ enum TN_RCode tn_eventgrp_iwait_polling( struct TN_EventGrp *eventgrp, - unsigned int wait_pattern, + TN_UWord wait_pattern, enum TN_EGrpWaitMode wait_mode, - unsigned int *p_flags_pattern + TN_UWord *p_flags_pattern ) { - TN_INTSAVE_DATA_INT; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_generic(eventgrp); - if (!tn_is_isr_context()){ + if (rc != TN_RC_OK){ + //-- just return rc as it is + } else if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA_INT; - TN_INT_IDIS_SAVE(); + TN_INT_IDIS_SAVE(); - rc = _eventgrp_wait(eventgrp, wait_pattern, wait_mode, p_flags_pattern); + rc = _eventgrp_wait(eventgrp, wait_pattern, wait_mode, p_flags_pattern); - TN_INT_IRESTORE(); + TN_INT_IRESTORE(); + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); -out: + } return rc; } @@ -436,25 +437,26 @@ enum TN_RCode tn_eventgrp_iwait_polling( enum TN_RCode tn_eventgrp_modify( struct TN_EventGrp *eventgrp, enum TN_EGrpOp operation, - unsigned int pattern + TN_UWord pattern ) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(eventgrp); - if (!tn_is_task_context()){ + if (rc != TN_RC_OK){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - rc = _eventgrp_modify(eventgrp, operation, pattern); + rc = _eventgrp_modify(eventgrp, operation, pattern); - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); -out: + } return rc; } @@ -465,24 +467,99 @@ enum TN_RCode tn_eventgrp_modify( enum TN_RCode tn_eventgrp_imodify( struct TN_EventGrp *eventgrp, enum TN_EGrpOp operation, - unsigned int pattern + TN_UWord pattern ) { - TN_INTSAVE_DATA_INT; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_generic(eventgrp); - if (!tn_is_isr_context()){ + if (rc != TN_RC_OK){ + //-- just return rc as it is + } else if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; + } else { + TN_INTSAVE_DATA_INT; + + TN_INT_IDIS_SAVE(); + + rc = _eventgrp_modify(eventgrp, operation, pattern); + + TN_INT_IRESTORE(); + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); } + return rc; +} + + + + +/******************************************************************************* + * PROTECTED FUNCTIONS + ******************************************************************************/ - TN_INT_IDIS_SAVE(); +/** + * See comments in the file tn_internal.h + */ +enum TN_RCode _tn_eventgrp_link_set( + struct TN_EGrpLink *eventgrp_link, + struct TN_EventGrp *eventgrp, + TN_UWord pattern + ) +{ + //-- interrupts should be disabled here + _TN_BUG_ON( !TN_IS_INT_DISABLED() ); - rc = _eventgrp_modify(eventgrp, operation, pattern); + enum TN_RCode rc = _check_param_generic(eventgrp); - TN_INT_IRESTORE(); + if (rc != TN_RC_OK){ + //-- just return rc as it is + } else if (pattern == (0)){ + rc = TN_RC_WPARAM; + } else { + eventgrp_link->eventgrp = eventgrp; + eventgrp_link->pattern = pattern; + } + + return rc; +} + + +/** + * See comments in the file tn_internal.h + */ +enum TN_RCode _tn_eventgrp_link_reset( + struct TN_EGrpLink *eventgrp_link + ) +{ + enum TN_RCode rc = TN_RC_OK; + + eventgrp_link->eventgrp = NULL; + eventgrp_link->pattern = (0); + + return rc; +} + + +/** + * See comments in the file tn_internal.h + */ +enum TN_RCode _tn_eventgrp_link_manage( + struct TN_EGrpLink *eventgrp_link, + BOOL set + ) +{ + //-- interrupts should be disabled here + _TN_BUG_ON( !TN_IS_INT_DISABLED() ); + + enum TN_RCode rc = TN_RC_OK; + + if (eventgrp_link->eventgrp != NULL){ + _eventgrp_modify( + eventgrp_link->eventgrp, + (set ? TN_EVENTGRP_OP_SET : TN_EVENTGRP_OP_CLEAR), + eventgrp_link->pattern + ); + } -out: return rc; } diff --git a/src/core/tn_eventgrp.h b/src/core/tn_eventgrp.h index 5bee781..1e67004 100644 --- a/src/core/tn_eventgrp.h +++ b/src/core/tn_eventgrp.h @@ -39,7 +39,7 @@ * * Event group. * - * An event group has an internal variable (of type `unsigned int`), which is + * An event group has an internal variable (of type `TN_UWord`), which is * interpreted as a bit pattern where each bit represents an event. An event * group also has a wait queue for the tasks waiting on these events. A task * may set specified bits when an event occurs and may clear specified bits @@ -49,6 +49,50 @@ * queue. An event group is a very suitable synchronization object for cases * where (for some reasons) one task has to wait for many tasks, or vice versa, * many tasks have to wait for one task. + * + * \section eventgrp_connect Connecting an event group to other system objects + * + * Sometimes task needs to wait for different system events, the most common + * examples are: + * + * - wait for a message from the queue(s) plus wait for some + * application-dependent event; + * - wait for messages from multiple queues. + * + * If the kernel doesn't offer a mechanism for that, programmer usually have to + * use polling services on these queues and sleep for a few system ticks. + * Obviously, this approach has serious drawbacks: we have a lot of useless + * context switches, and response for the message gets much slower. Actually, + * we lost the main goal of the preemtive kernel when we use polling services + * like that. + * + * TNeoKernel offers a solution: an event group can be connected to other + * kernel objects, and these objects will maintain certain flags inside that + * event group automatically. + * + * So, in case of multiple queues, we can act as follows (assume we have two + * queues: Q1 and Q2) : + * + * - create event group EG; + * - connect EG with flag 1 to Q1; + * - connect EG with flag 2 to Q2; + * - when task needs to receive a message from either Q1 or Q2, it just waits + * for the any of flags 1 or 2 in the EG, this is done in the single call + * to `tn_eventgrp_wait()`. + * - when that event happened, task checks which flag is set, and receive + * message from the appropriate queue. + * + * Please note that task waiting for the event should **not** clear the flag + * manually: this flag is maintained completely by the queue. If the queue is + * non-empty, the flag is set. If the queue becomes empty, the flag is cleared. + * + * For the information on system services related to queue, refer to the \ref + * tn_dqueue.h "queue reference". + * + * There is an example project available that demonstrates event group + * connection technique: `examples/queue_eventgrp_conn`. Be sure to examine the + * readme there. + * */ #ifndef _TN_EVENTGRP_H @@ -60,6 +104,7 @@ #include "tn_list.h" #include "tn_common.h" +#include "tn_sys.h" @@ -112,7 +157,7 @@ enum TN_EGrpOp { */ struct TN_EventGrp { struct TN_ListItem wait_queue; //!< task wait queue - unsigned int pattern; //!< current flags pattern + TN_UWord pattern; //!< current flags pattern enum TN_ObjId id_event; //!< id for object validity verification }; @@ -123,15 +168,27 @@ struct TN_EventGrp { struct TN_EGrpTaskWait { /// /// event wait pattern - int wait_pattern; + TN_UWord wait_pattern; /// /// event wait mode: `AND` or `OR` enum TN_EGrpWaitMode wait_mode; /// /// pattern that caused task to finish waiting - int actual_pattern; + TN_UWord actual_pattern; }; +/** + * A link to event group: used when event group can be connected to + * some kernel object, such as queue. + */ +struct TN_EGrpLink { + /// + /// event group whose event(s) should be managed by other kernel object + struct TN_EventGrp *eventgrp; + /// + /// event pattern to manage + TN_UWord pattern; +}; /******************************************************************************* @@ -167,8 +224,8 @@ struct TN_EGrpTaskWait { * is available: `#TN_RC_WPARAM`. */ enum TN_RCode tn_eventgrp_create( - struct TN_EventGrp *eventgrp, - unsigned int initial_pattern + struct TN_EventGrp *eventgrp, + TN_UWord initial_pattern ); /** @@ -210,7 +267,7 @@ enum TN_RCode tn_eventgrp_delete(struct TN_EventGrp *eventgrp); * `wait_pattern` to be set, or for just **any** of them * (see enum `#TN_EGrpWaitMode`) * @param p_flags_pattern - * Pointer to the `unsigned int` variable in which actual event pattern + * Pointer to the `TN_UWord` variable in which actual event pattern * that caused task to stop waiting will be stored. * May be `NULL`. * @param timeout @@ -227,9 +284,9 @@ enum TN_RCode tn_eventgrp_delete(struct TN_EventGrp *eventgrp); */ enum TN_RCode tn_eventgrp_wait( struct TN_EventGrp *eventgrp, - unsigned int wait_pattern, + TN_UWord wait_pattern, enum TN_EGrpWaitMode wait_mode, - unsigned int *p_flags_pattern, + TN_UWord *p_flags_pattern, TN_Timeout timeout ); @@ -242,9 +299,9 @@ enum TN_RCode tn_eventgrp_wait( */ enum TN_RCode tn_eventgrp_wait_polling( struct TN_EventGrp *eventgrp, - unsigned int wait_pattern, + TN_UWord wait_pattern, enum TN_EGrpWaitMode wait_mode, - unsigned int *p_flags_pattern + TN_UWord *p_flags_pattern ); /** @@ -256,9 +313,9 @@ enum TN_RCode tn_eventgrp_wait_polling( */ enum TN_RCode tn_eventgrp_iwait_polling( struct TN_EventGrp *eventgrp, - unsigned int wait_pattern, + TN_UWord wait_pattern, enum TN_EGrpWaitMode wait_mode, - unsigned int *p_flags_pattern + TN_UWord *p_flags_pattern ); /** @@ -286,7 +343,7 @@ enum TN_RCode tn_eventgrp_iwait_polling( enum TN_RCode tn_eventgrp_modify( struct TN_EventGrp *eventgrp, enum TN_EGrpOp operation, - unsigned int pattern + TN_UWord pattern ); /** @@ -299,7 +356,7 @@ enum TN_RCode tn_eventgrp_modify( enum TN_RCode tn_eventgrp_imodify( struct TN_EventGrp *eventgrp, enum TN_EGrpOp operation, - unsigned int pattern + TN_UWord pattern ); diff --git a/src/core/tn_fmem.c b/src/core/tn_fmem.c index 60618ba..d66cc42 100644 --- a/src/core/tn_fmem.c +++ b/src/core/tn_fmem.c @@ -268,33 +268,29 @@ enum TN_RCode tn_fmem_create( */ enum TN_RCode tn_fmem_delete(struct TN_FMem *fmem) { - TN_INTSAVE_DATA; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_fmem_delete(fmem); - rc = _check_param_fmem_delete(fmem); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - //-- remove all tasks (if any) from fmem's wait queue - _tn_wait_queue_notify_deleted(&(fmem->wait_queue)); + //-- remove all tasks (if any) from fmem's wait queue + _tn_wait_queue_notify_deleted(&(fmem->wait_queue)); - fmem->id_fmp = 0; //-- Fixed-size memory pool does not exist now + fmem->id_fmp = 0; //-- Fixed-size memory pool does not exist now - TN_INT_RESTORE(); + TN_INT_RESTORE(); - //-- we might need to switch context if _tn_wait_queue_notify_deleted() - // has woken up some high-priority task - _tn_switch_context_if_needed(); + //-- we might need to switch context if _tn_wait_queue_notify_deleted() + // has woken up some high-priority task + _tn_context_switch_pend_if_needed(); -out: + } return rc; } @@ -308,49 +304,45 @@ enum TN_RCode tn_fmem_get( TN_Timeout timeout ) { - TN_INTSAVE_DATA; - enum TN_RCode rc; BOOL waited_for_data = FALSE; + enum TN_RCode rc = _check_param_job_perform(fmem, p_data); - rc = _check_param_job_perform(fmem, p_data); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - rc = _fmem_get(fmem, p_data); + rc = _fmem_get(fmem, p_data); - if (rc == TN_RC_TIMEOUT && timeout > 0){ - _tn_task_curr_to_wait_action( - &(fmem->wait_queue), - TN_WAIT_REASON_WFIXMEM, - timeout - ); - waited_for_data = TRUE; - } + if (rc == TN_RC_TIMEOUT && timeout > 0){ + _tn_task_curr_to_wait_action( + &(fmem->wait_queue), + TN_WAIT_REASON_WFIXMEM, + timeout + ); + waited_for_data = TRUE; + } - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); - if (waited_for_data){ + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + if (waited_for_data){ - //-- get wait result - rc = tn_curr_run_task->task_wait_rc; + //-- get wait result + rc = tn_curr_run_task->task_wait_rc; + + //-- if wait result is TN_RC_OK, copy memory block pointer to the + // user's location + if (rc == TN_RC_OK){ + *p_data = tn_curr_run_task->subsys_wait.fmem.data_elem; + } - //-- if wait result is TN_RC_OK, copy memory block pointer to the - // user's location - if (rc == TN_RC_OK){ - *p_data = tn_curr_run_task->subsys_wait.fmem.data_elem; } } - -out: return rc; } @@ -360,26 +352,20 @@ enum TN_RCode tn_fmem_get( */ enum TN_RCode tn_fmem_get_polling(struct TN_FMem *fmem,void **p_data) { - TN_INTSAVE_DATA; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_job_perform(fmem, p_data); - rc = _check_param_job_perform(fmem, p_data); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); - - rc = _fmem_get(fmem, p_data); + } else { + TN_INTSAVE_DATA; - TN_INT_RESTORE(); + TN_INT_DIS_SAVE(); + rc = _fmem_get(fmem, p_data); + TN_INT_RESTORE(); + } -out: return rc; } @@ -389,26 +375,22 @@ enum TN_RCode tn_fmem_get_polling(struct TN_FMem *fmem,void **p_data) */ enum TN_RCode tn_fmem_iget_polling(struct TN_FMem *fmem, void **p_data) { - TN_INTSAVE_DATA_INT; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_job_perform(fmem, p_data); - rc = _check_param_job_perform(fmem, p_data); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_isr_context()){ + //-- just return rc as it is + } else if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_IDIS_SAVE(); + } else { + TN_INTSAVE_DATA_INT; - rc = _fmem_get(fmem, p_data); + TN_INT_IDIS_SAVE(); - TN_INT_IRESTORE(); + rc = _fmem_get(fmem, p_data); -out: + TN_INT_IRESTORE(); + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); + } return rc; } @@ -418,27 +400,22 @@ enum TN_RCode tn_fmem_iget_polling(struct TN_FMem *fmem, void **p_data) */ enum TN_RCode tn_fmem_release(struct TN_FMem *fmem, void *p_data) { - TN_INTSAVE_DATA; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_job_perform(fmem, p_data); - rc = _check_param_job_perform(fmem, p_data); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); + } else { + TN_INTSAVE_DATA; - rc = _fmem_release(fmem, p_data); + TN_INT_DIS_SAVE(); - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + rc = _fmem_release(fmem, p_data); -out: + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + } return rc; } @@ -448,26 +425,23 @@ enum TN_RCode tn_fmem_release(struct TN_FMem *fmem, void *p_data) */ enum TN_RCode tn_fmem_irelease(struct TN_FMem *fmem, void *p_data) { - TN_INTSAVE_DATA_INT; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_job_perform(fmem, p_data); - rc = _check_param_job_perform(fmem, p_data); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_isr_context()){ + //-- just return rc as it is + } else if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA_INT; - TN_INT_IDIS_SAVE(); + TN_INT_IDIS_SAVE(); - rc = _fmem_release(fmem, p_data); + rc = _fmem_release(fmem, p_data); - TN_INT_IRESTORE(); + TN_INT_IRESTORE(); + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); + } -out: return rc; } diff --git a/src/core/tn_fmem.h b/src/core/tn_fmem.h index 6c1d396..61ef0ad 100644 --- a/src/core/tn_fmem.h +++ b/src/core/tn_fmem.h @@ -45,6 +45,11 @@ * no free memory blocks, a task trying to acquire a memory block will be * placed into the wait queue until a free memory block arrives (another task * returns it to the memory pool). + * + * + * For the useful pattern on how to use fixed memory pool together with \ref + * tn_dqueue.h "queue", refer to the example: `examples/queue`. Be sure to + * examine the readme there. */ #ifndef _TN_MEM_H diff --git a/src/core/tn_internal.h b/src/core/tn_internal.h index ce4d066..12754e8 100644 --- a/src/core/tn_internal.h +++ b/src/core/tn_internal.h @@ -88,10 +88,10 @@ extern volatile int tn_created_tasks_cnt; extern volatile enum TN_StateFlag tn_sys_state; /// task that runs now -extern struct TN_Task * tn_curr_run_task; +extern struct TN_Task *tn_curr_run_task; /// task that should run as soon as possible (after context switch) -extern struct TN_Task * tn_next_task_to_run; +extern struct TN_Task *tn_next_task_to_run; /// bitmask of priorities with runnable tasks. /// lowest priority bit (1 << (TN_PRIORITIES_CNT - 1)) should always be set, @@ -140,6 +140,20 @@ extern struct TN_Task tn_idle_task; #endif +#ifndef TN_DEBUG +# error TN_DEBUG is not defined +#endif + +#if TN_DEBUG +#define _TN_BUG_ON(cond, ...){ \ + if (cond){ \ + _TN_FATAL_ERROR(__VA_ARGS__); \ + } \ +} +#else +#define _TN_BUG_ON(cond) /* nothing */ +#endif + /******************************************************************************* @@ -148,7 +162,6 @@ extern struct TN_Task tn_idle_task; /** * Remove all tasks from wait queue, returning the TN_RC_DELETED code. - * Note: this function might sleep. */ void _tn_wait_queue_notify_deleted(struct TN_ListItem *wait_queue); @@ -178,10 +191,10 @@ static inline BOOL _tn_need_context_switch(void) return (tn_curr_run_task != tn_next_task_to_run); } -static inline void _tn_switch_context_if_needed(void) +static inline void _tn_context_switch_pend_if_needed(void) { if (_tn_need_context_switch()){ - _tn_arch_context_switch(); + _tn_arch_context_switch_pend(); } } @@ -487,6 +500,33 @@ TN_Timeout _tn_timer_time_left(struct TN_Timer *timer); } /* extern "C" */ #endif + + +/******************************************************************************* + * tn_eventgrp.c + ******************************************************************************/ + +/** + * Establish link to the event group + */ +enum TN_RCode _tn_eventgrp_link_set( + struct TN_EGrpLink *eventgrp_link, + struct TN_EventGrp *eventgrp, + TN_UWord pattern + ); + +/** + * Reset link to the event group (no matter whether it is already established) + */ +enum TN_RCode _tn_eventgrp_link_reset( + struct TN_EGrpLink *eventgrp_link + ); + +enum TN_RCode _tn_eventgrp_link_manage( + struct TN_EGrpLink *eventgrp_link, + BOOL set + ); + #endif // _TN_INTERNAL_H diff --git a/src/core/tn_mutex.c b/src/core/tn_mutex.c index 60d7054..4fe1836 100644 --- a/src/core/tn_mutex.c +++ b/src/core/tn_mutex.c @@ -120,8 +120,8 @@ static inline enum TN_RCode _check_param_create( } else if (1 && protocol == TN_MUTEX_PROT_CEILING && (0 - || ceil_priority < 1 - || ceil_priority > (TN_PRIORITIES_CNT - 2) + || ceil_priority < 0 + || ceil_priority >= (TN_PRIORITIES_CNT - 1) ) ) { @@ -568,26 +568,25 @@ enum TN_RCode tn_mutex_create( int ceil_priority ) { - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_create(mutex, protocol, ceil_priority); - rc = _check_param_create(mutex, protocol, ceil_priority); if (rc != TN_RC_OK){ - goto out; - } + //-- just return rc as it is + } else { - tn_list_reset(&(mutex->wait_queue)); - tn_list_reset(&(mutex->mutex_queue)); + tn_list_reset(&(mutex->wait_queue)); + tn_list_reset(&(mutex->mutex_queue)); #if TN_MUTEX_DEADLOCK_DETECT - tn_list_reset(&(mutex->deadlock_list)); + tn_list_reset(&(mutex->deadlock_list)); #endif - mutex->protocol = protocol; - mutex->holder = NULL; - mutex->ceil_priority = ceil_priority; - mutex->cnt = 0; - mutex->id_mutex = TN_ID_MUTEX; + mutex->protocol = protocol; + mutex->holder = NULL; + mutex->ceil_priority = ceil_priority; + mutex->cnt = 0; + mutex->id_mutex = TN_ID_MUTEX; + } -out: return rc; } @@ -596,53 +595,47 @@ enum TN_RCode tn_mutex_create( */ enum TN_RCode tn_mutex_delete(struct TN_Mutex *mutex) { - TN_INTSAVE_DATA; - - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(mutex); - rc = _check_param_generic(mutex); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - //-- mutex can be deleted if only it isn't held - if (mutex->holder != NULL && mutex->holder != tn_curr_run_task){ - rc = TN_RC_ILLEGAL_USE; - goto out_ei; - } + //-- mutex can be deleted if only it isn't held + if (mutex->holder != NULL && mutex->holder != tn_curr_run_task){ + rc = TN_RC_ILLEGAL_USE; + } else { - //-- Remove all tasks (if any) from mutex's wait queue - // NOTE: we might sleep there - _tn_wait_queue_notify_deleted(&(mutex->wait_queue)); + //-- Remove all tasks (if any) from mutex's wait queue + _tn_wait_queue_notify_deleted(&(mutex->wait_queue)); - if (mutex->holder != NULL){ - //-- If the mutex is locked - _mutex_do_unlock(mutex); + if (mutex->holder != NULL){ + //-- If the mutex is locked + _mutex_do_unlock(mutex); - //-- NOTE: redundant reset, because it will anyway - // be reset in tn_mutex_create() - // - // Probably we need to remove it. - tn_list_reset(&(mutex->mutex_queue)); - } + //-- NOTE: redundant reset, because it will anyway + // be reset in tn_mutex_create() + // + // Probably we need to remove it. + tn_list_reset(&(mutex->mutex_queue)); + } - mutex->id_mutex = 0; //-- mutex does not exist now + mutex->id_mutex = 0; //-- mutex does not exist now -out_ei: - TN_INT_RESTORE(); + } - //-- we might need to switch context if _tn_wait_queue_notify_deleted() - // has woken up some high-priority task - _tn_switch_context_if_needed(); + TN_INT_RESTORE(); + + //-- we might need to switch context if _tn_wait_queue_notify_deleted() + // has woken up some high-priority task + _tn_context_switch_pend_if_needed(); + } -out: return rc; } @@ -651,94 +644,77 @@ enum TN_RCode tn_mutex_delete(struct TN_Mutex *mutex) */ enum TN_RCode tn_mutex_lock(struct TN_Mutex *mutex, TN_Timeout timeout) { - TN_INTSAVE_DATA; - - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(mutex); BOOL waited_for_mutex = FALSE; - rc = _check_param_generic(mutex); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - if (tn_curr_run_task == mutex->holder){ - //-- mutex is already locked by current task - // if recursive locking enabled (TN_MUTEX_REC), increment lock count, - // otherwise error is returned - __mutex_lock_cnt_change(mutex, 1); - rc = __MUTEX_REC_LOCK_RETVAL; - goto out_ei; - } + if (tn_curr_run_task == mutex->holder){ + //-- mutex is already locked by current task + // if recursive locking enabled (TN_MUTEX_REC), increment lock count, + // otherwise error is returned + __mutex_lock_cnt_change(mutex, 1); + rc = __MUTEX_REC_LOCK_RETVAL; - if ( - mutex->protocol == TN_MUTEX_PROT_CEILING - && tn_curr_run_task->base_priority < mutex->ceil_priority - ) - { - //-- base priority of current task higher - rc = TN_RC_ILLEGAL_USE; - goto out_ei; - } + } else if ( + mutex->protocol == TN_MUTEX_PROT_CEILING + && tn_curr_run_task->base_priority < mutex->ceil_priority + ) + { + //-- base priority of current task higher + rc = TN_RC_ILLEGAL_USE; + + } else if (mutex->holder == NULL){ + //-- mutex is not locked, let's lock it + + //-- TODO: probably, we should add special flat to _mutex_do_lock, + // something like "other_tasks_can_wait", and set it to false here. + // When _mutex_do_lock() is called from _mutex_do_unlock(), this flag + // should be set to true there. + // _mutex_do_lock() should forward this flag to _find_max_priority_by_mutex(), + // and if that flag is false, _find_max_priority_by_mutex() should not + // call _find_max_blocked_priority(). + // We could save about 30 cycles then. =) + _mutex_do_lock(mutex, tn_curr_run_task); - if (mutex->holder == NULL){ - //-- mutex is not locked, let's lock it - - //-- TODO: probably, we should add special flat to _mutex_do_lock, - // something like "other_tasks_can_wait", and set it to false here. - // When _mutex_do_lock() is called from _mutex_do_unlock(), this flag - // should be set to true there. - // _mutex_do_lock() should forward this flag to _find_max_priority_by_mutex(), - // and if that flag is false, _find_max_priority_by_mutex() should not - // call _find_max_blocked_priority(). - // We could save about 30 cycles then. =) - _mutex_do_lock(mutex, tn_curr_run_task); - goto out_ei; - } else { - //-- mutex is already locked - if (timeout == 0){ - //-- in polling mode, just return TN_RC_TIMEOUT - rc = TN_RC_TIMEOUT; - goto out_ei; } else { - //-- timeout specified, so, wait until mutex is free or timeout expired - _add_curr_task_to_mutex_wait_queue(mutex, timeout); - - waited_for_mutex = TRUE; + //-- mutex is already locked - //-- rc will be set later to tn_curr_run_task->task_wait_rc; - goto out_ei; - } - } + if (timeout == 0){ + //-- in polling mode, just return TN_RC_TIMEOUT + rc = TN_RC_TIMEOUT; + } else { + //-- timeout specified, so, wait until mutex is free or timeout expired + _add_curr_task_to_mutex_wait_queue(mutex, timeout); - //-- should never be here - rc = TN_RC_INTERNAL; - goto out_ei; + waited_for_mutex = TRUE; -out_ei: + //-- rc will be set later to tn_curr_run_task->task_wait_rc; + } + } #if TN_DEBUG - if (!_tn_need_context_switch() && waited_for_mutex){ - _TN_FATAL_ERROR(""); - } + if (!_tn_need_context_switch() && waited_for_mutex){ + _TN_FATAL_ERROR(""); + } #endif - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); - if (waited_for_mutex){ - //-- get wait result - rc = tn_curr_run_task->task_wait_rc; + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + if (waited_for_mutex){ + //-- get wait result + rc = tn_curr_run_task->task_wait_rc; + } } -out: return rc; - } /* @@ -755,49 +731,44 @@ enum TN_RCode tn_mutex_lock_polling(struct TN_Mutex *mutex) */ enum TN_RCode tn_mutex_unlock(struct TN_Mutex *mutex) { - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(mutex); - TN_INTSAVE_DATA; - - rc = _check_param_generic(mutex); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - //-- unlocking is enabled only for the owner and already locked mutex - if (tn_curr_run_task != mutex->holder){ - rc = TN_RC_ILLEGAL_USE; - goto out_ei; - } + //-- unlocking is enabled only for the owner and already locked mutex + if (tn_curr_run_task != mutex->holder){ + rc = TN_RC_ILLEGAL_USE; + } else { - __mutex_lock_cnt_change(mutex, -1); + //-- decrement lock count (if recursive locking is enabled) + __mutex_lock_cnt_change(mutex, -1); + + if (mutex->cnt > 0){ + //-- there was recursive lock, so here we just decremented counter, + // but don't unlock the mutex. + // We're done, TN_RC_OK will be returned. + } else if (mutex->cnt < 0){ + //-- should never be here: lock count is negative. + // bug in the kernel. + _TN_FATAL_ERROR(); + } else { + //-- lock counter is 0, so, unlock mutex + _mutex_do_unlock(mutex); + } - if (mutex->cnt > 0){ - //-- there was recursive lock, so here we just decremented counter, - // but don't unlock the mutex. TN_RC_OK will be returned. - goto out_ei; - } else if (mutex->cnt < 0){ - //-- should never be here: lock count is negative. - // bug in RTOS. - _TN_FATAL_ERROR(); - } else { - //-- lock counter is 0, so, unlock mutex - _mutex_do_unlock(mutex); - goto out_ei; - } + } -out_ei: - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + } -out: return rc; } diff --git a/src/core/tn_oldsymbols.h b/src/core/tn_oldsymbols.h index 9163955..320a19e 100644 --- a/src/core/tn_oldsymbols.h +++ b/src/core/tn_oldsymbols.h @@ -325,6 +325,13 @@ typedef struct TN_Sem TN_SEM; /// old TNKernel name for `sizeof(#TN_UWord)` #define TN_ALIG sizeof(TN_UWord) + + + +/// old name for `TN_STACK_ARR_DEF` +#define TN_TASK_STACK_DEF TN_STACK_ARR_DEF + + /******************************************************************************* * PUBLIC FUNCTION PROTOTYPES ******************************************************************************/ diff --git a/src/core/tn_sem.c b/src/core/tn_sem.c index 19ee77e..8f43d42 100644 --- a/src/core/tn_sem.c +++ b/src/core/tn_sem.c @@ -74,6 +74,9 @@ static inline enum TN_RCode _check_param_generic( return rc; } +/** + * Additional param checking when creating semaphore + */ static inline enum TN_RCode _check_param_create( struct TN_Sem *sem, int start_count, @@ -104,81 +107,86 @@ static inline enum TN_RCode _check_param_create( // }}} +/** + * Generic function that performs job from task context + * + * @param sem semaphore to perform job on + * @param p_worker pointer to actual worker function + * @param timeout see `#TN_Timeout` + */ static inline enum TN_RCode _sem_job_perform( struct TN_Sem *sem, int (p_worker)(struct TN_Sem *sem), TN_Timeout timeout ) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(sem); BOOL waited_for_sem = FALSE; - rc = _check_param_generic(sem); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); + } else { + TN_INTSAVE_DATA; - rc = p_worker(sem); + TN_INT_DIS_SAVE(); //-- disable interrupts + rc = p_worker(sem); //-- call actual worker function - if (rc == TN_RC_TIMEOUT && timeout != 0){ - _tn_task_curr_to_wait_action( - &(sem->wait_queue), TN_WAIT_REASON_SEM, timeout - ); + //-- if we should wait, put current task to wait + if (rc == TN_RC_TIMEOUT && timeout != 0){ + _tn_task_curr_to_wait_action( + &(sem->wait_queue), TN_WAIT_REASON_SEM, timeout + ); - //-- rc will be set later thanks to waited_for_sem - waited_for_sem = TRUE; - } + //-- rc will be set later thanks to waited_for_sem + waited_for_sem = TRUE; + } #if TN_DEBUG - if (!_tn_need_context_switch() && waited_for_sem){ - _TN_FATAL_ERROR(""); - } + //-- if we're going to wait, _tn_need_context_switch() must return TRUE + if (!_tn_need_context_switch() && waited_for_sem){ + _TN_FATAL_ERROR(""); + } #endif - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); - if (waited_for_sem){ - //-- get wait result - rc = tn_curr_run_task->task_wait_rc; - } + TN_INT_RESTORE(); //-- restore previous interrupts state + _tn_context_switch_pend_if_needed(); + if (waited_for_sem){ + //-- get wait result + rc = tn_curr_run_task->task_wait_rc; + } -out: + } return rc; } +/** + * Generic function that performs job from interrupt context + * + * @param sem semaphore to perform job on + * @param p_worker pointer to actual worker function + */ static inline enum TN_RCode _sem_job_iperform( struct TN_Sem *sem, int (p_worker)(struct TN_Sem *sem) ) { - TN_INTSAVE_DATA_INT; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(sem); - rc = _check_param_generic(sem); + //-- perform additional params checking (if enabled by TN_CHECK_PARAM) if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_isr_context()){ + //-- just return rc as it is + } else if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_IDIS_SAVE(); - - rc = p_worker(sem); - - TN_INT_IRESTORE(); + } else { + TN_INTSAVE_DATA_INT; -out: + TN_INT_IDIS_SAVE(); //-- disable interrupts + rc = p_worker(sem); //-- call actual worker function + TN_INT_IRESTORE(); //-- restore previous interrupts state + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); + } return rc; } @@ -186,6 +194,7 @@ static inline enum TN_RCode _sem_signal(struct TN_Sem *sem) { enum TN_RCode rc = TN_RC_OK; + //-- wake up first (if any) task from the semaphore wait queue if ( !_tn_task_first_wait_complete( &sem->wait_queue, TN_RC_OK, NULL, NULL, NULL @@ -208,6 +217,9 @@ static inline enum TN_RCode _sem_wait(struct TN_Sem *sem) { enum TN_RCode rc = TN_RC_OK; + //-- decrement semaphore count if possible. + // If not, return TN_RC_TIMEOUT + // (it is handled in _sem_job_perform() / _sem_job_iperform()) if (sem->count > 0){ sem->count--; } else { @@ -234,20 +246,20 @@ enum TN_RCode tn_sem_create( int max_count ) { - enum TN_RCode rc = TN_RC_OK; + //-- perform additional params checking (if enabled by TN_CHECK_PARAM) + enum TN_RCode rc = _check_param_create(sem, start_count, max_count); - rc = _check_param_create(sem, start_count, max_count); if (rc != TN_RC_OK){ - goto out; - } + //-- just return rc as it is + } else { - tn_list_reset(&(sem->wait_queue)); + tn_list_reset(&(sem->wait_queue)); - sem->count = start_count; - sem->max_count = max_count; - sem->id_sem = TN_ID_SEMAPHORE; + sem->count = start_count; + sem->max_count = max_count; + sem->id_sem = TN_ID_SEMAPHORE; -out: + } return rc; } @@ -256,32 +268,28 @@ enum TN_RCode tn_sem_create( */ enum TN_RCode tn_sem_delete(struct TN_Sem * sem) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + //-- perform additional params checking (if enabled by TN_CHECK_PARAM) + enum TN_RCode rc = _check_param_generic(sem); - rc = _check_param_generic(sem); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); - - _tn_wait_queue_notify_deleted(&(sem->wait_queue)); + } else { + TN_INTSAVE_DATA; - sem->id_sem = 0; //-- Semaphore does not exist now + TN_INT_DIS_SAVE(); - TN_INT_RESTORE(); + //-- Remove all tasks from wait queue, returning the TN_RC_DELETED code. + _tn_wait_queue_notify_deleted(&(sem->wait_queue)); - //-- we might need to switch context if _tn_wait_queue_notify_deleted() - // has woken up some high-priority task - _tn_switch_context_if_needed(); + sem->id_sem = 0; //-- Semaphore does not exist now + TN_INT_RESTORE(); -out: + //-- we might need to switch context if _tn_wait_queue_notify_deleted() + // has woken up some high-priority task + _tn_context_switch_pend_if_needed(); + } return rc; } diff --git a/src/core/tn_sys.c b/src/core/tn_sys.c index 2cf67ae..cec854a 100644 --- a/src/core/tn_sys.c +++ b/src/core/tn_sys.c @@ -89,7 +89,7 @@ void *tn_int_sp; //-- System tasks -//-- idle task - priority (TN_PRIORITIES_CNT-1) - lowest +//-- idle task - priority (TN_PRIORITIES_CNT - 1) - lowest struct TN_Task tn_idle_task; static void _idle_task_body(void * par); @@ -171,7 +171,7 @@ static inline enum TN_RCode _idle_task_create( return tn_task_create( (struct TN_Task*)&tn_idle_task, //-- task TCB _idle_task_body, //-- task function - TN_PRIORITIES_CNT - 1, //-- task priority + TN_PRIORITIES_CNT - 1, //-- task priority idle_task_stack, //-- task stack idle_task_stack_size, //-- task stack size // (in int, not bytes) @@ -217,7 +217,7 @@ void tn_sys_start( //-- initial system flags: no flags set (see enum TN_StateFlag) tn_sys_state = (0); - //-- reset bitmask of prioritis with runnable tasks + //-- reset bitmask of priorities with runnable tasks tn_ready_to_run_bmp = 0; //-- reset system time @@ -238,7 +238,7 @@ void tn_sys_start( int_stack[i] = TN_FILL_STACK_VAL; } - //-- pre-decrement stack + //-- set interrupt's top of the stack tn_int_sp = _tn_arch_stack_top_get( int_stack, int_stack_size @@ -279,8 +279,12 @@ void tn_sys_start( // (by user-provided callback) cb_user_task_create(); + //-- set flag that system is running + // (well, it will be running soon actually) + tn_sys_state |= TN_STATE_FLAG__SYS_RUNNING; + //-- Run OS - first context switch - _tn_arch_system_start(); + _tn_arch_context_switch_now_nosave(); } @@ -290,28 +294,28 @@ void tn_sys_start( */ enum TN_RCode tn_tick_int_processing(void) { - TN_INTSAVE_DATA_INT; enum TN_RCode rc = TN_RC_OK; if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA_INT; - TN_INT_IDIS_SAVE(); + TN_INT_IDIS_SAVE(); - //-- increment system timer - tn_sys_time_count++; + //-- increment system timer + tn_sys_time_count++; - //-- manage round-robin (if used) - _round_robin_manage(); + //-- manage round-robin (if used) + _round_robin_manage(); - //-- manage timers - _tn_timers_tick_proceed(); + //-- manage timers + _tn_timers_tick_proceed(); - TN_INT_IRESTORE(); + TN_INT_IRESTORE(); + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); -out: + } return rc; } @@ -322,26 +326,20 @@ enum TN_RCode tn_sys_tslice_set(int priority, int ticks) { enum TN_RCode rc = TN_RC_OK; - TN_INTSAVE_DATA; if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - if ( priority <= 0 || priority >= (TN_PRIORITIES_CNT - 1) - || ticks < 0 || ticks > TN_MAX_TIME_SLICE) + } else if (0 + || priority < 0 || priority >= (TN_PRIORITIES_CNT - 1) + || ticks < 0 || ticks > TN_MAX_TIME_SLICE) { rc = TN_RC_WPARAM; - goto out; - } - - TN_INT_DIS_SAVE(); - - tn_tslice_ticks[priority] = ticks; - - TN_INT_RESTORE(); + } else { + TN_INTSAVE_DATA; -out: + TN_INT_DIS_SAVE(); + tn_tslice_ticks[priority] = ticks; + TN_INT_RESTORE(); + } return rc; } diff --git a/src/core/tn_sys.h b/src/core/tn_sys.h index eea4a15..a448802 100644 --- a/src/core/tn_sys.h +++ b/src/core/tn_sys.h @@ -73,6 +73,22 @@ struct TN_Mutex; * DEFINITIONS ******************************************************************************/ +/** + * Convenience macro for the definition of stack array. See + * `tn_task_create()` for the usage example. + * + * @param name + * C variable name of the array + * @param size + * size of the stack array in words (`#TN_UWord`), not in bytes. + */ +#define TN_STACK_ARR_DEF(name, size) \ + TN_ARCH_STK_ATTR_BEFORE \ + TN_UWord name[ (size) ] \ + TN_ARCH_STK_ATTR_AFTER + + + /******************************************************************************* * PUBLIC TYPES @@ -215,14 +231,14 @@ typedef void (TN_CBDeadlock)( * * @param idle_task_stack * Pointer to array for idle task stack. - * User must either use the macro `TN_TASK_STACK_DEF()` for the definition + * User must either use the macro `TN_STACK_ARR_DEF()` for the definition * of stack array, or allocate it manually as an array of `#TN_UWord` with * `#TN_ARCH_STK_ATTR_BEFORE` and `#TN_ARCH_STK_ATTR_AFTER` macros. * @param idle_task_stack_size * Size of idle task stack, in words (`#TN_UWord`) * @param int_stack * Pointer to array for interrupt stack. - * User must either use the macro `TN_TASK_STACK_DEF()` for the definition + * User must either use the macro `TN_STACK_ARR_DEF()` for the definition * of stack array, or allocate it manually as an array of `#TN_UWord` with * `#TN_ARCH_STK_ATTR_BEFORE` and `#TN_ARCH_STK_ATTR_AFTER` macros. * @param int_stack_size @@ -306,7 +322,8 @@ unsigned int tn_sys_time_get(void); * $(TN_CALL_FROM_MAIN) * $(TN_LEGEND_LINK) * - * **Note:** this function should be called before `tn_sys_start()` + * **Note:** this function should be called from `main()`, before + * `tn_sys_start()`. * * @param cb * Pointer to user-provided callback function. diff --git a/src/core/tn_tasks.c b/src/core/tn_tasks.c index b177730..714cccd 100644 --- a/src/core/tn_tasks.c +++ b/src/core/tn_tasks.c @@ -204,27 +204,23 @@ static inline enum TN_RCode _task_job_perform( int (p_worker)(struct TN_Task *task) ) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(task); - rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); + } else { + //-- proceed to real job + TN_INTSAVE_DATA; - rc = p_worker(task); + TN_INT_DIS_SAVE(); - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + rc = p_worker(task); -out: + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + } return rc; } @@ -233,26 +229,23 @@ static inline enum TN_RCode _task_job_iperform( int (p_worker)(struct TN_Task *task) ) { - TN_INTSAVE_DATA_INT; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(task); - rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_isr_context()){ + //-- just return rc as it is + } else if (!tn_is_isr_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA_INT; - TN_INT_IDIS_SAVE(); + TN_INT_IDIS_SAVE(); - rc = p_worker(task); + rc = p_worker(task); - TN_INT_IRESTORE(); + TN_INT_IRESTORE(); + _TN_CONTEXT_SWITCH_IPEND_IF_NEEDED(); -out: + } return rc; } @@ -383,9 +376,6 @@ enum TN_RCode tn_task_create( //-- Lightweight checking of system tasks recreation if (0 || (priority == (TN_PRIORITIES_CNT - 1) && !(opts & TN_TASK_CREATE_OPT_IDLE)) - || (priority == 0) //-- there's no more timer task in the kernel, - // but for a kind of compatibility - // it's better to disallow tasks with priority 0 ) { return TN_RC_WPARAM; @@ -482,37 +472,33 @@ enum TN_RCode tn_task_create( */ enum TN_RCode tn_task_suspend(struct TN_Task *task) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(task); - rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; + + TN_INT_DIS_SAVE(); - TN_INT_DIS_SAVE(); + if (_tn_task_is_suspended(task) || _tn_task_is_dormant(task)){ + rc = TN_RC_WSTATE; + } else { - if (_tn_task_is_suspended(task) || _tn_task_is_dormant(task)){ - rc = TN_RC_WSTATE; - goto out_ei; - } + if (_tn_task_is_runnable(task)){ + _tn_task_clear_runnable(task); + } - if (_tn_task_is_runnable(task)){ - _tn_task_clear_runnable(task); - } + _tn_task_set_suspended(task); - _tn_task_set_suspended(task); + } -out_ei: - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); -out: + } return rc; } @@ -521,39 +507,35 @@ enum TN_RCode tn_task_suspend(struct TN_Task *task) */ enum TN_RCode tn_task_resume(struct TN_Task *task) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(task); - rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - if (!_tn_task_is_suspended(task)){ - rc = TN_RC_WSTATE; - goto out_ei; - } + if (!_tn_task_is_suspended(task)){ + rc = TN_RC_WSTATE; + } else { - _tn_task_clear_suspended(task); + _tn_task_clear_suspended(task); - if (!_tn_task_is_waiting(task)){ - //-- The task is not in the WAIT-SUSPEND state, - // so we need to make it runnable and probably switch context - _tn_task_set_runnable(task); - } + if (!_tn_task_is_waiting(task)){ + //-- The task is not in the WAIT-SUSPEND state, + // so we need to make it runnable and probably switch context + _tn_task_set_runnable(task); + } + + } -out_ei: - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); -out: + } return rc; } @@ -563,30 +545,26 @@ enum TN_RCode tn_task_resume(struct TN_Task *task) */ enum TN_RCode tn_task_sleep(TN_Timeout timeout) { - TN_INTSAVE_DATA; enum TN_RCode rc; if (timeout == 0){ rc = TN_RC_TIMEOUT; - goto out; - } - - if (!tn_is_task_context()){ + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } + } else { + TN_INTSAVE_DATA; - TN_INT_DIS_SAVE(); + TN_INT_DIS_SAVE(); - _tn_task_curr_to_wait_action(NULL, TN_WAIT_REASON_SLEEP, timeout); + _tn_task_curr_to_wait_action(NULL, TN_WAIT_REASON_SLEEP, timeout); - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); - rc = tn_curr_run_task->task_wait_rc; + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + rc = tn_curr_run_task->task_wait_rc; -out: - return rc; + } + return rc; } /* @@ -643,33 +621,33 @@ enum TN_RCode tn_task_irelease_wait(struct TN_Task *task) void tn_task_exit(enum TN_TaskExitOpt opts) { struct TN_Task *task; - - //-- it is here only for TN_INT_DIS_SAVE() normal operation, - // but actually we don't need to save current interrupt status: - // this function never returns, and interrupt status is restored - // from different task's stack inside `_tn_arch_context_switch_exit()` - // call. - TN_INTSAVE_DATA; if (!tn_is_task_context()){ - goto out; - } + //-- do nothing, just return + } else { - TN_INT_DIS_SAVE(); + //-- it is here only for TN_INT_DIS_SAVE() normal operation, + // but actually we don't need to save current interrupt status: + // this function never returns, and interrupt status is restored + // from different task's stack inside + // `_tn_arch_context_switch_now_nosave()` call. + TN_INTSAVE_DATA; - task = tn_curr_run_task; - _tn_task_clear_runnable(task); + TN_INT_DIS_SAVE(); - _task_terminate(task); + task = tn_curr_run_task; + _tn_task_clear_runnable(task); - if ((opts & TN_TASK_EXIT_OPT_DELETE)){ - _task_delete(task); - } + _task_terminate(task); - //-- interrupts will be enabled inside _tn_arch_context_switch_exit() - _tn_arch_context_switch_exit(); + if ((opts & TN_TASK_EXIT_OPT_DELETE)){ + _task_delete(task); + } + + //-- interrupts will be enabled inside _tn_arch_context_switch_now_nosave() + _tn_arch_context_switch_now_nosave(); + } -out: return; } @@ -678,53 +656,46 @@ void tn_task_exit(enum TN_TaskExitOpt opts) */ enum TN_RCode tn_task_terminate(struct TN_Task *task) { - TN_INTSAVE_DATA; - - enum TN_RCode rc; + enum TN_RCode rc = _check_param_generic(task); - rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ - rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); - - rc = TN_RC_OK; - - if (_tn_task_is_dormant(task)){ - //-- The task is already terminated - rc = TN_RC_WSTATE; - } else if (tn_curr_run_task == task){ - //-- Cannot terminate currently running task - // (use tn_task_exit() instead) + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; } else { + TN_INTSAVE_DATA; - if (_tn_task_is_runnable(task)){ - _tn_task_clear_runnable(task); - } else if (_tn_task_is_waiting(task)){ - _tn_task_clear_waiting( - task, - TN_RC_OK //-- doesn't matter: nobody will read it - ); - } + TN_INT_DIS_SAVE(); - if (_tn_task_is_suspended(task)){ - _tn_task_clear_suspended(task); + if (_tn_task_is_dormant(task)){ + //-- The task is already terminated + rc = TN_RC_WSTATE; + } else if (tn_curr_run_task == task){ + //-- Cannot terminate currently running task + // (use tn_task_exit() instead) + rc = TN_RC_WCONTEXT; + } else { + + if (_tn_task_is_runnable(task)){ + _tn_task_clear_runnable(task); + } else if (_tn_task_is_waiting(task)){ + _tn_task_clear_waiting( + task, + TN_RC_OK //-- doesn't matter: nobody will read it + ); + } + + if (_tn_task_is_suspended(task)){ + _tn_task_clear_suspended(task); + } + + _task_terminate(task); } - _task_terminate(task); - } - - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); -out: + } return rc; } @@ -733,26 +704,20 @@ enum TN_RCode tn_task_terminate(struct TN_Task *task) */ enum TN_RCode tn_task_delete(struct TN_Task *task) { - TN_INTSAVE_DATA; - enum TN_RCode rc; + enum TN_RCode rc = _check_param_generic(task); - rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); - - rc = _task_delete(task); + } else { + TN_INTSAVE_DATA; - TN_INT_RESTORE(); + TN_INT_DIS_SAVE(); + rc = _task_delete(task); + TN_INT_RESTORE(); -out: + } return rc; } @@ -766,23 +731,16 @@ enum TN_RCode tn_task_state_get( { enum TN_RCode rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - TN_INTSAVE_DATA; - - if (!tn_is_task_context()){ + //-- just return rc as it is + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); - - *p_state = task->task_state; - - TN_INT_RESTORE(); + } else { + TN_INTSAVE_DATA; -out: + TN_INT_DIS_SAVE(); + *p_state = task->task_state; + TN_INT_RESTORE(); + } return rc; } @@ -791,42 +749,34 @@ enum TN_RCode tn_task_state_get( */ enum TN_RCode tn_task_change_priority(struct TN_Task *task, int new_priority) { - TN_INTSAVE_DATA; - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_generic(task); - rc = _check_param_generic(task); if (rc != TN_RC_OK){ - goto out; - } - - if (new_priority < 0 || new_priority >= (TN_PRIORITIES_CNT - 1)){ + //-- just return rc as it is + } else if (new_priority < 0 || new_priority >= (TN_PRIORITIES_CNT - 1)){ rc = TN_RC_WPARAM; - goto out; - } - - if (!tn_is_task_context()){ + } else if (!tn_is_task_context()){ rc = TN_RC_WCONTEXT; - goto out; - } - - TN_INT_DIS_SAVE(); + } else { + TN_INTSAVE_DATA; - if(new_priority == 0){ - new_priority = task->base_priority; - } + TN_INT_DIS_SAVE(); - rc = TN_RC_OK; + if (new_priority == 0){ + new_priority = task->base_priority; + } - if (_tn_task_is_dormant(task)){ - rc = TN_RC_WSTATE; - } else { - _tn_change_task_priority(task, new_priority); - } + rc = TN_RC_OK; - TN_INT_RESTORE(); - _tn_switch_context_if_needed(); + if (_tn_task_is_dormant(task)){ + rc = TN_RC_WSTATE; + } else { + _tn_change_task_priority(task, new_priority); + } -out: + TN_INT_RESTORE(); + _tn_context_switch_pend_if_needed(); + } return rc; } @@ -1071,7 +1021,7 @@ void _tn_task_clear_dormant(struct TN_Task *task) //--- Init task stack, save pointer to task top of stack, // when not running - task->task_stk = _tn_arch_stack_init( + task->stack_top = _tn_arch_stack_init( task->task_func_addr, task->base_stack_top, task->task_func_param diff --git a/src/core/tn_tasks.h b/src/core/tn_tasks.h index 85357ba..cd372dd 100644 --- a/src/core/tn_tasks.h +++ b/src/core/tn_tasks.h @@ -171,10 +171,10 @@ enum TN_TaskExitOpt { * Task */ struct TN_Task { - /// pointer to task's current stack pointer; + /// pointer to task's current top of the stack; /// Note that this field **must** be a first field in the struct, /// this fact is exploited by platform-specific routines. - unsigned int *task_stk; + TN_UWord *stack_top; /// /// queue is used to include task in ready/wait lists struct TN_ListItem task_queue; @@ -298,19 +298,6 @@ struct TN_Task { * DEFINITIONS ******************************************************************************/ -/** - * Convenience macro for the definition of stack array. See - * `tn_task_create()` for the usage example. - * - * @param name - * C variable name of the array - * @param size - * size of the stack array in words (`#TN_UWord`), not in bytes. - */ -#define TN_TASK_STACK_DEF(name, size) \ - TN_ARCH_STK_ATTR_BEFORE \ - TN_UWord name[ (size) ] \ - TN_ARCH_STK_ATTR_AFTER @@ -331,9 +318,9 @@ struct TN_Task { * * struct TN_Task my_task; * - * //-- define stack array, we use convenience macro TN_TASK_STACK_DEF() + * //-- define stack array, we use convenience macro TN_STACK_ARR_DEF() * // for that - * TN_TASK_STACK_DEF(my_task_stack, MY_TASK_STACK_SIZE); + * TN_STACK_ARR_DEF(my_task_stack, MY_TASK_STACK_SIZE); * * void my_task_body(void *param) * { @@ -379,7 +366,7 @@ struct TN_Task { * Must be > `0` and < `(#TN_PRIORITIES_CNT - 1)`. * @param task_stack_low_addr * Pointer to the stack for task. - * User must either use the macro `TN_TASK_STACK_DEF()` for the definition + * User must either use the macro `TN_STACK_ARR_DEF()` for the definition * of stack array, or allocate it manually as an array of `#TN_UWord` with * `#TN_ARCH_STK_ATTR_BEFORE` and `#TN_ARCH_STK_ATTR_AFTER` macros. * @param task_stack_size diff --git a/src/core/tn_timer.c b/src/core/tn_timer.c index d39e307..e79da5a 100644 --- a/src/core/tn_timer.c +++ b/src/core/tn_timer.c @@ -144,16 +144,14 @@ enum TN_RCode tn_timer_create( void *p_user_data ) { - enum TN_RCode rc = TN_RC_OK; + enum TN_RCode rc = _check_param_create(timer, func); - rc = _check_param_create(timer, func); if (rc != TN_RC_OK){ - goto out; + //-- just return rc as it is + } else { + rc = _tn_timer_create(timer, func, p_user_data); } - rc = _tn_timer_create(timer, func, p_user_data); - -out: return rc; } @@ -480,15 +478,15 @@ enum TN_RCode _tn_timer_create( enum TN_RCode rc = _tn_timer_set_func(timer, func, p_user_data); if (rc != TN_RC_OK){ - goto out; - } + //-- just return rc as it is + } else { - tn_list_reset(&(timer->timer_queue)); + tn_list_reset(&(timer->timer_queue)); - timer->timeout_cur = 0; - timer->id_timer = TN_ID_TIMER; + timer->timeout_cur = 0; + timer->id_timer = TN_ID_TIMER; -out: + } return rc; } diff --git a/src/tn.h b/src/tn.h index 3f4c08e..ed4c359 100644 --- a/src/tn.h +++ b/src/tn.h @@ -57,6 +57,7 @@ #include "core/tn_sem.h" #include "core/tn_tasks.h" #include "core/tn_list.h" +#include "core/tn_timer.h" //-- include old symbols for compatibility with old projects diff --git a/src/tn_cfg_default.h b/src/tn_cfg_default.h index c88e70e..d14d3d9 100644 --- a/src/tn_cfg_default.h +++ b/src/tn_cfg_default.h @@ -72,6 +72,28 @@ * USER-DEFINED OPTIONS ******************************************************************************/ +/** + * Number of priorities that can be used by application, plus one for idle task + * (which has the lowest priority). This value can't be higher than + * architecture-dependent value `#TN_PRIORITIES_MAX_CNT`, which typically + * equals to width of `int` type. So, for 32-bit systems, max number of + * priorities is 32. + * + * But usually, application needs much less: I can imagine **at most** 4-5 + * different priorities, plus one for the idle task. + * + * Do note also that each possible priority level takes RAM: two pointers for + * linked list and one `short` for time slice value, so on 32-bit system it + * takes 10 bytes. So, with default value of 32 priorities available, it takes + * 320 bytes. If you set it, say, to 5, you save `270` bytes, which might be + * notable. + * + * Default: `#TN_PRIORITIES_MAX_CNT`. + */ +#ifndef TN_PRIORITIES_CNT +# define TN_PRIORITIES_CNT TN_PRIORITIES_MAX_CNT +#endif + /** * Enables additional param checking for most of the system functions. * It's surely useful for debug, but probably better to remove in release. diff --git a/stuff/doc_pages/changelog.dox b/stuff/doc_pages/changelog.dox index 7846f84..2448be7 100644 --- a/stuff/doc_pages/changelog.dox +++ b/stuff/doc_pages/changelog.dox @@ -5,6 +5,34 @@ TNeoKernel changelog +\section changelog_v1_03 v1.03 + +Release date: 2014-10-20. + + + - Added a capability to connect an \ref tn_eventgrp.h "event group" to other + system objects, particularly to the \ref tn_dqueue.h "queue". This offers a + way to wait for messages from multiple queues with just a single system + call. Refer to the section \ref eventgrp_connect for details. Example + project that demonstrates that technique is also available: + `examples/queue_eventgrp_conn`. + - PIC32 Interrupts: this isn't a mandatory anymore to use kernel-provided macros + `tn_soft_isr()` or `tn_srs_isr()`: interrupts can be defined with standard + way too: this particular ISR will use task's stack instead of interrupt + stack, therefore it takes much more RAM and works a bit faster. There are + no additional constraints on ISR defined without kernel-provided macro: in + either ISR, you can call the same set of kernel services. Refer + to the page \ref interrupts for details. + - Priority 0 is now allowed to use by application (in the original TNKernel, + it was reserved for the timer task, but TNeoKernel \ref + tnkernel_diff_timer_task "does not have timer task") + - Application is now available to specify how many priority levels does it + need for, it helps to save a bit of RAM. For details, refer to + `#TN_PRIORITIES_CNT`. + - Added example project `examples/queue` that demonstrates the pattern on + how to use \ref tn_dqueue.h "queue" together with \ref tn_fmem.h + "fixed memory pool" effectively. + \section changelog_v1_02 v1.02 Release date: 2014-10-14. diff --git a/stuff/doc_pages/foreword.dox b/stuff/doc_pages/foreword.dox index 2971837..d6297ef 100644 --- a/stuff/doc_pages/foreword.dox +++ b/stuff/doc_pages/foreword.dox @@ -49,4 +49,6 @@ point to the real-time kernels for me. I would also like to thank my chiefs in the [ORION](http://orionspb.ru/) company, Alexey Morozov and Alexey Gromov, for being flexible about my time. +For the full thanks list, refer to the page \ref thanks. + */ diff --git a/stuff/doc_pages/interrupts.dox b/stuff/doc_pages/interrupts.dox new file mode 100644 index 0000000..200ad3d --- /dev/null +++ b/stuff/doc_pages/interrupts.dox @@ -0,0 +1,54 @@ +/** +\page interrupts Interrupts + +\tableofcontents + +\section interrupt_stack Interrupt stack + +TNeoKernel provides a separate stack for interrupt handlers. This approach +could save a lot of RAM: interrupt can happen at any moment of time, and if +there's no separate interrupt stack, then each task should have enough stack +space for the worse case of interrupt nesting. + +Assume application's ISRs take max 64 words (64 * 4 = 256 bytes on PIC32) and application has 4 +tasks (plus one idle task). Then, each of 5 tasks must have 64 words for +interrupts: 64 * 5 * 4 = 1280 bytes of RAM just for 64 words for ISR. + +With separate stack for interrupts, these 64 words should be allocated just once. +Interrupt stack array should be given to `#tn_sys_start()`. For additional +information, refer to the section \ref starting_the_kernel. + +In order to make particular ISR use separate interrupt stack, this ISR should +be defined by kernel-provided macro, which is platform-dependent: see \ref +pic32_details. + +In spite of the fact that the kernel provides separate stack for interrupt, +this isn't a mandatory: you're able to define your ISR in a standard way, +making it use stask of interrupted task and work a bit faster. There is always +a tradeoff. There are **no additional constraints** on ISR defined without +kernel-provided macro: in either ISR, you can call the same set of kernel +services. + +When you make a decision on whether particular ISR should use separate stack, +consider the following: + +- When ISR is defined in a standard way, and no function is called from that + ISR, only necessary registers are saved on stack. If you have such an ISR + (that doesn't call any function), and this ISR should work very fast, + consider using standard way instead of kernel-provided macro. +- When ISR is defined in a standard way, but it calls any function and doesn't + use shadow register set, compiler saves (almost) full context **on the task's + stack**, because it doesn't know which registers are used inside the function. + In this case, it usually makes more sense to use kernel-provided macro (see + below). +- Kernel-provided interrupt macros switch stack pointer between interrupt stack + and task stack automatically, it takes additional time: e.g. on PIC32 it's + about 20 cycles. +- Kernel-provided interrupt macro that doesn't use shadow register set always + saves (almost) full context **on the interrupt stack**, independently of + whether any function is called from an ISR. +- Kernel-provided interrupt macro that uses shadow register set saves a little + amount of registers **on the interrupt stack**. + + +*/ diff --git a/stuff/doc_pages/mainpage.dox b/stuff/doc_pages/mainpage.dox index 44b13fc..0a39bea 100644 --- a/stuff/doc_pages/mainpage.dox +++ b/stuff/doc_pages/mainpage.dox @@ -22,6 +22,7 @@ Related pages: - \ref foreword - \ref quick_guide + - \ref interrupts - \ref building - \ref pic32_details - \ref why_reimplement @@ -29,6 +30,7 @@ Related pages: - \ref unit_tests - \ref plans - \ref changelog + - \ref thanks - \ref legend API reference: diff --git a/stuff/doc_pages/pic32_details.dox b/stuff/doc_pages/pic32_details.dox index ccf0006..cda32e5 100644 --- a/stuff/doc_pages/pic32_details.dox +++ b/stuff/doc_pages/pic32_details.dox @@ -21,10 +21,23 @@ configured to use shadow register sets. \attention if tneokernel is built as a separate library, then the file `src/arch/pic32/tn_arch_pic32_int_vec1.S` must be included in the main project -itself, in order to dispatch vector1 (core software interrupt 0) correctly. +itself, in order to dispatch vector1 (core software interrupt 0) correctly. Do +note that if we include this file in the TNeoKernel library project, it doesn't +work for vector, unfortunately. + +\attention If you forgot to include this file, you got an error on the link step, +like this: +\code +undefined reference to `_you_should_add_file___tn_arch_pic32_int_vec1_S___to_the_project' +\endcode +Which is much more informative than if you just get to `_DefaultInterrupt` when +it's time to switch context. \section pic32_interrupts Interrupts +For detailed information about interrupts in TNeoKernel, refer to the page \ref +interrupts. + PIC32 port supports nested interrupts. The kernel provides C-language macros for calling C-language interrupt service routines, which can use either MIPS32 or MIPS16e mode. Both software and shadow register interrupt context @@ -44,15 +57,17 @@ tn_srs_isr(_UART_1_VECTOR) } \endcode -\attention every ISR in your system should use kernel-provided macro, either -`tn_soft_isr` or `tn_srs_isr`, instead of `__ISR` macro. +Alternatively, you can define your ISR in a standard way, like this: -\section pic32_interrupt_stack Interrupt stack +\code{.c} +void __ISR(_TIMER_1_VECTOR) timer_1_isr(void) +{ + /* here is your ISR code, including clearing of interrupt flag, and so on */ +} +\endcode -PIC32 uses a separate stack for interrupt handlers. Switching stack pointers is -done automatically in the ISR handler wrapper macros. User should allocate -array for interrupt stack and pass it as an argument to `tn_sys_start()`. -Refer to the \ref starting_the_kernel "Starting the kernel" section for the -usage example and additional comments. +Then, context is saved on the task's stack instead of interrupt stack (and +takes therefore much more RAM), but you save about 20 cycles for each +interrupt. See the page \ref interrupts for details. */ diff --git a/stuff/doc_pages/quick_guide.dox b/stuff/doc_pages/quick_guide.dox index a8dcd3b..2e5a804 100644 --- a/stuff/doc_pages/quick_guide.dox +++ b/stuff/doc_pages/quick_guide.dox @@ -29,7 +29,7 @@ tn_soft_isr(_TIMER_5_VECTOR) ### Quick guide on startup process - You allocate arrays for idle task stack and interrupt stack, there is a - convenience macro `TN_TASK_STACK_DEF()` for that. It is good idea to + convenience macro `TN_STACK_ARR_DEF()` for that. It is good idea to consult the `#TN_MIN_STACK_SIZE` to determine stack sizes (see example below). - You provide callback function like `void init_task_create(void) { ... }`, @@ -68,7 +68,7 @@ tn_soft_isr(_TIMER_5_VECTOR) ### Basic example for PIC32 This example project can be found in the TNeoKernel repository, in the -`examples/pic32/basic` directory. +`examples/basic/arch/pic32` directory. \attention Before trying to build examples, please read \ref building page carefully: you need to copy configuration file in the tneokernel directory to @@ -76,7 +76,7 @@ build it. Each example has `tn_cfg_appl.h` file, and you should either create a symbolic link to this file from `tneokernel/src/tn_cfg.h` or just copy this file as `tneokernel/src/tn_cfg.h`. -\include pic32/basic/src/tn_pic32_example_basic.c +\include basic/arch/pic32/tn_pic32_example_basic.c \section round_robin Round-robin scheduling diff --git a/stuff/doc_pages/thanks.dox b/stuff/doc_pages/thanks.dox new file mode 100644 index 0000000..f43b615 --- /dev/null +++ b/stuff/doc_pages/thanks.dox @@ -0,0 +1,21 @@ +/** +\page thanks Thanks + +\tableofcontents + +There are people that I would like to thank: + +- **Yuri Tiomkin** - for original TNKernel. Although the implementation of TNKernel is far from perfect in my opinion, the +ideas behind the implementation are generally really nice (that's why I decided +to reimplement it instead of starting from scratch), and it was great entry +point to the real-time kernels for me; +- **Anders Montonen** - for original implementation of TNKernel-PIC32 port; +- **Alex Borisov** - for TNKernel port which I was using for a long time; +- **Alexey Morozov** and **Alexey Gromov**, my chiefs in the [ORION](http://orionspb.ru/) +company, for being flexible about my time; +- **Robert White** - for nice ideas. + +Thank you guys. TNeoKernel would never be what it is without you. + +*/ + diff --git a/stuff/doc_pages/tnkernel_diff.dox b/stuff/doc_pages/tnkernel_diff.dox index 90dce55..81bb1c6 100644 --- a/stuff/doc_pages/tnkernel_diff.dox +++ b/stuff/doc_pages/tnkernel_diff.dox @@ -152,6 +152,14 @@ Sometimes I feel lack of mutexes that allow recursive locking. I know there are Sometimes they are really useful though (say, if you want to use some third-party library that requires locking primitives to be recursive), so I decided to implement an option for that: `#TN_MUTEX_REC`. If it is non-zero, mutexes allow recursive locking; otherwise you get `#TN_RC_ILLEGAL_USE` when you try to lock mutex that is already locked by this task. Default value: `1`. +\subsection tnkernel_diff_mutex_deadlock Mutex deadlock detection + +TNeoKernel can notify you by calling your callback function if deadlock occurs. See: + + - compile-time option `#TN_MUTEX_DEADLOCK_DETECT`, + - `tn_callback_deadlock_set()`, + - `#TN_CBDeadlock`. + \subsection tnkernel_diff_new_functions New system services added Several system services were added: @@ -194,7 +202,7 @@ You can still use "manual" definition of stack arrays, like that: TN_ARCH_STK_ATTR_AFTER; \endcode -Although it is recommended to use convenience macro for that: `TN_TASK_STACK_DEF()`. See `tn_task_create()` for the usage example. +Although it is recommended to use convenience macro for that: `TN_STACK_ARR_DEF()`. See `tn_task_create()` for the usage example. \subsection tnkernel_new_api__convenience_macros_fmem Convenience macros for fixed memory block pool buffers definition diff --git a/stuff/doc_pages/why_reimplement.dox b/stuff/doc_pages/why_reimplement.dox index 076c1e1..0a701b3 100644 --- a/stuff/doc_pages/why_reimplement.dox +++ b/stuff/doc_pages/why_reimplement.dox @@ -44,9 +44,6 @@ int my_function(void) } \endcode -The code above is simplified; in the real TNKernel code, things are usually -more complicated. - If you have multiple `return` statements or, even more, if you have to perform some action before return (`tn_enable_interrupt()` in the example above), it's great job for `goto`: @@ -54,28 +51,97 @@ great job for `goto`: \code{.c} int my_function(void) { - int ret = SUCCESS; + int rc = SUCCESS; tn_disable_interrupt(); //-- do something if (error()){ //-- do something - ret = ERROR; + rc = ERROR; goto out; } //-- do something out: tn_enable_interrupt(); - return ret; + return rc; } \endcode I understand there are a lot of people that don't agree with me on this (mostly because they religiously believe that `goto` is unconditionally evil), but -anyway I decided to explain it. And yeah, original TNKernel code contains zero -`goto` statement occurrences, but a lot of code like the one in first example -above. +anyway I decided to explain it. And, let's go further: + +While multiple `goto`-s to single label are better than multiple `return` +statements, it becomes less useful as we get to something more complicated. +Imagine we need to perform some checks *before* disabling interrupts, and +perform some other checks *after* disabling them. Then, we have to create two +labels, like that: + +\code{.c} +int my_function(void) +{ + int rc = SUCCESS; + + if (error1()){ + rc = ERROR1; + goto out; + } + + tn_disable_interrupt(); + + if (error2()){ + rc = ERROR2; + goto out_ei; + } + + if (error3()){ + rc = ERROR3; + goto out_ei; + } + + //-- perform job + +out_ei: + tn_enable_interrupt(); + +out: + return rc; +} +\endcode + +For each error handling, we should specify the label explicitly, and it's easy +to mix labels up, especially if we add some new case to check in the future. +So, I believe this approach is a superior: + +\code{.c} +int my_function(void) +{ + int rc = SUCCESS; + + if (error1()){ + rc = ERROR1; + } else { + tn_disable_interrupt(); + + if (error2()){ + rc = ERROR2; + } else if (error3()){ + rc = ERROR3; + } else { + //-- perform job + } + + tn_enable_interrupt(); + } + + return rc; +} +\endcode + +Then, for each new error handling, we should just add new `else if` block, and +there's no need to care where to go if error happened. Let the compiler do the +branching job for you. More, this code looks more compact. Needless to say, I already found such bug in original TNKernel 2.7 code. The function `tn_sys_tslice_ticks()` looks as follows: diff --git a/stuff/doxygen/tn_doxyfile b/stuff/doxygen/tn_doxyfile index 183090b..cadbdb3 100644 --- a/stuff/doxygen/tn_doxyfile +++ b/stuff/doxygen/tn_doxyfile @@ -763,6 +763,7 @@ INPUT = ../../src \ ../../stuff/doc_pages/mainpage.dox \ ../../stuff/doc_pages/foreword.dox \ ../../stuff/doc_pages/quick_guide.dox \ + ../../stuff/doc_pages/interrupts.dox \ ../../stuff/doc_pages/building.dox \ ../../stuff/doc_pages/pic32_details.dox \ ../../stuff/doc_pages/why_reimplement.dox \ @@ -770,6 +771,7 @@ INPUT = ../../src \ ../../stuff/doc_pages/unit_tests.dox \ ../../stuff/doc_pages/plans.dox \ ../../stuff/doc_pages/changelog.dox \ + ../../stuff/doc_pages/thanks.dox \ ../../stuff/doc_pages/legend.dox # This tag can be used to specify the character encoding of the source files diff --git a/stuff/vimwiki/index.wiki b/stuff/vimwiki/index.wiki index aabffa2..a0163c7 100644 --- a/stuff/vimwiki/index.wiki +++ b/stuff/vimwiki/index.wiki @@ -11,8 +11,12 @@ == Plans == - * [ ] Make TN_PRIORITIES_CNT customizable. For 32-bit systems, TN_PRIORITIES_CNT is 32, and since we have struct TN_ListItem tn_ready_list[TN_PRIORITIES_CNT] and unsigned short tn_tslice_ticks[TN_PRIORITIES_CNT], it takes 10 bytes for each priority. So, overhead is 320 bytes, which is notable. We usually need about 5 priorities, so, it would take 50 bytes. - * [ ] Event group connection (at least, it would be useful to connect event group to queue) + * [.] Event group connection (at least, it would be useful to connect event group to queue) + * [X] implement functionality + * [ ] implement tests + * [ ] implement example + * [ ] add the link to example from `tn_eventgrp.h` + * [ ] Exchange object: see AVIX User Guide & Reference Guide, 6.6.8 * [ ] Event group: probably, add mask of flags that should be cleared automatically when any task `wait`s for it * [ ] Add a destroy callback for each task, this callback (if not NULL) should be called whenever task is terminated with `tn_task_exit()` or `tn_task_terminate()`. * [ ] Examples diff --git a/stuff/vimwiki/thoughts.wiki b/stuff/vimwiki/thoughts.wiki index 525731f..6dcda40 100644 --- a/stuff/vimwiki/thoughts.wiki +++ b/stuff/vimwiki/thoughts.wiki @@ -44,3 +44,15 @@ What about some object like EventCollector? (not sure how to implement it) Well, we want to be able to wait for multiple events atomically: say, we want to wait for message from the queue and for the semaphore. It seems really hard to do that in single call to something like `tn_collector_wait()`, because then `tn_collector_wait()` should take all the possible arguments for all possible waiting events: void *p_data for sending the data to queue, void **pp_data for receiving, the same for fmem, etc. So, "event connection" approach seems much better. + +== interrupt context save: pre-push or post-push == + + * pre-push: save context on the interrupt stack + * post-push: save context on the interrupted task's stack + +PIC32 port of TNeoKernel uses pre-push. It doesn't allow us to immediately switch to another task after ISR returns, but instead we have the following advantages: + + * we can use shadow register set + * even for ISRs that don't use shadow register set, the number of registers to save is less: we shouldn't save callee-saved registers `$s0 - $s9` + * we save 34 words of RAM for each task: interrupt can happen when scheduler is saving/restoring context, so, if we use post-push, context can be saved on the stack twice. +