2 * Copyright 2014 Olivier Matz <zer0@droids-corp.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * Revision : $Id: main.c,v 1.10 2009-11-08 17:24:33 zer0 Exp $
25 * AVR simulator. This code is used on host (PC for instance) to
26 * generate a signal that will call other aversive modules like
27 * scheduler or uart. The goal is to simulate the behaviour of
28 * hardware interrupts in a unix application.
32 * To simulate AVR interruptions, we use signals. The signals are
33 * always allowed, and the handler is located in hostsim code. In the
34 * hostsim handler, we check if the IRQ are allowed (in this case we
35 * call the handler) or not (in this case we ignore the IRQ). Indeed,
36 * the sei() / cli() functions just set a flag located in hostsim.
38 * Note that a signal handler can be preempted by itself. It means
52 #include <sys/types.h>
54 #include <sys/queue.h>
58 #ifdef CONFIG_MODULE_SCHEDULER
59 #include <scheduler.h>
61 #ifdef CONFIG_MODULE_UART
62 #include <uart_host.h>
65 /* list of scheduled ittimers */
66 LIST_HEAD(hostsim_ittimer_list, hostsim_ittimer);
67 struct hostsim_ittimer_list tim_list = LIST_HEAD_INITIALIZER(&tim_list);
69 /* structure describing an interruption timer */
70 struct hostsim_ittimer {
71 LIST_ENTRY(hostsim_ittimer) next;
75 void (*handler)(void);
78 /* tell if global emulated IRQ are locked (1) or allowed (0). The
79 * default IRQ state is locked as on a real avr */
82 static uint8_t irq_flags = IRQ_LOCKED;
84 static int old_stdin, old_stdout;
85 static int stdin_pipe[2];
86 static int stdout_pipe[2];
88 /* ring buffer to send from pthread to signal */
89 #define RG_SIZE 256 /* pow of 2 */
90 #define RG_MASK (RG_SIZE-1)
91 volatile char out_buf[RG_SIZE];
92 volatile unsigned out_tail = 0;
93 volatile unsigned out_head = 0;
94 pthread_t out_pthread;
95 volatile char in_buf[RG_SIZE];
96 volatile unsigned in_tail = 0;
97 volatile unsigned in_head = 0;
100 /* store the previous state of termios, restored in hostsim_exit() */
101 static struct termios oldterm;
103 void hostsim_sei(void)
105 irq_flags = IRQ_ALLOWED;
108 void hostsim_cli(void)
110 irq_flags = IRQ_LOCKED;
113 /* lock emulated IRQ and return the previous state */
114 uint8_t hostsim_irq_save(void)
116 uint8_t old = irq_flags;
121 /* returns a monotonic clock in nanoseconds */
122 static inline uint64_t get_clk(void)
127 if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts) < 0) {
128 printf("clock_getres() failed: %s\n", strerror(errno));
137 static void handle_interrupts(void)
139 struct hostsim_ittimer *tim;
147 /* do the uart events if data is available */
148 if (irq_flags == IRQ_ALLOWED) {
149 while ((int)(out_tail - out_head) > 0) {
150 irq_flags = IRQ_LOCKED;
152 c = out_buf[out_head & RG_MASK];
153 if (__sync_val_compare_and_swap(&out_head,
154 head, head + 1) != head)
156 uart_host_tx_event(c);
157 irq_flags = IRQ_ALLOWED;
159 while ((int)(in_tail - in_head) > 0) {
160 irq_flags = IRQ_LOCKED;
162 c = in_buf[in_head & RG_MASK];
163 uart_host_rx_event(c);
164 if (__sync_val_compare_and_swap(&in_head,
165 head, head + 1) != head)
167 irq_flags = IRQ_ALLOWED;
171 /* browse all timers */
172 LIST_FOREACH(tim, &tim_list, next) {
174 /* Call the handler if it's time to. We use an atomic operation
175 * because we can be interrupted by our own signal any moment */
177 cur = tim->prev_tick;
178 next = tim->prev_tick + tim->period_ns;
183 if (__sync_val_compare_and_swap(&tim->prev_tick,
187 /* if irq are disabled, just mark the timer as pending,
188 * it will be executed from hostsim_irq_restore(). We
189 * may loose interrupts if they stay locked too long,
190 * like on the real hw */
191 if (irq_flags == IRQ_LOCKED)
194 irq_flags = IRQ_LOCKED;
197 irq_flags = IRQ_ALLOWED;
201 /* also execute the irq if it was pending */
202 if (irq_flags == IRQ_ALLOWED && tim->pending == 1) {
203 irq_flags = IRQ_LOCKED;
206 irq_flags = IRQ_ALLOWED;
211 /* restore the state given as parameter */
212 void hostsim_irq_restore(uint8_t flags)
214 /* on transition "locked" -> "unlocked", call any pending interrupts
215 * before releasing IRQ lock. For other transitions, just set the
216 * irq_flags variable */
218 if (irq_flags == IRQ_ALLOWED || flags == IRQ_LOCKED) {
223 irq_flags = IRQ_ALLOWED;
227 /* return 1 if emulated IRQ are locked */
228 uint8_t hostsim_irq_locked(void)
230 return irq_flags == IRQ_LOCKED;
233 /* replacement for wait_ms() */
234 void host_wait_ms(int ms)
236 struct timeval tv, tv2, diff;
238 gettimeofday(&tv, NULL);
239 diff.tv_sec = (1000 * ms) / 1000000;
240 diff.tv_usec = (1000 * ms) % 1000000;
241 timeradd(&tv, &diff, &tv);
242 gettimeofday(&tv2, NULL);
244 while (timercmp(&tv2, &tv, <)) {
246 gettimeofday(&tv2, NULL);
250 /* add a new timer: loaded at init and cannot be unloaded. */
251 struct hostsim_ittimer *hostsim_ittimer_add(void (*handler)(void),
254 struct hostsim_ittimer *tim;
256 tim = malloc(sizeof(*tim));
258 printf("not enough memory: cannot allocate timer\n");
262 tim->period_ns = period_ns;
263 tim->prev_tick = get_clk();
264 tim->handler = handler;
265 LIST_INSERT_HEAD(&tim_list, tim, next);
269 /* the signal handler, preemptable by itself */
271 static void sigtimer(__attribute__((unused)) int sig,
272 __attribute__((unused)) siginfo_t *info,
273 __attribute__((unused)) void *uc)
275 void sigtimer(__attribute__((unused)) int sig)
278 static int recurs = 0;
287 /* enable loaded ittimers */
288 int hostsim_ittimer_enable(unsigned timer_resolution_us)
290 struct sigaction sigact;
293 /* register a signal handler, which is interruptible */
294 memset(&sigact, 0, sizeof(sigact));
295 sigemptyset(&sigact.sa_mask);
296 sigact.sa_flags |= SA_NODEFER;
297 sigact.sa_sigaction = sigtimer;
298 sigaction(SIGALRM, &sigact, NULL);
300 /* do not interrupt syscalls */
301 if (siginterrupt (SIGALRM, 0) != 0)
304 memset(&t, 0, sizeof(t));
305 t.it_value.tv_usec = timer_resolution_us;
306 t.it_value.tv_sec = 0;
307 t.it_interval.tv_usec = timer_resolution_us;
308 t.it_interval.tv_sec = 0;
310 if (setitimer(ITIMER_REAL, &t, NULL) < 0) {
311 printf("setitimer failed\n");
318 void *hostsim_uart_stdin(void *arg)
326 /* read on old stdin and put it in pipe */
328 if (in_tail - in_head >= (RG_SIZE - 1)) {
329 /* no more room, wait (should we drop ?) */
334 n = read(old_stdin, &c, 1);
338 if (prev_c == '\x01' /* ctrl-a */ && c == 'q')
343 in_buf[in_tail & RG_MASK] = c;
346 write(stdin_pipe[1], &c, 1);
352 void *hostsim_uart_stdout(void *arg)
359 /* read on our pipe, and forward it to the old stdout */
361 if (out_tail - out_head >= (RG_SIZE - 1)) {
362 /* no more room, wait (should we drop ?) */
367 n = read(stdout_pipe[0], &c, 1);
369 printf("read failed: %s\n", strerror(errno));
373 out_buf[out_tail & RG_MASK] = c;
376 write(old_stdout, &c, 1);
382 /* initialize hostsim framework for uart */
383 int hostsim_uart_init(void)
388 printf("hostsim/uart: type ctrl-a + q to exit\n");
390 tcgetattr(0, &oldterm);
391 memcpy(&term, &oldterm, sizeof(term));
392 term.c_lflag &= ~(ICANON | ECHO | ISIG);
393 tcsetattr(0, TCSANOW, &term);
396 setbuf(stdout, NULL);
398 /* duplicate stdin */
403 /* duplicate stdout */
409 if (pipe(stdin_pipe) < 0)
411 if (pipe(stdout_pipe) < 0)
414 /* replace file desc 0 (stdin) by our pipe */
415 if (dup2(stdin_pipe[0], 0) < 0)
417 close(stdin_pipe[0]);
419 /* replace file desc 1 (stdout) by our pipe */
420 if (dup2(stdout_pipe[1], 1) < 0)
422 close(stdout_pipe[1]);
424 ret = pthread_create(&in_pthread, NULL, hostsim_uart_stdin, NULL);
426 printf("pthread_create() returned %d\n", ret);
429 ret = pthread_create(&out_pthread, NULL, hostsim_uart_stdout, NULL);
431 printf("pthread_create() returned %d\n", ret);
438 int hostsim_uart_exit(void)
440 tcsetattr(old_stdin, TCSANOW, &oldterm);
444 #endif /* HOST_VERSION */