Skip to content

Commit

Permalink
EM-337: Handle timer_offset overflow and add timer unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JFinneganAloxy committed Aug 4, 2023
1 parent 731313a commit cf39511
Show file tree
Hide file tree
Showing 14 changed files with 475 additions and 13 deletions.
14 changes: 8 additions & 6 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ create_docker_image:
- docker build --cache-from registry.gitlab.com/aloxy/$CI_PROJECT_NAME/builder:$DOCKER_TAG --pull -t registry.gitlab.com/aloxy/$CI_PROJECT_NAME/builder:$DOCKER_TAG . && docker push registry.gitlab.com/aloxy/$CI_PROJECT_NAME/builder:$DOCKER_TAG


job 1:
build primary apps:
image: "registry.gitlab.com/aloxy/$CI_PROJECT_NAME/builder:$DOCKER_TAG"
tags:
- docker
Expand All @@ -73,7 +73,7 @@ job 1:
- build/apps/gateway/*
- build/apps/modem/*
- build/apps/sensor_push/*
job 1b:
run unit tests:
image: "registry.gitlab.com/aloxy/$CI_PROJECT_NAME/builder:$DOCKER_TAG"
tags:
- docker
Expand All @@ -87,18 +87,20 @@ job 1b:
script:
- mkdir build && cd build
- platform="NATIVE"
- cmake ../stack/ -DPLATFORM=$platform -DCMAKE_TOOLCHAIN_FILE="../stack/cmake/toolchains/gcc.cmake" -DBUILD_UNIT_TESTS=y -DFRAMEWORK_CONSOLE_ENABLED=n -DTEST_AES=y -DTEST_FEC=y -DTEST_ALP=y -DTEST_FIFO=y -DTEST_SCHEDULER=y -DMODULE_D7AP=n -DFRAMEWORK_MODEM_INTERFACE_ENABLED=n -DFRAMEWORK_USE_POWER_TRACKING=n
- cmake ../stack/ -DPLATFORM=$platform -DCMAKE_TOOLCHAIN_FILE="../stack/cmake/toolchains/gcc.cmake" -DBUILD_UNIT_TESTS=y -DFRAMEWORK_CONSOLE_ENABLED=n -DTEST_AES=y -DTEST_FEC=y -DTEST_ALP=y -DTEST_FIFO=y -DTEST_SCHEDULER=y -DTEST_TIMER=y -DMODULE_D7AP=n -DFRAMEWORK_MODEM_INTERFACE_ENABLED=n -DFRAMEWORK_USE_POWER_TRACKING=n
- make -j
- ./tests/aes/test_aes &> results_aes.txt
- ./tests/alp/test_alp &> results_alp.txt
- ./tests/fec/test_fec &> results_fec.txt
- ./tests/fifo/test_fifo &> results_fifo.txt
- ./tests/scheduler/test_scheduler &> results_scheduler.txt
- ./tests/timer/test_timer &> results_timer.txt
- if ! grep -q 'AES all unit tests OK !' "results_aes.txt"; then exit 1; fi
- if ! grep -q 'Unit-tests for ALP completed' "results_alp.txt"; then exit 1; fi
- if ! grep -q 'Input was decoded successfully' "results_fec.txt"; then exit 1; fi
- if ! grep -q 'All FIFO tests passed!' "results_fifo.txt"; then exit 1; fi
- if ! grep -q 'All scheduler tests passed!' "results_scheduler.txt"; then exit 1; fi
- if ! grep -q 'All timer tests passed!' "results_timer.txt"; then exit 1; fi
- cd ..
- mv build /builds/aloxy/$CI_PROJECT_NAME/build
artifacts:
Expand Down Expand Up @@ -141,12 +143,12 @@ build sample apps:
- cmake ../stack/ -DAPP_SENSOR_MULTIMODAL=y -DAPP_SENSOR_PUSH_LORAWAN=y -DPLATFORM=$platform -DMODULE_LORAWAN=y -DFRAMEWORK_MODEM_INTERFACE_ENABLED=n -DMODULE_LORAWAN_REGION=MODEM_REGION_EU868
- make -j

job 2:
flash devkits:
tags:
- testsuite
stage: flash test boards
dependencies:
- job 1
- build primary apps
script:
- if $(lsof -i -P -n | grep -q JLinkExe) ; then exit 1; fi
- if pgrep -f "python3 run.py" ; then exit 1; fi
Expand All @@ -157,7 +159,7 @@ job 2:
- cat flashOutput2.txt
- if grep -q 'FAILED\|ERROR' "flashOutput1.txt"; then exit 1; fi
- if grep -q 'FAILED\|ERROR' "flashOutput2.txt"; then exit 1; fi
job 3:
run testsuite:
image: python:3.8
tags:
- testsuite
Expand Down
11 changes: 6 additions & 5 deletions stack/framework/components/timer/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#define HW_TIMER_ID 0

#define COUNTER_EVENTTIME_LIMIT TIMER_TICKS_PER_SEC * 20
#define COUNTER_OVERFLOW_INCREASE (UINT32_C(1) << (8*sizeof(hwtimer_tick_t)))

// define inline functions from timer.h as extern
Expand Down Expand Up @@ -302,7 +303,8 @@ static bool configure_next_event()
if(NG(next_event) != NO_EVENT)
{
next_fire_time = NG(timers)[NG(next_event)].next_event;
if ( (((int32_t)next_fire_time) - ((int32_t)current_time) - timer_info->min_delay_ticks) <= 0 )
// fire if the fire time is less than 20 seconds ago since this means we should have fired already (timer_calculate_difference takes overflow into account)
if(timer_calculate_difference(next_fire_time, current_time + timer_info->min_delay_ticks) < COUNTER_EVENTTIME_LIMIT)
{
DPRINT("will be late, sched immediately\n\n");
if(NG(timers)[NG(next_event)].f == 0)
Expand Down Expand Up @@ -375,7 +377,7 @@ timer_tick_t timer_calculate_difference(timer_tick_t start_time, timer_tick_t st
{
if(start_time <= stop_time)
return stop_time - start_time;
// if a rolloved happened, add both parts together
// if a rollover happened, add both parts together
else
return (UINT32_MAX - start_time) + 1 + stop_time;
}
Expand All @@ -385,11 +387,10 @@ static void timer_overflow()
NG(timer_offset) += COUNTER_OVERFLOW_INCREASE;
if(NG(next_event) != NO_EVENT && //there is an event scheduled at THIS timer level
(!NG(hw_event_scheduled)) && //but NOT at the hw timer level
NG(timers)[NG(next_event)].next_event <= (NG(timer_offset) + COUNTER_OVERFLOW_INCREASE) //and the next trigger will happen before the next overflow
( (NG(timers)[NG(next_event)].next_event >= NG(timer_offset))
&& (NG(timers)[NG(next_event)].next_event <= (NG(timer_offset) + COUNTER_OVERFLOW_INCREASE - 1) ) ) //and the next trigger will happen before the next overflow
)
{
//normally this shouldn't happen. Put an assert here just to make sure
assert(NG(timers)[NG(next_event)].next_event >= NG(timer_offset));
timer_tick_t fire_time = (NG(timers)[NG(next_event)].next_event - NG(timer_offset));

//fire time already passed
Expand Down
1 change: 1 addition & 0 deletions stack/tests/error_event_file/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ cmake_minimum_required(VERSION 2.8)

add_executable(${PROJECT_NAME} main.c )

#link with the framework library that includes the error_event_file component
target_link_libraries (${PROJECT_NAME} d7ap_fs framework )
2 changes: 1 addition & 1 deletion stack/tests/fifo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ cmake_minimum_required(VERSION 2.8)

add_executable(${PROJECT_NAME} main.c)

#link with the framework library that includes the AES library
#link with the framework library that includes the fifo component
target_link_libraries (${PROJECT_NAME} framework)
2 changes: 1 addition & 1 deletion stack/tests/scheduler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ cmake_minimum_required(VERSION 2.8)

add_executable(${PROJECT_NAME} main.c)

#link with the framework library that includes the AES library
#link with the framework library that includes the scheduler component
target_link_libraries (${PROJECT_NAME} framework)
32 changes: 32 additions & 0 deletions stack/tests/timer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#[[
Copyright (c) 2015-2021 University of Antwerp, Aloxy NV.

This file is part of Sub-IoT.
See https://github.com/Sub-IoT/Sub-IoT-Stack for further info.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]
project(test_timer)
cmake_minimum_required(VERSION 2.8)

add_subdirectory(./mocks)

#create a lib out of the scheduler
add_library(scheduler STATIC ../../framework/components/scheduler/scheduler.c)
set_target_properties(scheduler PROPERTIES LINKER_LANGUAGE C)
target_include_directories(scheduler PUBLIC ../../framework/inc ../../framework/hal/inc ./) #to include framework_defs.h

add_executable(${PROJECT_NAME} main.c ) #the main includes timer.c directly

target_include_directories(${PROJECT_NAME} PUBLIC ../../framework/components/timer ../../framework/inc ../../framework/hal/inc ./)
target_link_libraries (${PROJECT_NAME} scheduler mocks)
5 changes: 5 additions & 0 deletions stack/tests/timer/framework_defs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#define FRAMEWORK_TIMER_RESOLUTION 1MS
#define FRAMEWORK_TIMER_STACK_SIZE 10
#define FRAMEWORK_SCHEDULER_MAX_TASKS 41
#define FRAMEWORK_SCHEDULER_LP_MODE 0
#define FRAMEWORK_SCHEDULER_MAX_ACTIVE_TIME 0
220 changes: 220 additions & 0 deletions stack/tests/timer/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2015-2021 University of Antwerp, Aloxy NV.
*
* This file is part of Sub-IoT.
* See https://github.com/Sub-IoT/Sub-IoT-Stack for further info.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "stdio.h"
#include <stdlib.h>
#include "timer.c"

#define COUNTER_OVERFLOW_MAX 0xFFFF0000

static timer_event dummy_tester_event;
static timer_event dummy_tester_event2;
static timer_event dummy_tester_event3;
static timer_event dummy_tester_event4;
static timer_event dummy_tester_event5;
static timer_event dummy_tester_event6;

void dummy_function(){}
void dummy_function2(){}
void dummy_function3(){}
void dummy_function4(){}
void dummy_function5(){}
void dummy_function6(){}
void dummy_function7(){}
void dummy_function8(){}

void test_timer_events() {
//can init event
assert(timer_init_event(&dummy_tester_event, &dummy_function) == SUCCESS);
//cannot init same event multiple times
assert(timer_init_event(&dummy_tester_event, &dummy_function) == -EALREADY);
//manually conf next_event
dummy_tester_event.next_event = TIMER_TICKS_PER_SEC * 1;
// can post a task from an event
assert(timer_add_event(&dummy_tester_event) == SUCCESS);
// after adding event, associated task is scheduled
assert(timer_is_task_scheduled(dummy_tester_event.f));
// cancel event
timer_cancel_event(&dummy_tester_event);
// after cancelling event, associated task is no longer scheduled
assert(timer_is_task_scheduled(dummy_tester_event.f) == false);
}

void test_timer_post_tasks() {
// cannot post task with less than min priority
assert(timer_post_task_prio_delay(&dummy_function2, TIMER_TICKS_PER_SEC * 5, MIN_PRIORITY + 1) == EINVAL);
// can post task
assert(timer_post_task_prio_delay(&dummy_function2, TIMER_TICKS_PER_SEC * 5, DEFAULT_PRIORITY) == SUCCESS);
// after posting, task is scheduled
assert(timer_is_task_scheduled(&dummy_function2));
// can modify the fire time by posting the same task again
assert(timer_post_task_prio_delay(&dummy_function2, TIMER_TICKS_PER_SEC * 7, DEFAULT_PRIORITY) == SUCCESS);
// cannot modify the priority by posting the same task again
assert(timer_post_task_prio_delay(&dummy_function2, TIMER_TICKS_PER_SEC * 7, DEFAULT_PRIORITY-1) == EALREADY);
// can cancel the task
assert(timer_cancel_task(&dummy_function2) == SUCCESS);
// after cancelling, task is no longer scheduled
assert(timer_is_task_scheduled(&dummy_function2) == false);
// cannot cancel an unscheduled task
assert(timer_cancel_task(&dummy_function2) == EALREADY);

// task posted with delay of 0 is scheduled immediately
sched_register_task(&dummy_function3);
assert(timer_post_task_prio_delay(&dummy_function3, TIMER_TICKS_PER_SEC * 0, DEFAULT_PRIORITY) == SUCCESS);
assert(timer_is_task_scheduled(&dummy_function3) == false);
}

void run_overflow_test(timer_tick_t timer_offset, timer_tick_t next_event_time) {
NG(timer_offset) = timer_offset;
NG(timers)[NG(next_event)].next_event = next_event_time;
NG(hw_event_scheduled) = false;
run_overflow_c();
}
void test_timer_overflow() {
set_hw_timer_value(0);
//regular cases:
//create an event
assert(timer_init_event(&dummy_tester_event2, &dummy_function4) == SUCCESS);
dummy_tester_event2.next_event = TIMER_TICKS_PER_SEC * 1;
assert(timer_add_event(&dummy_tester_event2) == SUCCESS);

// event is in the next period
run_overflow_test(COUNTER_OVERFLOW_INCREASE * 10, COUNTER_OVERFLOW_INCREASE * 11 + 3000);
assert(NG(hw_event_scheduled) == true); //i.e. event gets scheduled for the next period

// event is in the far future
run_overflow_test(COUNTER_OVERFLOW_INCREASE * 10, COUNTER_OVERFLOW_INCREASE * 12 + 3000);
assert( (NG(hw_event_scheduled) == false) && NG(timers)[NG(next_event)].f != 0x0 ); //i.e. event should not be scheduled yet

// event is in the distant past
run_overflow_test(COUNTER_OVERFLOW_INCREASE * 10, 0);
assert( (NG(hw_event_scheduled) == false) && NG(timers)[NG(next_event)].f != 0x0 ); //i.e. event doesn't get fired

// event is in this period but in near past (behind hw_timer time)
set_hw_timer_value(4000);
run_overflow_test(COUNTER_OVERFLOW_INCREASE * 10, COUNTER_OVERFLOW_INCREASE * 11 + 3000);
assert( (NG(hw_event_scheduled) == false) && get_next_event() == NO_EVENT ); //i.e. event got fired immediately
set_hw_timer_value(0);

timer_cancel_event(&dummy_tester_event2);

//overflow cases:
//create another event
assert(timer_init_event(&dummy_tester_event3, &dummy_function5) == SUCCESS);
dummy_tester_event3.next_event = TIMER_TICKS_PER_SEC * 1;
assert(timer_add_event(&dummy_tester_event3) == SUCCESS);

// event is in the next period
run_overflow_test(COUNTER_OVERFLOW_MAX, 3000);
assert(NG(hw_event_scheduled) == true); //i.e. event gets scheduled for the next period

// event is in the far future
run_overflow_test(COUNTER_OVERFLOW_MAX, COUNTER_OVERFLOW_INCREASE + 3000);
assert( (NG(hw_event_scheduled) == false) && NG(timers)[NG(next_event)].f != 0x0 ); //i.e. event should not be scheduled yet

// event is in the distant past
set_hw_timer_value(0);
run_overflow_test(0, COUNTER_OVERFLOW_MAX);
assert( (NG(hw_event_scheduled) == false) && NG(timers)[NG(next_event)].f != 0x0 ); //i.e. event doesn't get fired

// event is in this period but in near past (behind hw_timer time)
set_hw_timer_value(4000);
run_overflow_test(COUNTER_OVERFLOW_MAX, 3000);
assert( (NG(hw_event_scheduled) == false) && get_next_event() == NO_EVENT ); //i.e. event got fired immediately

timer_cancel_event(&dummy_tester_event3);
}

void setup_counter_test(timer_tick_t timer_offset, uint16_t hw_timer_value, bool is_overflow_pending) {
NG(timer_offset) = timer_offset;
set_hw_timer_value(hw_timer_value);
set_overflow_pending(is_overflow_pending);
}

void test_timer_get_counter_value() {
//regular cases:
setup_counter_test(COUNTER_OVERFLOW_INCREASE, 0x00ff, false);
assert(timer_get_counter_value() == COUNTER_OVERFLOW_INCREASE + 0x00ff);
setup_counter_test(COUNTER_OVERFLOW_INCREASE, 0x00ff, true);
assert(timer_get_counter_value() == COUNTER_OVERFLOW_INCREASE + 0x00ff + COUNTER_OVERFLOW_INCREASE);

//overflow cases:
setup_counter_test(COUNTER_OVERFLOW_MAX, 0x00ff, false);
assert(timer_get_counter_value() == COUNTER_OVERFLOW_MAX + 0x00ff);
setup_counter_test(COUNTER_OVERFLOW_MAX, 0x00ff, true);
assert(timer_get_counter_value() == COUNTER_OVERFLOW_MAX + 0x00ff + COUNTER_OVERFLOW_INCREASE);
}

void test_timer_timed_events() {
//reset time to 0
NG(timer_offset) = 0;
set_hw_timer_value(0);
set_overflow_pending(false);

// - schedule three events of different times
assert(timer_init_event(&dummy_tester_event4, &dummy_function6) == SUCCESS);
dummy_tester_event4.next_event = TIMER_TICKS_PER_SEC * 1;
assert(timer_init_event(&dummy_tester_event5, &dummy_function7) == SUCCESS);
dummy_tester_event5.next_event = TIMER_TICKS_PER_SEC * 3;
assert(timer_init_event(&dummy_tester_event6, &dummy_function8) == SUCCESS);
dummy_tester_event6.next_event = TIMER_TICKS_PER_SEC * 5;

// post tasks from the events
assert(timer_add_event(&dummy_tester_event4) == SUCCESS);
assert(timer_add_event(&dummy_tester_event5) == SUCCESS);
assert(timer_add_event(&dummy_tester_event6) == SUCCESS);

assert(timer_is_task_scheduled(dummy_tester_event4.f));
// check event returned is as expected
uint32_t next_event = get_next_event();
assert(NG(timers)[next_event].f == dummy_tester_event4.f);
// move time forward till after that event
NG(timer_offset) = TIMER_TICKS_PER_SEC * 2;
// events from the past should still get returned if they haven't fired yet
next_event = get_next_event();
assert(NG(timers)[next_event].f == dummy_tester_event4.f);
timer_cancel_event(&dummy_tester_event4);
next_event = get_next_event();
assert(NG(timers)[next_event].f == dummy_tester_event5.f);
timer_cancel_event(&dummy_tester_event5);
next_event = get_next_event();
assert(NG(timers)[next_event].f == dummy_tester_event6.f);
timer_cancel_event(&dummy_tester_event6);
//when no events listed, should return NO_EVENT
next_event = get_next_event();
assert(next_event == NO_EVENT);
}

int main(int argc, char *argv[]){
timer_init();
scheduler_init();

test_timer_events();

test_timer_post_tasks();

test_timer_overflow();

test_timer_get_counter_value();

test_timer_timed_events();

printf("All timer tests passed!\n");

exit(0);
}
Loading

0 comments on commit cf39511

Please sign in to comment.