Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix problem with threaded Zephyr and overflow handling in QEMU emulation #159

Merged
merged 6 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions core/platform/lf_zephyr_support.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Combine 2 32-bit words to a 64-bit word
#define COMBINE_HI_LO(hi,lo) ((((uint64_t) hi) << 32) | ((uint64_t) lo))

// Keep track of overflows to keep clocks monotonic
static int64_t _lf_timer_epoch_duration_nsec;
static volatile int64_t _lf_timer_last_epoch_nsec = 0;

#if defined(LF_ZEPHYR_CLOCK_HI_RES)
// Create semaphore for async wakeup from physical action
Expand All @@ -55,13 +58,11 @@ const struct device *const _lf_counter_dev = DEVICE_DT_GET(LF_TIMER);
static volatile bool _lf_alarm_fired;
static uint32_t _lf_timer_freq;

static int64_t _lf_timer_epoch_duration_usec;
static volatile int64_t _lf_timer_last_epoch_nsec = 0;


// Timer overflow callback
static void _lf_timer_overflow_callback(const struct device *dev, void *user_data) {
_lf_timer_last_epoch_nsec += _lf_timer_epoch_duration_usec*1000LL;
_lf_timer_last_epoch_nsec += _lf_timer_epoch_duration_nsec;
}


Expand Down Expand Up @@ -109,7 +110,7 @@ void lf_initialize_clock() {

// Calculate the duration of an epoch
counter_max_ticks = counter_get_max_top_value(_lf_counter_dev);
_lf_timer_epoch_duration_usec = counter_ticks_to_us(_lf_counter_dev, counter_max_ticks);
_lf_timer_epoch_duration_nsec = counter_ticks_to_us(_lf_counter_dev, counter_max_ticks) * 1000LL;

// Set the max_top value to be the maximum
counter_max_ticks = counter_get_max_top_value(_lf_counter_dev);
Expand All @@ -135,6 +136,9 @@ void lf_initialize_clock() {
#else
LF_PRINT_LOG("Using Low resolution zephyr kernel clock");
LF_PRINT_LOG("Kernel Clock has frequency of %u Hz\n", CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC);
_lf_timer_last_epoch_nsec = 0;
// Compute the duration of an
_lf_timer_epoch_duration_nsec = ((1LL << 32) * SECONDS(1))/CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC;
#endif
}

Expand All @@ -144,7 +148,9 @@ void lf_initialize_clock() {
// Zephyrs Counter API

/**
* Return the current time in nanoseconds
* Return the current time in nanoseconds. It gets the current value
* of the hi-res counter device and also keeps track of overflows
* to deliver a monotonically increasing clock.
*/
int lf_clock_gettime(instant_t* t) {
uint32_t now_cycles;
Expand All @@ -154,8 +160,6 @@ int lf_clock_gettime(instant_t* t) {
res = counter_get_value(_lf_counter_dev, &now_cycles);
now_nsec = counter_ticks_to_us(_lf_counter_dev, now_cycles)*1000ULL;
*t = now_nsec + _lf_timer_last_epoch_nsec;
now_cycles = k_cycle_get_32();
*t = (SECOND(1)/CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC)*now_cycles;
return 0;
}

Expand Down Expand Up @@ -222,11 +226,19 @@ int lf_sleep_until_locked(instant_t wakeup) {
}
}
#else
// Clock and sleep implementation for LO_RES clock

// Clock and sleep implementation for LO_RES clock. Handle wraps
// by checking if two consecutive reads are monotonic
static uint32_t last_read_cycles=0;
int lf_clock_gettime(instant_t* t) {
uint32_t now_cycles = k_cycle_get_32();
*t = (SECOND(1)/CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC)*now_cycles;

if (now_cycles < last_read_cycles) {
_lf_timer_last_epoch_nsec += _lf_timer_epoch_duration_nsec;
}

*t = (SECOND(1)/CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC)*now_cycles + _lf_timer_last_epoch_nsec;

last_read_cycles = now_cycles;
return 0;
}

Expand Down Expand Up @@ -331,12 +343,19 @@ int lf_notify_of_event() {


#ifdef LF_THREADED
#warning "Threaded support on Zephyr is still experimental"
#error "Threaded support on Zephyr is not supported"

// FIXME: What is an appropriate stack size?
#define _LF_STACK_SIZE 1024
// FIXME: What is an appropriate thread prio?
#define _LF_THREAD_PRIORITY 5

// If NUMBER_OF_WORKERS is not specified, or set to 0, then we default to 1.
#if !defined(NUMBER_OF_WORKERS) || NUMBER_OF_WORKERS==0
#undef NUMBER_OF_WORKERS
#define NUMBER_OF_WORKERS 1
#endif

static K_THREAD_STACK_ARRAY_DEFINE(stacks, NUMBER_OF_WORKERS, _LF_STACK_SIZE);
static struct k_thread threads[NUMBER_OF_WORKERS];

Expand All @@ -362,12 +381,16 @@ int lf_available_cores() {
* getting passed arguments. The new handle is stored in thread_id.
*
* @return 0 on success, platform-specific error number otherwise.
*
* FIXME: As this function is currently part of the user-facing API,
* it should not care about the number of workers specified.
* If we want static allocation of workers, as implemented now,
* it must be removed from the API.
*/
int lf_thread_create(lf_thread_t* thread, void *(*lf_thread) (void *), void* arguments) {
// Use static id to map each created thread to a
static int tid = 0;


// Make sure we dont try to create too many threads
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If lf_thread_create is called in user code, then the number of workers isn't really relevant, is it? Does this really belong here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is bad practice to do too much dynamic spawning of threads in a microcontroller setting. You want to avoid dynamic memory allocation if you can. Therefore the stacks and thread control blocks are statically allocated. This puts an upper limit to how many times you can call lf_thread_t. The user can use ordinary calls to zephyrs API to create other threads.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I don't disagree with you on this, AFAIK, the Zephyr implementation is the only one that does this. My point of criticism has to do with the fact that the function is lf_thread_create not lf_worker_create. Threads and workers are two different things, and you're now looking at the number of workers in the thread creation function, which I think is odd...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I would argue that reactor-c is not a threading library and that user-code should not use the LF threading API. That would make the runtime more attractive for use in these embedded systems where people are wary of using dynamic memory allocation. There is already a lot of that in the runtime though, but theoretically, most of it can be replaced by static allocations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function does not have a leading underscore, so it is part of the public API...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zephyr has a POSIX implementation that we could use temporarily, or we can just turn of threaded support for Zephyr, that is fine with me. With the bodiless reactions we have to make a decision about what is actually going to be part of the user-facing API since those includes must be code-generated. So let's make the right decision there and revisit this later

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand your proposal. Here are some possible solutions that crossed my mind:

  • have a define MAX_THREAD_COUNT and check against that
  • update the docs and have all implementations check against NUMBER_OF_WORKERS
  • rename to _lf_thread_create
  • introduce a lf_worker_create that does the check and then just invokes lf_thread_create
  • ...?

if (tid > (NUMBER_OF_WORKERS-1)) {
return -1;
}
Expand Down
4 changes: 3 additions & 1 deletion core/threaded/reactor_threaded.c
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,9 @@ void start_threads() {
LF_PRINT_LOG("Starting %u worker threads.", _lf_number_of_workers);
_lf_thread_ids = (lf_thread_t*)malloc(_lf_number_of_workers * sizeof(lf_thread_t));
for (unsigned int i = 0; i < _lf_number_of_workers; i++) {
lf_thread_create(&_lf_thread_ids[i], worker, NULL);
if (lf_thread_create(&_lf_thread_ids[i], worker, NULL) != 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the check implemented in lf_thread_create should be done here, at the call site, before even invoking lf_thread_create...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I have statically allocated the stacks for the threads I have to check it inside lf_thread_create to make sure that it is not called from somewhere else

lf_print_error_and_exit("Could not start thread-%u", i);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions core/threaded/scheduler_adaptive.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* This is a non-priority-driven scheduler. See scheduler.h for documentation.
* @author{Peter Donovan <[email protected]>}
*/
#include "lf_types.h"
#if defined SCHEDULER && SCHEDULER == ADAPTIVE
#ifndef NUMBER_OF_WORKERS
#define NUMBER_OF_WORKERS 1
Expand Down