aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--klippy/mcu.py3
-rw-r--r--src/Kconfig3
-rw-r--r--src/command.c1
-rw-r--r--src/linux/Kconfig14
-rw-r--r--src/linux/Makefile8
-rw-r--r--src/linux/console.c210
-rw-r--r--src/linux/internal.h17
-rw-r--r--src/linux/main.c99
-rw-r--r--src/linux/timer.c233
-rw-r--r--src/linux/watchdog.c36
10 files changed, 622 insertions, 2 deletions
diff --git a/klippy/mcu.py b/klippy/mcu.py
index f0397f19..390375f2 100644
--- a/klippy/mcu.py
+++ b/klippy/mcu.py
@@ -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)
diff --git a/src/Kconfig b/src/Kconfig
index 5515bdb5..c28cec14 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -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
diff --git a/src/command.c b/src/command.c
index 8780ae8a..1519a428 100644
--- a/src/command.c
+++ b/src/command.c
@@ -186,7 +186,6 @@ command_sendf(const struct command_encoder *ce, ...)
va_end(args);
writeb(&in_sendf, 0);
- return;
}
void
diff --git a/src/linux/Kconfig b/src/linux/Kconfig
new file mode 100644
index 00000000..b80ca3a7
--- /dev/null
+++ b/src/linux/Kconfig
@@ -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
diff --git a/src/linux/Makefile b/src/linux/Makefile
new file mode 100644
index 00000000..8ebb9945
--- /dev/null
+++ b/src/linux/Makefile
@@ -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
diff --git a/src/linux/console.c b/src/linux/console.c
new file mode 100644
index 00000000..dcffc701
--- /dev/null
+++ b/src/linux/console.c
@@ -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();
+}
diff --git a/src/linux/internal.h b/src/linux/internal.h
new file mode 100644
index 00000000..1ade2233
--- /dev/null
+++ b/src/linux/internal.h
@@ -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
diff --git a/src/linux/main.c b/src/linux/main.c
new file mode 100644
index 00000000..55d7ec54
--- /dev/null
+++ b/src/linux/main.c
@@ -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;
+}
diff --git a/src/linux/timer.c b/src/linux/timer.c
new file mode 100644
index 00000000..016f3da3
--- /dev/null
+++ b/src/linux/timer.c
@@ -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();
+}
diff --git a/src/linux/watchdog.c b/src/linux/watchdog.c
new file mode 100644
index 00000000..d89fcd29
--- /dev/null
+++ b/src/linux/watchdog.c
@@ -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);