blob: c1adeba5403a6f8da16adac9e209a6e02ac9f7ee [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/**
* This file implements the "signals" version of sim. This implementation uses
* signals to perform context switches. This is the more correct version of
* sim: the OS tick timer will cause a high-priority task to preempt a
* low-priority task. Unfortunately, there are stability issues because a task
* can be preempted while it is in the middle of a system call, potentially
* causing deadlock or memory corruption.
*
* To use this version of sim, enable the MCU_NATIVE_USE_SIGNALS syscfg
* setting.
*/
#include "os/mynewt.h"
#if MYNEWT_VAL(MCU_NATIVE_USE_SIGNALS)
#include "sim_priv.h"
#include <hal/hal_bsp.h>
#ifdef __APPLE__
#define _XOPEN_SOURCE
#endif
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/time.h>
#include <assert.h>
static bool suspended; /* process is blocked in sigsuspend() */
static sigset_t suspsigs; /* signals delivered in sigsuspend() */
static sigset_t allsigs;
static sigset_t nosigs;
void
sim_ctx_sw(struct os_task *next_t)
{
/*
* gdb will stop execution of the program on most signals (e.g. SIGUSR1)
* whereas it passes SIGURG to the process without any special settings.
*/
kill(sim_pid, SIGURG);
}
static void
ctxsw_handler(int sig)
{
OS_ASSERT_CRITICAL();
/*
* Just record that this handler was called when the process was blocked.
* The handler will be called after sigsuspend() returns in the correct
* order.
*/
if (suspended) {
sigaddset(&suspsigs, sig);
} else {
sim_switch_tasks();
}
}
/*
* Disable signals and enter a critical section.
*
* Returns 1 if signals were already blocked and 0 otherwise.
*/
os_sr_t
sim_save_sr(void)
{
int error;
sigset_t omask;
error = sigprocmask(SIG_BLOCK, &allsigs, &omask);
assert(error == 0);
/*
* If any one of the signals in 'allsigs' is present in 'omask' then
* we are already inside a critical section.
*/
return (sigismember(&omask, SIGALRM));
}
void
sim_restore_sr(os_sr_t osr)
{
int error;
OS_ASSERT_CRITICAL();
assert(osr == 0 || osr == 1);
if (osr == 1) {
/* Exiting a nested critical section */
return;
}
error = sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
assert(error == 0);
}
int
sim_in_critical(void)
{
int error;
sigset_t omask;
error = sigprocmask(SIG_SETMASK, NULL, &omask);
assert(error == 0);
/*
* If any one of the signals in 'allsigs' is present in 'omask' then
* we are already inside a critical section.
*/
return (sigismember(&omask, SIGALRM));
}
static void
timer_handler(int sig)
{
OS_ASSERT_CRITICAL();
/*
* Just record that this handler was called when the process was blocked.
* The handler will be called after sigsuspend() returns in the proper
* order.
*/
if (suspended) {
sigaddset(&suspsigs, sig);
} else {
sim_tick();
}
}
static struct {
int num;
void (*handler)(int sig);
} signals[] = {
{ SIGALRM, timer_handler },
{ SIGURG, ctxsw_handler },
};
#define NUMSIGS (sizeof(signals)/sizeof(signals[0]))
void
sim_tick_idle(os_time_t ticks)
{
int i, rc, sig;
struct itimerval it;
void (*handler)(int sig);
OS_ASSERT_CRITICAL();
if (ticks > 0) {
/*
* Enter tickless regime and set the timer to fire after 'ticks'
* worth of time has elapsed.
*/
it.it_value.tv_sec = ticks / OS_TICKS_PER_SEC;
it.it_value.tv_usec = (ticks % OS_TICKS_PER_SEC) * OS_USEC_PER_TICK;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = OS_USEC_PER_TICK;
rc = setitimer(ITIMER_REAL, &it, NULL);
assert(rc == 0);
}
suspended = true;
sigemptyset(&suspsigs);
sigsuspend(&nosigs); /* Wait for a signal to wake us up */
suspended = false;
/*
* Call handlers for signals delivered to the process during sigsuspend().
* The SIGALRM handler is called before any other handlers to ensure that
* OS time is always correct.
*/
if (sigismember(&suspsigs, SIGALRM)) {
sim_tick();
}
for (i = 0; i < NUMSIGS; i++) {
sig = signals[i].num;
handler = signals[i].handler;
if (sig != SIGALRM && sigismember(&suspsigs, sig)) {
handler(sig);
}
}
if (ticks > 0) {
/*
* Enable the periodic timer interrupt.
*/
it.it_value.tv_sec = 0;
it.it_value.tv_usec = OS_USEC_PER_TICK;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = OS_USEC_PER_TICK;
rc = setitimer(ITIMER_REAL, &it, NULL);
assert(rc == 0);
}
}
void
sim_signals_init(void)
{
int i, error;
struct sigaction sa;
sigemptyset(&nosigs);
sigemptyset(&allsigs);
for (i = 0; i < NUMSIGS; i++) {
sigaddset(&allsigs, signals[i].num);
}
for (i = 0; i < NUMSIGS; i++) {
memset(&sa, 0, sizeof sa);
sa.sa_handler = signals[i].handler;
sa.sa_mask = allsigs;
sa.sa_flags = SA_RESTART;
error = sigaction(signals[i].num, &sa, NULL);
assert(error == 0);
}
/*
* We use SIGALRM as a proxy for 'allsigs' to check if we are inside
* a critical section (for e.g. see sim_in_critical()). Make sure
* that SIGALRM is indeed present in 'allsigs'.
*/
assert(sigismember(&allsigs, SIGALRM));
}
void
sim_signals_cleanup(void)
{
int i, error;
struct sigaction sa;
for (i = 0; i < NUMSIGS; i++) {
memset(&sa, 0, sizeof sa);
sa.sa_handler = SIG_DFL;
error = sigaction(signals[i].num, &sa, NULL);
assert(error == 0);
}
}
#endif /* MYNEWT_VAL(MCU_NATIVE_USE_SIGNALS) */