diff --git a/src/linux/Makefile b/src/linux/Makefile index 73c8bc93..f234c3af 100644 --- a/src/linux/Makefile +++ b/src/linux/Makefile @@ -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/sensor_ds18b20.c -CFLAGS_klipper.elf += -lutil -lpthread +CFLAGS_klipper.elf += -lutil -lrt -lpthread flash: $(OUT)klipper.elf @echo " Flashing" diff --git a/src/linux/console.c b/src/linux/console.c index 55688322..96782096 100644 --- a/src/linux/console.c +++ b/src/linux/console.c @@ -1,28 +1,27 @@ // TTY based IO // -// Copyright (C) 2017 Kevin O'Connor +// Copyright (C) 2017-2021 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. +#define _GNU_SOURCE #include // errno #include // fcntl -#include // poll +#include // ppoll #include // openpty #include // fprintf #include // memmove #include // chmod -#include // timerfd_create #include // struct timespec #include // ttyname -#include "board/irq.h" // irq_poll +#include "board/irq.h" // irq_wait #include "board/misc.h" // console_sendf #include "command.h" // command_find_block #include "internal.h" // console_setup #include "sched.h" // sched_wake_task -static struct pollfd main_pfd[2]; -#define MP_TIMER_IDX 0 -#define MP_TTY_IDX 1 +static struct pollfd main_pfd[1]; +#define MP_TTY_IDX 0 // Report 'errno' in a message written to stderr void @@ -110,15 +109,6 @@ console_setup(char *name) if (ret) 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; } @@ -188,26 +178,16 @@ console_sendf(const struct command_encoder *ce, va_list args) 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 -console_sleep(struct timespec ts) +console_sleep(sigset_t *sigset) { - struct itimerspec its; - 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); + int ret = ppoll(main_pfd, ARRAY_SIZE(main_pfd), NULL, sigset); if (ret <= 0) { - report_errno("poll main_pfd", ret); + if (errno != EINTR) + report_errno("ppoll main_pfd", ret); return; } if (main_pfd[MP_TTY_IDX].revents) sched_wake_task(&console_wake); - if (main_pfd[MP_TIMER_IDX].revents) - irq_poll(); } diff --git a/src/linux/internal.h b/src/linux/internal.h index 5a9ce907..001a9072 100644 --- a/src/linux/internal.h +++ b/src/linux/internal.h @@ -2,6 +2,7 @@ #define __LINUX_INTERNAL_H // Local definitions for micro-controllers running on linux +#include // sigset_t #include // uint32_t #include "autoconf.h" // CONFIG_CLOCK_FREQ @@ -10,7 +11,6 @@ #define GPIO2PORT(PIN) ((PIN) / MAX_GPIO_LINES) #define GPIO2PIN(PIN) ((PIN) % MAX_GPIO_LINES) - #define NSECS 1000000000 #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_close_on_exec(int fd); int console_setup(char *name); -void console_sleep(struct timespec ts); +void console_sleep(sigset_t *sigset); // timer.c int timer_check_periodic(uint32_t *ts); +void timer_disable_signals(void); +void timer_enable_signals(void); // watchdog.c int watchdog_setup(void); diff --git a/src/linux/sensor_ds18b20.c b/src/linux/sensor_ds18b20.c index 6ac58f35..4525a014 100644 --- a/src/linux/sensor_ds18b20.c +++ b/src/linux/sensor_ds18b20.c @@ -163,7 +163,9 @@ command_config_ds18b20(uint32_t *args) goto fail4; pthread_t reader_tid; // Not used + timer_disable_signals(); ret = pthread_create(&reader_tid, NULL, reader_start_routine, d); + timer_enable_signals(); if (ret) goto fail5; diff --git a/src/linux/timer.c b/src/linux/timer.c index dbfa5a7d..21be0131 100644 --- a/src/linux/timer.c +++ b/src/linux/timer.c @@ -6,6 +6,7 @@ #include // struct timespec #include "autoconf.h" // CONFIG_CLOCK_FREQ +#include "board/io.h" // readl #include "board/irq.h" // irq_disable #include "board/misc.h" // timer_from_us #include "command.h" // DECL_CONSTANT @@ -18,9 +19,14 @@ static struct { uint32_t last_read_time; // Fields for converting from a systime to ticks 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) uint32_t next_wake_counter; struct timespec next_wake; + // Unix signal tracking + timer_t t_alarm; + sigset_t ss_alarm, ss_sleep; } TimerInfo; @@ -28,14 +34,6 @@ static struct { * 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 static inline uint32_t timespec_to_time(struct timespec ts) @@ -120,8 +118,8 @@ timer_read_time(void) void timer_kick(void) { - TimerInfo.next_wake = timespec_read(); - TimerInfo.next_wake_counter = timespec_to_time(TimerInfo.next_wake); + struct itimerspec it = { .it_interval = {0, 0}, .it_value = {0, 1} }; + timer_settime(TimerInfo.t_alarm, TIMER_ABSTIME, &it, NULL); } #define TIMER_IDLE_REPEAT_COUNT 100 @@ -130,13 +128,13 @@ timer_kick(void) #define TIMER_MIN_TRY_TICKS timer_from_us(2) // Invoke timers -static uint32_t -timer_dispatch_many(void) +static void +timer_dispatch(void) { - uint32_t repeat_count = TIMER_REPEAT_COUNT; + uint32_t repeat_count = TIMER_REPEAT_COUNT, next; for (;;) { // Run the next software timer - uint32_t next = sched_timer_dispatch(); + next = sched_timer_dispatch(); repeat_count--; uint32_t lrt = TimerInfo.last_read_time; @@ -148,14 +146,14 @@ timer_dispatch_many(void) int32_t diff = next - now; if (diff > (int32_t)TIMER_MIN_TRY_TICKS) // Schedule next timer normally. - return next; + break; if (unlikely(!repeat_count)) { // Check if there are too many repeat timers if (diff < (int32_t)(-timer_from_us(100000))) try_shutdown("Rescheduled timer in the past"); if (sched_tasks_busy()) - return now; + return; repeat_count = TIMER_IDLE_REPEAT_COUNT; } @@ -163,24 +161,83 @@ timer_dispatch_many(void) while (unlikely(diff > 0)) 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 -timer_dispatch(void) +timer_signal(int signal) { - uint32_t next = timer_dispatch_many(); - TimerInfo.next_wake = timespec_from_time(next); - TimerInfo.next_wake_counter = next; + TimerInfo.must_wake_timers = 1; } 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(); } 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 @@ -210,12 +267,19 @@ irq_restore(irqstatus_t flag) 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 irq_poll(void) { - if (!timespec_is_before(timespec_read(), TimerInfo.next_wake)) + if (readl(&TimerInfo.must_wake_timers)) timer_dispatch(); }