linux: Use Unix signals to notify when a timer is pending

Use Unix signals in software timer implementation.  This makes the
code a little more efficient.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2020-06-07 20:38:20 -04:00
parent ffeafb690b
commit 913d099261
5 changed files with 105 additions and 57 deletions

View File

@ -7,7 +7,7 @@ src-y += linux/pca9685.c linux/spidev.c linux/analog.c linux/hard_pwm.c
src-y += linux/i2c.c linux/gpio.c generic/crc16_ccitt.c generic/alloc.c src-y += linux/i2c.c linux/gpio.c generic/crc16_ccitt.c generic/alloc.c
src-y += linux/sensor_ds18b20.c src-y += linux/sensor_ds18b20.c
CFLAGS_klipper.elf += -lutil -lpthread CFLAGS_klipper.elf += -lutil -lrt -lpthread
flash: $(OUT)klipper.elf flash: $(OUT)klipper.elf
@echo " Flashing" @echo " Flashing"

View File

@ -1,28 +1,27 @@
// TTY based IO // TTY based IO
// //
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net> // Copyright (C) 2017-2021 Kevin O'Connor <kevin@koconnor.net>
// //
// This file may be distributed under the terms of the GNU GPLv3 license. // This file may be distributed under the terms of the GNU GPLv3 license.
#define _GNU_SOURCE
#include <errno.h> // errno #include <errno.h> // errno
#include <fcntl.h> // fcntl #include <fcntl.h> // fcntl
#include <poll.h> // poll #include <poll.h> // ppoll
#include <pty.h> // openpty #include <pty.h> // openpty
#include <stdio.h> // fprintf #include <stdio.h> // fprintf
#include <string.h> // memmove #include <string.h> // memmove
#include <sys/stat.h> // chmod #include <sys/stat.h> // chmod
#include <sys/timerfd.h> // timerfd_create
#include <time.h> // struct timespec #include <time.h> // struct timespec
#include <unistd.h> // ttyname #include <unistd.h> // ttyname
#include "board/irq.h" // irq_poll #include "board/irq.h" // irq_wait
#include "board/misc.h" // console_sendf #include "board/misc.h" // console_sendf
#include "command.h" // command_find_block #include "command.h" // command_find_block
#include "internal.h" // console_setup #include "internal.h" // console_setup
#include "sched.h" // sched_wake_task #include "sched.h" // sched_wake_task
static struct pollfd main_pfd[2]; static struct pollfd main_pfd[1];
#define MP_TIMER_IDX 0 #define MP_TTY_IDX 0
#define MP_TTY_IDX 1
// Report 'errno' in a message written to stderr // Report 'errno' in a message written to stderr
void void
@ -110,15 +109,6 @@ console_setup(char *name)
if (ret) if (ret)
return -1; return -1;
// Create sleep wakeup timer fd
ret = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC|TFD_NONBLOCK);
if (ret < 0) {
report_errno("timerfd_create", ret);
return -1;
}
main_pfd[MP_TIMER_IDX].fd = ret;
main_pfd[MP_TIMER_IDX].events = POLLIN;
return 0; return 0;
} }
@ -188,26 +178,16 @@ console_sendf(const struct command_encoder *ce, va_list args)
report_errno("write", ret); report_errno("write", ret);
} }
// Sleep until the specified time (waking early for console input if needed) // Sleep until a signal received (waking early for console input if needed)
void void
console_sleep(struct timespec ts) console_sleep(sigset_t *sigset)
{ {
struct itimerspec its; int ret = ppoll(main_pfd, ARRAY_SIZE(main_pfd), NULL, sigset);
its.it_interval = (struct timespec){0, 0};
its.it_value = ts;
int ret = timerfd_settime(main_pfd[MP_TIMER_IDX].fd, TFD_TIMER_ABSTIME
, &its, NULL);
if (ret < 0) {
report_errno("timerfd_settime", ret);
return;
}
ret = poll(main_pfd, ARRAY_SIZE(main_pfd), -1);
if (ret <= 0) { if (ret <= 0) {
report_errno("poll main_pfd", ret); if (errno != EINTR)
report_errno("ppoll main_pfd", ret);
return; return;
} }
if (main_pfd[MP_TTY_IDX].revents) if (main_pfd[MP_TTY_IDX].revents)
sched_wake_task(&console_wake); sched_wake_task(&console_wake);
if (main_pfd[MP_TIMER_IDX].revents)
irq_poll();
} }

View File

@ -2,6 +2,7 @@
#define __LINUX_INTERNAL_H #define __LINUX_INTERNAL_H
// Local definitions for micro-controllers running on linux // Local definitions for micro-controllers running on linux
#include <signal.h> // sigset_t
#include <stdint.h> // uint32_t #include <stdint.h> // uint32_t
#include "autoconf.h" // CONFIG_CLOCK_FREQ #include "autoconf.h" // CONFIG_CLOCK_FREQ
@ -10,7 +11,6 @@
#define GPIO2PORT(PIN) ((PIN) / MAX_GPIO_LINES) #define GPIO2PORT(PIN) ((PIN) / MAX_GPIO_LINES)
#define GPIO2PIN(PIN) ((PIN) % MAX_GPIO_LINES) #define GPIO2PIN(PIN) ((PIN) % MAX_GPIO_LINES)
#define NSECS 1000000000 #define NSECS 1000000000
#define NSECS_PER_TICK (NSECS / CONFIG_CLOCK_FREQ) #define NSECS_PER_TICK (NSECS / CONFIG_CLOCK_FREQ)
@ -19,10 +19,12 @@ void report_errno(char *where, int rc);
int set_non_blocking(int fd); int set_non_blocking(int fd);
int set_close_on_exec(int fd); int set_close_on_exec(int fd);
int console_setup(char *name); int console_setup(char *name);
void console_sleep(struct timespec ts); void console_sleep(sigset_t *sigset);
// timer.c // timer.c
int timer_check_periodic(uint32_t *ts); int timer_check_periodic(uint32_t *ts);
void timer_disable_signals(void);
void timer_enable_signals(void);
// watchdog.c // watchdog.c
int watchdog_setup(void); int watchdog_setup(void);

View File

@ -163,7 +163,9 @@ command_config_ds18b20(uint32_t *args)
goto fail4; goto fail4;
pthread_t reader_tid; // Not used pthread_t reader_tid; // Not used
timer_disable_signals();
ret = pthread_create(&reader_tid, NULL, reader_start_routine, d); ret = pthread_create(&reader_tid, NULL, reader_start_routine, d);
timer_enable_signals();
if (ret) if (ret)
goto fail5; goto fail5;

View File

@ -6,6 +6,7 @@
#include <time.h> // struct timespec #include <time.h> // struct timespec
#include "autoconf.h" // CONFIG_CLOCK_FREQ #include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "board/io.h" // readl
#include "board/irq.h" // irq_disable #include "board/irq.h" // irq_disable
#include "board/misc.h" // timer_from_us #include "board/misc.h" // timer_from_us
#include "command.h" // DECL_CONSTANT #include "command.h" // DECL_CONSTANT
@ -18,9 +19,14 @@ static struct {
uint32_t last_read_time; uint32_t last_read_time;
// Fields for converting from a systime to ticks // Fields for converting from a systime to ticks
time_t start_sec; time_t start_sec;
// Flags for tracking irq_enable()/irq_disable()
uint32_t must_wake_timers;
// Time of next software timer (also used to convert from ticks to systime) // Time of next software timer (also used to convert from ticks to systime)
uint32_t next_wake_counter; uint32_t next_wake_counter;
struct timespec next_wake; struct timespec next_wake;
// Unix signal tracking
timer_t t_alarm;
sigset_t ss_alarm, ss_sleep;
} TimerInfo; } TimerInfo;
@ -28,14 +34,6 @@ static struct {
* Timespec helpers * Timespec helpers
****************************************************************/ ****************************************************************/
// Compare two 'struct timespec' times
static inline uint8_t
timespec_is_before(struct timespec ts1, struct timespec ts2)
{
return (ts1.tv_sec < ts2.tv_sec
|| (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec));
}
// Convert a 'struct timespec' to a counter value // Convert a 'struct timespec' to a counter value
static inline uint32_t static inline uint32_t
timespec_to_time(struct timespec ts) timespec_to_time(struct timespec ts)
@ -120,8 +118,8 @@ timer_read_time(void)
void void
timer_kick(void) timer_kick(void)
{ {
TimerInfo.next_wake = timespec_read(); struct itimerspec it = { .it_interval = {0, 0}, .it_value = {0, 1} };
TimerInfo.next_wake_counter = timespec_to_time(TimerInfo.next_wake); timer_settime(TimerInfo.t_alarm, TIMER_ABSTIME, &it, NULL);
} }
#define TIMER_IDLE_REPEAT_COUNT 100 #define TIMER_IDLE_REPEAT_COUNT 100
@ -130,13 +128,13 @@ timer_kick(void)
#define TIMER_MIN_TRY_TICKS timer_from_us(2) #define TIMER_MIN_TRY_TICKS timer_from_us(2)
// Invoke timers // Invoke timers
static uint32_t static void
timer_dispatch_many(void) timer_dispatch(void)
{ {
uint32_t repeat_count = TIMER_REPEAT_COUNT; uint32_t repeat_count = TIMER_REPEAT_COUNT, next;
for (;;) { for (;;) {
// Run the next software timer // Run the next software timer
uint32_t next = sched_timer_dispatch(); next = sched_timer_dispatch();
repeat_count--; repeat_count--;
uint32_t lrt = TimerInfo.last_read_time; uint32_t lrt = TimerInfo.last_read_time;
@ -148,14 +146,14 @@ timer_dispatch_many(void)
int32_t diff = next - now; int32_t diff = next - now;
if (diff > (int32_t)TIMER_MIN_TRY_TICKS) if (diff > (int32_t)TIMER_MIN_TRY_TICKS)
// Schedule next timer normally. // Schedule next timer normally.
return next; break;
if (unlikely(!repeat_count)) { if (unlikely(!repeat_count)) {
// Check if there are too many repeat timers // Check if there are too many repeat timers
if (diff < (int32_t)(-timer_from_us(100000))) if (diff < (int32_t)(-timer_from_us(100000)))
try_shutdown("Rescheduled timer in the past"); try_shutdown("Rescheduled timer in the past");
if (sched_tasks_busy()) if (sched_tasks_busy())
return now; return;
repeat_count = TIMER_IDLE_REPEAT_COUNT; repeat_count = TIMER_IDLE_REPEAT_COUNT;
} }
@ -163,24 +161,83 @@ timer_dispatch_many(void)
while (unlikely(diff > 0)) while (unlikely(diff > 0))
diff = next - timer_read_time(); diff = next - timer_read_time();
} }
// Schedule SIGALRM signal
struct itimerspec it;
it.it_interval = (struct timespec){0, 0};
TimerInfo.next_wake = it.it_value = timespec_from_time(next);
TimerInfo.next_wake_counter = next;
TimerInfo.must_wake_timers = 0;
timer_settime(TimerInfo.t_alarm, TIMER_ABSTIME, &it, NULL);
} }
// OS signal handler
static void static void
timer_dispatch(void) timer_signal(int signal)
{ {
uint32_t next = timer_dispatch_many(); TimerInfo.must_wake_timers = 1;
TimerInfo.next_wake = timespec_from_time(next);
TimerInfo.next_wake_counter = next;
} }
void void
timer_init(void) timer_init(void)
{ {
TimerInfo.start_sec = timespec_read().tv_sec + 1; // Initialize ss_alarm signal set
int ret = sigemptyset(&TimerInfo.ss_alarm);
if (ret < 0) {
report_errno("sigemptyset", ret);
return;
}
ret = sigaddset(&TimerInfo.ss_alarm, SIGALRM);
if (ret < 0) {
report_errno("sigaddset", ret);
return;
}
// Initialize ss_sleep signal set
ret = sigprocmask(0, NULL, &TimerInfo.ss_sleep);
if (ret < 0) {
report_errno("sigprocmask ss_sleep", ret);
return;
}
ret = sigdelset(&TimerInfo.ss_sleep, SIGALRM);
if (ret < 0) {
report_errno("sigdelset", ret);
return;
}
// Initialize timespec_to_time() and timespec_from_time()
struct timespec curtime = timespec_read();
TimerInfo.start_sec = curtime.tv_sec + 1;
TimerInfo.next_wake = curtime;
TimerInfo.next_wake_counter = timespec_to_time(curtime);
// Initialize t_alarm signal based timer
ret = timer_create(CLOCK_MONOTONIC, NULL, &TimerInfo.t_alarm);
if (ret < 0) {
report_errno("timer_create", ret);
return;
}
struct sigaction act = {.sa_handler = timer_signal, .sa_flags = SA_RESTART};
ret = sigaction(SIGALRM, &act, NULL);
if (ret < 0) {
report_errno("sigaction", ret);
return;
}
timer_kick(); timer_kick();
} }
DECL_INIT(timer_init); DECL_INIT(timer_init);
// Block SIGALRM signal
void
timer_disable_signals(void)
{
sigprocmask(SIG_BLOCK, &TimerInfo.ss_alarm, NULL);
}
// Restore reception of SIGALRM signal
void
timer_enable_signals(void)
{
sigprocmask(SIG_UNBLOCK, &TimerInfo.ss_alarm, NULL);
}
/**************************************************************** /****************************************************************
* Interrupt wrappers * Interrupt wrappers
@ -210,12 +267,19 @@ irq_restore(irqstatus_t flag)
void void
irq_wait(void) irq_wait(void)
{ {
console_sleep(TimerInfo.next_wake); // Must atomically sleep until signaled
if (!readl(&TimerInfo.must_wake_timers)) {
timer_disable_signals();
if (!TimerInfo.must_wake_timers)
console_sleep(&TimerInfo.ss_sleep);
timer_enable_signals();
}
irq_poll();
} }
void void
irq_poll(void) irq_poll(void)
{ {
if (!timespec_is_before(timespec_read(), TimerInfo.next_wake)) if (readl(&TimerInfo.must_wake_timers))
timer_dispatch(); timer_dispatch();
} }