From 78c825afe83ff20e43fb680e16d5b86ccbe04fe1 Mon Sep 17 00:00:00 2001
From: NIIBE Yutaka <gniibe@fsij.org>
Date: Fri, 23 Jun 2017 09:05:11 +0900
Subject: Add GNU/Linux support (not yet makefile).

---
 ChangeLog           |   2 +
 chopstx-gnu-linux.c | 342 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 chopstx-gnu-linux.h |   9 ++
 3 files changed, 353 insertions(+)
 create mode 100644 chopstx-gnu-linux.c
 create mode 100644 chopstx-gnu-linux.h

diff --git a/ChangeLog b/ChangeLog
index 4d043e9..cce33a2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,7 @@
 2017-06-23  NIIBE Yutaka  <gniibe@fsij.org>
 
+	* chopstx-gnu-linux.c, chopstx-gnu-linux.h: New.
+
 	* chopstx.c (chx_init): Use chx_init_arch.
 	(chopstx_create): Use chopstx_create_arch.
 	(chx_systick_reset, chx_systick_reload, chx_systick_get)
diff --git a/chopstx-gnu-linux.c b/chopstx-gnu-linux.c
new file mode 100644
index 0000000..b7b6767
--- /dev/null
+++ b/chopstx-gnu-linux.c
@@ -0,0 +1,342 @@
+/*
+ * chopstx-gnu-linux.c - Threads and only threads: Arch specific code
+ *                       for GNU/Linux emulation
+ *
+ * Copyright (C) 2017 Flying Stone Technology
+ * Author: NIIBE Yutaka <gniibe@fsij.org>
+ *
+ * This file is a part of Chopstx, a thread library for embedded.
+ *
+ * Chopstx is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Chopstx is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you may
+ * distribute non-source form of the Program without the copy of the
+ * GNU GPL normally required by section 4, provided you inform the
+ * receipents of GNU GPL by a written offer.
+ *
+ */
+
+#include <unistd.h>
+#include <ucontext.h>
+#include <signal.h>
+#include <sys/time.h>
+
+static sigset_t ss_cur;
+
+static void
+chx_systick_reset (void)
+{
+  const struct itimerval it = { {0, 0}, {0, 0} };
+
+  setitimer (ITIMER_REAL, &it, 0);
+}
+
+static void
+chx_systick_reload (uint32_t ticks)
+{
+  struct itimerval it;
+
+  it.it_value.tv_sec = 0;
+  it.it_value.tv_usec = (ticks / MHZ);
+  it.it_interval.tv_sec = 0;
+  it.it_interval.tv_usec = 0;
+
+  setitimer (ITIMER_REAL, &it, 0);
+}
+
+static uint32_t
+chx_systick_get (void)
+{
+  struct itimerval it;
+  getitimer (ITIMER_REAL, &it);
+  return it.it_value.tv_usec * 72;
+}
+
+static uint32_t
+usec_to_ticks (uint32_t usec)
+{
+  return usec * MHZ;
+}
+
+
+static void
+chx_enable_intr (uint8_t irq_num)
+{
+  sigdelset (&ss_cur, irq_num);
+}
+
+static void
+chx_clr_intr (uint8_t irq_num)
+{				/* Clear pending interrupt.  */
+  (void)irq_num;
+}
+
+static void
+chx_disable_intr (uint8_t irq_num)
+{
+  sigaddset (&ss_cur, irq_num);
+}
+
+static void
+chx_set_intr_prio (uint8_t n)
+{
+  (void)n;
+}
+
+static void
+chx_prio_init (void)
+{
+}
+
+static void
+chx_cpu_sched_lock (void)
+{
+  sigset_t ss;
+
+  sigfillset (&ss);
+  pthread_sigmask (SIG_BLOCK, &ss, &ss_cur);
+}
+
+static void
+chx_cpu_sched_unlock (void)
+{
+  pthread_sigmask (SIG_SETMASK, &ss_cur, NULL);
+}
+
+static void
+idle (void)
+{
+  for (;;)
+    pause ();
+}
+
+void
+chx_handle_intr (uint32_t irq_num)
+{
+  struct chx_pq *p;
+
+  chx_disable_intr (irq_num);
+  chx_spin_lock (&q_intr.lock);
+  for (p = q_intr.q.next; p != (struct chx_pq *)&q_intr.q; p = p->next)
+    if (p->v == irq_num)
+      {			/* should be one at most. */
+	struct chx_px *px = (struct chx_px *)p;
+
+	ll_dequeue (p);
+	chx_wakeup (p);
+	chx_spin_unlock (&q_intr.lock);
+	chx_request_preemption (px->master->prio);
+	return;
+      }
+  chx_spin_unlock (&q_intr.lock);
+}
+
+
+static ucontext_t idle_tc;
+static char idle_stack[4096];
+
+static struct chx_thread main_thread;
+
+void
+chx_sigmask (ucontext_t *uc)
+{
+  /* Modify oldmask to SS_CUR, so that the signal mask will
+   * be set to SS_CUR.
+   *
+   * In user-level, sigset_t is big, but only the first word
+   * is used by the kernel.
+   */
+  memcpy (&uc->uc_sigmask, &ss_cur, sizeof (uint64_t));
+}
+
+static void
+sigalrm_handler (int sig, siginfo_t *siginfo, void *arg)
+{
+  ucontext_t *uc = arg;
+  (void)sig;
+  (void)siginfo;
+  chx_timer_expired ();
+  chx_sigmask (uc);
+}
+
+static void
+chx_init_arch (struct chx_thread *tp)
+{
+  struct sigaction sa;
+
+  sigemptyset (&ss_cur);
+
+  sa.sa_sigaction = sigalrm_handler;
+  sigfillset (&sa.sa_mask);
+  sa.sa_flags = SA_SIGINFO|SA_RESTART;
+  sigaction (SIGALRM, &sa, NULL); 
+
+  getcontext (&idle_tc);
+  idle_tc.uc_stack.ss_sp = idle_stack;
+  idle_tc.uc_stack.ss_size = sizeof (idle_stack);
+  idle_tc.uc_link = NULL;
+  makecontext (&idle_tc, idle, 0);
+
+  getcontext (&tp->tc);
+}
+
+static void
+chx_request_preemption (uint16_t prio)
+{
+  struct chx_thread *tp, *tp_prev;
+  ucontext_t *tcp;
+
+  if (running && (uint16_t)running->prio >= prio)
+    return;
+
+  /* Change the context to another thread with higher priority.  */
+  tp = tp_prev = running;
+  if (tp)
+    {
+      if (tp->flag_sched_rr)
+	{
+	  if (tp->state == THREAD_RUNNING)
+	    {
+	      chx_timer_dequeue (tp);
+	      chx_ready_enqueue (tp);
+	    }
+	}
+      else
+	chx_ready_push (tp);
+      running = NULL;
+    }
+
+  tp = running = chx_ready_pop ();
+  if (tp)
+    {
+      tcp = &tp->tc;
+      if (tp->flag_sched_rr)
+	{
+	  chx_spin_lock (&q_timer.lock);
+	  tp = chx_timer_insert (tp, PREEMPTION_USEC);
+	  chx_spin_unlock (&q_timer.lock);
+	}
+    }
+  else
+    tcp = &idle_tc;
+
+  if (tp_prev)
+    {
+      /*
+       * The swapcontext implementation may reset sigmask in the
+       * middle of its execution, unfortunately.  It is best if
+       * sigmask restore is done at the end of the routine, but we
+       * can't assume that.
+       *
+       * Thus, there might be a race condition with regards to the
+       * user context TCP, if signal mask is cleared and signal comes
+       * in.  To avoid this situation, we block signals.
+       *
+       * We don't need to fill the mask here.  It keeps the condition
+       * of blocking signals before&after swapcontext call.  It is
+       * done by the signal mask for sigaction, the initial creation
+       * of the thread, and the condition of chx_sched function which
+       * mandates holding cpu_sched_lock.
+       */
+      swapcontext (&tp_prev->tc, tcp);
+    }
+  else if (tp)
+    {
+      setcontext (tcp);
+    }
+}
+
+/*
+ * chx_sched: switch to another thread.
+ *
+ * There are two cases:
+ *   YIELD=0 (SLEEP): Current RUNNING thread is already connected to
+ *                    something (mutex, cond, intr, etc.)
+ *   YIELD=1 (YIELD): Current RUNNING thread is active,
+ *                    it is needed to be enqueued to READY queue.
+ *
+ * Returns:
+ *          1 on wakeup by others.
+ *          0 on normal wakeup.
+ *         -1 on cancellation.
+ */
+static uintptr_t
+chx_sched (uint32_t yield)
+{
+  struct chx_thread *tp, *tp_prev;
+  uintptr_t v;
+  ucontext_t *tcp;
+
+  tp = tp_prev = running;
+  if (yield)
+    {
+      if (tp->flag_sched_rr)
+	chx_timer_dequeue (tp);
+      chx_ready_enqueue (tp);
+    }
+
+  running = tp = chx_ready_pop ();
+  if (tp)
+    {
+      v = tp->v;
+      if (tp->flag_sched_rr)
+	{
+	  chx_spin_lock (&q_timer.lock);
+	  tp = chx_timer_insert (tp, PREEMPTION_USEC);
+	  chx_spin_unlock (&q_timer.lock);
+	}
+      tcp = &tp->tc;
+    }
+  else
+    {
+      v = 0;
+      tcp = &idle_tc;
+    }
+
+  swapcontext (&tp_prev->tc, tcp);
+  chx_cpu_sched_unlock ();
+  return v;
+}
+
+static void __attribute__((__noreturn__))
+chx_thread_start (voidfunc thread_entry, void *arg)
+{
+  chx_cpu_sched_unlock ();
+  thread_entry (arg);
+  chopstx_exit (0);
+}
+
+static struct chx_thread *
+chopstx_create_arch (uintptr_t stack_addr, size_t stack_size,
+		     voidfunc thread_entry, void *arg)
+{
+  tp = malloc (sizeof (struct chx_thread));
+  if (!tp)
+    chx_fatal (CHOPSTX_ERR_THREAD_CREATE);
+
+  /*
+   * Calling getcontext with sched_lock held, the context is with
+   * signal blocked.  The sigmask will be cleared in chx_thread_start.
+   */
+  chx_cpu_sched_lock ();
+  getcontext (&tp->tc);
+  tp->tc.uc_stack.ss_sp = (void *)stack_addr;
+  tp->tc.uc_stack.ss_size = stack_size;
+  tp->tc.uc_link = NULL;
+
+  makecontext (&tp->tc, (void (*)(void))chx_thread_start,
+	       4, thread_entry, arg);
+  chx_cpu_sched_unlock ();
+  return tp;
+}
diff --git a/chopstx-gnu-linux.h b/chopstx-gnu-linux.h
new file mode 100644
index 0000000..cce34f1
--- /dev/null
+++ b/chopstx-gnu-linux.h
@@ -0,0 +1,9 @@
+/*
+ * The thread context: specific to GNU/Linux.
+ *
+ * We use the type ucontext_t, which includes all registers;
+ * Note that signal mask is also included in ucontext_t.
+ *
+ */
+
+typedef ucontext_t tcontext_t;
-- 
cgit v1.2.3