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:
Kevin O'Connor 2017-07-20 00:02:43 -04:00
parent 3ccecc568d
commit d851882278
10 changed files with 622 additions and 2 deletions

View File

@ -399,7 +399,8 @@ class MCU:
# Serial port
self._serialport = config.get('serial', '/dev/ttyS0')
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)
self._serial = serialhdl.SerialReader(
printer.reactor, self._serialport, baud)

View File

@ -10,6 +10,8 @@ choice
bool "SAM3x8e (Arduino Due)"
config MACH_PRU
bool "Beaglebone PRU"
config MACH_LINUX
bool "Linux process"
config MACH_SIMU
bool "Host simulator"
endchoice
@ -17,6 +19,7 @@ endchoice
source "src/avr/Kconfig"
source "src/sam3x8e/Kconfig"
source "src/pru/Kconfig"
source "src/linux/Kconfig"
source "src/simulator/Kconfig"
# The HAVE_GPIO_x options allow boards to disable support for some

View File

@ -186,7 +186,6 @@ command_sendf(const struct command_encoder *ce, ...)
va_end(args);
writeb(&in_sendf, 0);
return;
}
void

14
src/linux/Kconfig Normal file
View File

@ -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

8
src/linux/Makefile Normal file
View File

@ -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

210
src/linux/console.c Normal file
View File

@ -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();
}

17
src/linux/internal.h Normal file
View File

@ -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

99
src/linux/main.c Normal file
View File

@ -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;
}

233
src/linux/timer.c Normal file
View File

@ -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();
}

36
src/linux/watchdog.c Normal file
View File

@ -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);