/* * chopstx-gnu-linux.c - Threads and only threads: Arch specific code * for GNU/Linux emulation * * Copyright (C) 2017 Flying Stone Technology * Author: NIIBE Yutaka * * 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 . * * 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 #include #include #include 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]; 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) { extern void chx_timer_expired (void); 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) { struct chx_thread *tp; 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; }