linux: Initial support for running Klipper in a Linux real-time process
Add support for compiling the Klipper micro-controller code as a real-time process capable of running on standard Linux systems. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
3ccecc568d
commit
d851882278
|
@ -399,7 +399,8 @@ class MCU:
|
||||||
# Serial port
|
# Serial port
|
||||||
self._serialport = config.get('serial', '/dev/ttyS0')
|
self._serialport = config.get('serial', '/dev/ttyS0')
|
||||||
baud = 0
|
baud = 0
|
||||||
if not self._serialport.startswith("/dev/rpmsg_"):
|
if not (self._serialport.startswith("/dev/rpmsg_")
|
||||||
|
or self._serialport.startswith("/tmp/klipper_host_")):
|
||||||
baud = config.getint('baud', 250000, minval=2400)
|
baud = config.getint('baud', 250000, minval=2400)
|
||||||
self._serial = serialhdl.SerialReader(
|
self._serial = serialhdl.SerialReader(
|
||||||
printer.reactor, self._serialport, baud)
|
printer.reactor, self._serialport, baud)
|
||||||
|
|
|
@ -10,6 +10,8 @@ choice
|
||||||
bool "SAM3x8e (Arduino Due)"
|
bool "SAM3x8e (Arduino Due)"
|
||||||
config MACH_PRU
|
config MACH_PRU
|
||||||
bool "Beaglebone PRU"
|
bool "Beaglebone PRU"
|
||||||
|
config MACH_LINUX
|
||||||
|
bool "Linux process"
|
||||||
config MACH_SIMU
|
config MACH_SIMU
|
||||||
bool "Host simulator"
|
bool "Host simulator"
|
||||||
endchoice
|
endchoice
|
||||||
|
@ -17,6 +19,7 @@ endchoice
|
||||||
source "src/avr/Kconfig"
|
source "src/avr/Kconfig"
|
||||||
source "src/sam3x8e/Kconfig"
|
source "src/sam3x8e/Kconfig"
|
||||||
source "src/pru/Kconfig"
|
source "src/pru/Kconfig"
|
||||||
|
source "src/linux/Kconfig"
|
||||||
source "src/simulator/Kconfig"
|
source "src/simulator/Kconfig"
|
||||||
|
|
||||||
# The HAVE_GPIO_x options allow boards to disable support for some
|
# The HAVE_GPIO_x options allow boards to disable support for some
|
||||||
|
|
|
@ -186,7 +186,6 @@ command_sendf(const struct command_encoder *ce, ...)
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
writeb(&in_sendf, 0);
|
writeb(&in_sendf, 0);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Kconfig settings for compiling and running the micro-controller code
|
||||||
|
# in a Linux process
|
||||||
|
|
||||||
|
if MACH_LINUX
|
||||||
|
|
||||||
|
config BOARD_DIRECTORY
|
||||||
|
string
|
||||||
|
default "linux"
|
||||||
|
|
||||||
|
config CLOCK_FREQ
|
||||||
|
int
|
||||||
|
default 20000000
|
||||||
|
|
||||||
|
endif
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Additional linux build rules
|
||||||
|
|
||||||
|
dirs-y += src/linux src/generic
|
||||||
|
|
||||||
|
src-y += linux/main.c linux/timer.c linux/console.c linux/watchdog.c
|
||||||
|
src-y += generic/crc16_ccitt.c generic/alloc.c
|
||||||
|
|
||||||
|
CFLAGS_klipper.elf += -lutil
|
|
@ -0,0 +1,210 @@
|
||||||
|
// TTY based IO
|
||||||
|
//
|
||||||
|
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
//
|
||||||
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
#include <errno.h> // errno
|
||||||
|
#include <fcntl.h> // fcntl
|
||||||
|
#include <poll.h> // poll
|
||||||
|
#include <pty.h> // openpty
|
||||||
|
#include <stdio.h> // fprintf
|
||||||
|
#include <string.h> // memmove
|
||||||
|
#include <sys/stat.h> // chmod
|
||||||
|
#include <sys/timerfd.h> // timerfd_create
|
||||||
|
#include <time.h> // struct timespec
|
||||||
|
#include <unistd.h> // ttyname
|
||||||
|
#include "board/irq.h" // irq_poll
|
||||||
|
#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
|
||||||
|
|
||||||
|
// Report 'errno' in a message written to stderr
|
||||||
|
static void
|
||||||
|
report_errno(char *where, int rc)
|
||||||
|
{
|
||||||
|
int e = errno;
|
||||||
|
fprintf(stderr, "Got error %d in %s: (%d)%s\n", rc, where, e, strerror(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Setup
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
static int
|
||||||
|
set_non_blocking(int fd)
|
||||||
|
{
|
||||||
|
int flags = fcntl(fd, F_GETFL);
|
||||||
|
if (flags < 0) {
|
||||||
|
report_errno("fcntl getfl", flags);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
if (ret < 0) {
|
||||||
|
report_errno("fcntl setfl", flags);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
set_close_on_exec(int fd)
|
||||||
|
{
|
||||||
|
int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||||
|
if (ret < 0) {
|
||||||
|
report_errno("fcntl set cloexec", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
console_setup(char *name)
|
||||||
|
{
|
||||||
|
// Open pseudo-tty
|
||||||
|
struct termios ti;
|
||||||
|
memset(&ti, 0, sizeof(ti));
|
||||||
|
int mfd, sfd, ret = openpty(&mfd, &sfd, NULL, &ti, NULL);
|
||||||
|
if (ret) {
|
||||||
|
report_errno("openpty", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = set_non_blocking(mfd);
|
||||||
|
if (ret)
|
||||||
|
return -1;
|
||||||
|
ret = set_close_on_exec(mfd);
|
||||||
|
if (ret)
|
||||||
|
return -1;
|
||||||
|
ret = set_close_on_exec(sfd);
|
||||||
|
if (ret)
|
||||||
|
return -1;
|
||||||
|
main_pfd[MP_TTY_IDX].fd = mfd;
|
||||||
|
main_pfd[MP_TTY_IDX].events = POLLIN;
|
||||||
|
|
||||||
|
// Create symlink to tty
|
||||||
|
unlink(name);
|
||||||
|
char *tname = ttyname(sfd);
|
||||||
|
if (!tname) {
|
||||||
|
report_errno("ttyname", 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = symlink(tname, name);
|
||||||
|
if (ret) {
|
||||||
|
report_errno("symlink", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = chmod(tname, 0660);
|
||||||
|
if (ret) {
|
||||||
|
report_errno("chmod", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure stderr is non-blocking
|
||||||
|
ret = set_non_blocking(STDERR_FILENO);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Console handling
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
static struct task_wake console_wake;
|
||||||
|
static char receive_buf[4096];
|
||||||
|
static int receive_pos;
|
||||||
|
|
||||||
|
// Process any incoming commands
|
||||||
|
void
|
||||||
|
console_task(void)
|
||||||
|
{
|
||||||
|
if (!sched_check_wake(&console_wake))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
int ret = read(main_pfd[MP_TTY_IDX].fd, &receive_buf[receive_pos]
|
||||||
|
, sizeof(receive_buf) - receive_pos);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EWOULDBLOCK) {
|
||||||
|
ret = 0;
|
||||||
|
} else {
|
||||||
|
report_errno("read", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ret == 15 && receive_buf[receive_pos+14] == '\n'
|
||||||
|
&& memcmp(&receive_buf[receive_pos], "FORCE_SHUTDOWN\n", 15) == 0)
|
||||||
|
shutdown("Force shutdown command");
|
||||||
|
|
||||||
|
// Find and dispatch message blocks in the input
|
||||||
|
int len = receive_pos + ret;
|
||||||
|
uint8_t pop_count, msglen = len > MESSAGE_MAX ? MESSAGE_MAX : len;
|
||||||
|
ret = command_find_block(receive_buf, msglen, &pop_count);
|
||||||
|
if (ret > 0)
|
||||||
|
command_dispatch(receive_buf, pop_count);
|
||||||
|
if (ret) {
|
||||||
|
len -= pop_count;
|
||||||
|
if (len) {
|
||||||
|
memmove(receive_buf, &receive_buf[pop_count], len);
|
||||||
|
sched_wake_task(&console_wake);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
receive_pos = len;
|
||||||
|
}
|
||||||
|
DECL_TASK(console_task);
|
||||||
|
|
||||||
|
// Encode and transmit a "response" message
|
||||||
|
void
|
||||||
|
console_sendf(const struct command_encoder *ce, va_list args)
|
||||||
|
{
|
||||||
|
// Generate message
|
||||||
|
char buf[MESSAGE_MAX];
|
||||||
|
uint8_t msglen = command_encodef(buf, ce, args);
|
||||||
|
command_add_frame(buf, msglen);
|
||||||
|
|
||||||
|
// Transmit message
|
||||||
|
int ret = write(main_pfd[MP_TTY_IDX].fd, buf, msglen);
|
||||||
|
if (ret < 0)
|
||||||
|
report_errno("write", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep until the specified time (waking early for console input if needed)
|
||||||
|
void
|
||||||
|
console_sleep(struct timespec ts)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
if (ret <= 0) {
|
||||||
|
report_errno("poll 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();
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef __LINUX_INTERNAL_H
|
||||||
|
#define __LINUX_INTERNAL_H
|
||||||
|
// Local definitions for micro-controllers running on linux
|
||||||
|
|
||||||
|
#include <time.h> // struct timespec
|
||||||
|
|
||||||
|
// console.c
|
||||||
|
int console_setup(char *name);
|
||||||
|
void console_sleep(struct timespec ts);
|
||||||
|
|
||||||
|
// timer.c
|
||||||
|
int timer_check_periodic(struct timespec *ts);
|
||||||
|
|
||||||
|
// watchdog.c
|
||||||
|
int watchdog_setup(void);
|
||||||
|
|
||||||
|
#endif // internal.h
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Main starting point for micro-controller code running on linux systems
|
||||||
|
//
|
||||||
|
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
//
|
||||||
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
#include </usr/include/sched.h> // sched_setscheduler
|
||||||
|
#include <stdio.h> // fprintf
|
||||||
|
#include <string.h> // memset
|
||||||
|
#include <unistd.h> // getopt
|
||||||
|
#include "board/misc.h" // console_sendf
|
||||||
|
#include "command.h" // DECL_CONSTANT
|
||||||
|
#include "internal.h" // console_setup
|
||||||
|
#include "sched.h" // sched_main
|
||||||
|
|
||||||
|
DECL_CONSTANT(MCU, "linux");
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Real-time setup
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
static int
|
||||||
|
realtime_setup(void)
|
||||||
|
{
|
||||||
|
struct sched_param sp;
|
||||||
|
memset(&sp, 0, sizeof(sp));
|
||||||
|
sp.sched_priority = 1;
|
||||||
|
int ret = sched_setscheduler(0, SCHED_FIFO, &sp);
|
||||||
|
if (ret < 0) {
|
||||||
|
report_errno("sched_setscheduler", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Restart
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
static char **orig_argv;
|
||||||
|
|
||||||
|
void
|
||||||
|
command_config_reset(uint32_t *args)
|
||||||
|
{
|
||||||
|
if (! sched_is_shutdown())
|
||||||
|
shutdown("config_reset only available when shutdown");
|
||||||
|
int ret = execv(orig_argv[0], orig_argv);
|
||||||
|
report_errno("execv", ret);
|
||||||
|
}
|
||||||
|
DECL_COMMAND_FLAGS(config_reset, HF_IN_SHUTDOWN, "config_reset");
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Startup
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// Parse program args
|
||||||
|
orig_argv = argv;
|
||||||
|
int opt, watchdog = 0, realtime = 0;
|
||||||
|
while ((opt = getopt(argc, argv, "wr")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'w':
|
||||||
|
watchdog = 1;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
realtime = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Usage: %s [-w] [-r]\n", argv[0]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
if (watchdog) {
|
||||||
|
int ret = watchdog_setup();
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realtime) {
|
||||||
|
int ret = realtime_setup();
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = console_setup("/tmp/klipper_host_mcu");
|
||||||
|
if (ret)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
sched_main();
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,233 @@
|
||||||
|
// Handling of timers on linux systems
|
||||||
|
//
|
||||||
|
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
//
|
||||||
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
#include <time.h> // struct timespec
|
||||||
|
#include "autoconf.h" // CONFIG_CLOCK_FREQ
|
||||||
|
#include "board/misc.h" // timer_from_us
|
||||||
|
#include "board/irq.h" // irq_disable
|
||||||
|
#include "basecmd.h" // stats_note_sleep
|
||||||
|
#include "command.h" // DECL_CONSTANT
|
||||||
|
#include "generic/timer_irq.h" // timer_dispatch_many
|
||||||
|
#include "internal.h" // console_sleep
|
||||||
|
#include "sched.h" // DECL_INIT
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Timespec helpers
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
static uint32_t last_read_time_counter;
|
||||||
|
static struct timespec last_read_time, next_wake_time;
|
||||||
|
static time_t start_sec;
|
||||||
|
|
||||||
|
#define NSECS 1000000000
|
||||||
|
#define NSECS_PER_TICK (NSECS / CONFIG_CLOCK_FREQ)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
return ((ts.tv_sec - start_sec) * CONFIG_CLOCK_FREQ
|
||||||
|
+ ts.tv_nsec / NSECS_PER_TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an internal time counter to a 'struct timespec'
|
||||||
|
static inline struct timespec
|
||||||
|
timespec_from_time(uint32_t time)
|
||||||
|
{
|
||||||
|
int32_t counter_diff = time - last_read_time_counter;
|
||||||
|
struct timespec ts;
|
||||||
|
ts.tv_sec = last_read_time.tv_sec;
|
||||||
|
ts.tv_nsec = last_read_time.tv_nsec + counter_diff * NSECS_PER_TICK;
|
||||||
|
if ((unsigned long)ts.tv_nsec >= NSECS) {
|
||||||
|
if (ts.tv_nsec < 0) {
|
||||||
|
ts.tv_sec--;
|
||||||
|
ts.tv_nsec += NSECS;
|
||||||
|
} else {
|
||||||
|
ts.tv_sec++;
|
||||||
|
ts.tv_nsec -= NSECS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a given number of nanoseconds to a 'struct timespec'
|
||||||
|
static inline struct timespec
|
||||||
|
timespec_add(struct timespec ts, long ns)
|
||||||
|
{
|
||||||
|
ts.tv_nsec += ns;
|
||||||
|
if (ts.tv_nsec >= NSECS) {
|
||||||
|
ts.tv_sec++;
|
||||||
|
ts.tv_nsec -= NSECS;
|
||||||
|
}
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the current time
|
||||||
|
static struct timespec
|
||||||
|
timespec_read(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodically update last_read_time / last_read_time_counter
|
||||||
|
void
|
||||||
|
timespec_update(void)
|
||||||
|
{
|
||||||
|
last_read_time = timespec_read();
|
||||||
|
last_read_time_counter = timespec_to_time(last_read_time);
|
||||||
|
}
|
||||||
|
DECL_TASK(timespec_update);
|
||||||
|
|
||||||
|
// Check if a given time has past
|
||||||
|
int
|
||||||
|
timer_check_periodic(struct timespec *ts)
|
||||||
|
{
|
||||||
|
if (timespec_is_before(next_wake_time, *ts))
|
||||||
|
return 0;
|
||||||
|
*ts = next_wake_time;
|
||||||
|
ts->tv_sec += 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Timers
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
|
||||||
|
|
||||||
|
// Return the number of clock ticks for a given number of microseconds
|
||||||
|
uint32_t
|
||||||
|
timer_from_us(uint32_t us)
|
||||||
|
{
|
||||||
|
return us * (CONFIG_CLOCK_FREQ / 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if time1 is before time2. Always use this function to
|
||||||
|
// compare times as regular C comparisons can fail if the counter
|
||||||
|
// rolls over.
|
||||||
|
uint8_t
|
||||||
|
timer_is_before(uint32_t time1, uint32_t time2)
|
||||||
|
{
|
||||||
|
return (int32_t)(time1 - time2) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the current time (in clock ticks)
|
||||||
|
uint32_t
|
||||||
|
timer_read_time(void)
|
||||||
|
{
|
||||||
|
return timespec_to_time(timespec_read());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate timer dispatch as soon as possible
|
||||||
|
void
|
||||||
|
timer_kick(void)
|
||||||
|
{
|
||||||
|
next_wake_time = last_read_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct timespec timer_repeat_until;
|
||||||
|
#define TIMER_IDLE_REPEAT_NS 500000
|
||||||
|
#define TIMER_REPEAT_NS 100000
|
||||||
|
|
||||||
|
#define TIMER_MIN_TRY_NS 1000
|
||||||
|
#define TIMER_DEFER_REPEAT_NS 5000
|
||||||
|
|
||||||
|
// Invoke timers
|
||||||
|
static void
|
||||||
|
timer_dispatch(void)
|
||||||
|
{
|
||||||
|
struct timespec tru = timer_repeat_until;
|
||||||
|
for (;;) {
|
||||||
|
// Run the next software timer
|
||||||
|
uint32_t next = sched_timer_dispatch();
|
||||||
|
struct timespec nt = timespec_from_time(next);
|
||||||
|
|
||||||
|
struct timespec now = timespec_read();
|
||||||
|
if (!timespec_is_before(nt, timespec_add(now, TIMER_MIN_TRY_NS))) {
|
||||||
|
// Schedule next timer normally.
|
||||||
|
next_wake_time = nt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(timespec_is_before(tru, now))) {
|
||||||
|
// Check if there are too many repeat timers
|
||||||
|
if (unlikely(timespec_is_before(timespec_add(nt, 100000000), now))
|
||||||
|
&& !sched_is_shutdown())
|
||||||
|
shutdown("Rescheduled timer in the past");
|
||||||
|
if (sched_tasks_busy()) {
|
||||||
|
timer_repeat_until = timespec_add(now, TIMER_REPEAT_NS);
|
||||||
|
next_wake_time = timespec_add(now, TIMER_DEFER_REPEAT_NS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timer_repeat_until = timespec_add(now, TIMER_IDLE_REPEAT_NS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next timer in the past or near future - wait for it to be ready
|
||||||
|
while (unlikely(timespec_is_before(now, nt)))
|
||||||
|
now = timespec_read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
timer_init(void)
|
||||||
|
{
|
||||||
|
start_sec = timespec_read().tv_sec;
|
||||||
|
timer_repeat_until.tv_sec = start_sec + 2;
|
||||||
|
timespec_update();
|
||||||
|
timer_kick();
|
||||||
|
}
|
||||||
|
DECL_INIT(timer_init);
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Interrupt wrappers
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
void
|
||||||
|
irq_disable(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
irq_enable(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
irqstatus_t
|
||||||
|
irq_save(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
irq_restore(irqstatus_t flag)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
irq_wait(void)
|
||||||
|
{
|
||||||
|
console_sleep(next_wake_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
irq_poll(void)
|
||||||
|
{
|
||||||
|
if (!timespec_is_before(timespec_read(), next_wake_time))
|
||||||
|
timer_dispatch();
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Support for Linux watchdog
|
||||||
|
//
|
||||||
|
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
//
|
||||||
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
#include <fcntl.h> // open
|
||||||
|
#include <unistd.h> // write
|
||||||
|
#include "internal.h" // report_errno
|
||||||
|
#include "sched.h" // DECL_TASK
|
||||||
|
|
||||||
|
static int watchdog_fd = -1;
|
||||||
|
|
||||||
|
int
|
||||||
|
watchdog_setup(void)
|
||||||
|
{
|
||||||
|
int ret = open("/dev/watchdog", O_RDWR|O_CLOEXEC);
|
||||||
|
if (ret < 0) {
|
||||||
|
report_errno("watchdog open", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
watchdog_fd = ret;
|
||||||
|
return set_non_blocking(watchdog_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
watchdog_task(void)
|
||||||
|
{
|
||||||
|
static struct timespec next_watchdog_time;
|
||||||
|
if (watchdog_fd < 0 || !timer_check_periodic(&next_watchdog_time))
|
||||||
|
return;
|
||||||
|
int ret = write(watchdog_fd, ".", 1);
|
||||||
|
if (ret <= 0)
|
||||||
|
report_errno("watchdog write", ret);
|
||||||
|
}
|
||||||
|
DECL_TASK(watchdog_task);
|
Loading…
Reference in New Issue