Blame view

RIOT/cpu/mips32r2_common/thread_arch.c 11.7 KB
a752c7ab   elopes   add first test an...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
  /*
   * Copyright 2016, Imagination Technologies Limited and/or its
   *                 affiliated group companies.
   *
   * This file is subject to the terms and conditions of the GNU Lesser
   * General Public License v2.1. See the file LICENSE in the top level
   * directory for more details.
   */
  #include <mips/cpu.h>
  #include <mips/hal.h>
  #include <unistd.h>
  #include <sys/stat.h>
  #include <stdio.h>
  #include <string.h>
  
  #include "thread.h"
  #include "cpu.h"
  #include "irq.h"
  #include "cpu_conf.h"
  #include "periph_conf.h" /* for debug uart number */
  #include "periph/uart.h"
  #include "malloc.h"
  
  #define STACK_END_PAINT    (0xdeadc0de)
  #define C0_STATUS_EXL      (2)
  #define PADDING            (16)
  #define MICROMIPS_ISA_MODE (1)
  #define M32_SYSCALL        (0xC)
  #define M32_SYSCALL_MASK   (0xfc00003f)
  
  /*
   * note the major 16bits of a 32bit MicroMIPS opcode appear first in the
   * instruction stream
   */
  #define MM_SYSCALL         (0x8B7C0000)
  #define MM_SYSCALL_MASK    (0xfffffc00)
  
  
  #ifdef MIPS_HARD_FLOAT
  /* pointer to the current and old fpu context for lazy context switching */
  static struct fp64ctx *currentfpctx;   /* fpu context of current running task */
  static struct fp64ctx *oldfpctx;       /* fpu context of last task that executed fpu */
  #endif
  
  /*
   *    Stack Layout, note struct gpctx is defined in
   *    $MIPS_ELF_ROOT/mips-mti-elf/include/mips/hal.h
   *
   *    Top Of Stack
   *     ---------------
   *    |               |
   *    |  User stack   |
   *    |               |
   *     ---------------  <--- gpctx->sp
   *    |               |
   *    |    gpctx      |
   *    |               |
   *     ---------------
   *    |  16 byte pad  |
   *     ---------------   <--- sched_active_thread->sp
   */
  
  char *thread_arch_stack_init(thread_task_func_t task_func, void *arg,
                               void *stack_start, int stack_size)
  {
      /* make sure it is aligned to 8 bytes this is a requirement of the O32 ABI */
      uintptr_t *p = (uintptr_t *)(((long)(stack_start) + stack_size) & ~7);
      uintptr_t *fp;
  
      /* paint */
      p--;
      *p-- = STACK_END_PAINT;
  
      /* prepare stack for __exception_restore() */
      fp = p;
      p -= sizeof(struct gpctx) / sizeof(unsigned int);
  
      struct gpctx *initial_ctx = (struct gpctx *)p;
      initial_ctx->a[0] = (reg_t)arg;
      initial_ctx->status = mips32_get_c0(C0_STATUS) | SR_IE; /* Enable interrupts */
      __asm volatile ("sw    $gp, 0(%0)" : : "r" (&initial_ctx->gp));
      initial_ctx->epc = (reg_t)task_func;
      initial_ctx->ra = (reg_t)sched_task_exit;
      initial_ctx->sp = (reg_t)fp;
      initial_ctx->link = (struct linkctx *)NULL;
  
  #ifdef MIPS_MICROMIPS
      initial_ctx->epc |= MICROMIPS_ISA_MODE;
      initial_ctx->ra |= MICROMIPS_ISA_MODE;
  #endif
  
  #ifdef MIPS_HARD_FLOAT
      /*
       * Disable FPU so we get an exception on first use to allow
       * Lazy FPU context save and restore
       */
      initial_ctx->status &= ~SR_CU1;
      initial_ctx->status |= SR_FR; /*use double width FPU */
  #endif
      /*
       * note the -4 (-16 bytes) as the toolchain exception handling code
       * adjusts the sp for alignment
       */
      p -= PADDING/sizeof(unsigned int);
  
      return (void *)p;
  }
  
  void thread_arch_stack_print(void)
  {
      uintptr_t *sp = (void *)sched_active_thread->sp;
  
      printf("Stack trace:\n");
      while (*sp != STACK_END_PAINT) {
          printf(" 0x%p: 0x%08lx\n", sp, *sp);
          sp++;
      }
  }
  
  extern void __exception_restore(void);
  void thread_arch_start_threading(void)
  {
      unsigned int status = mips32_get_c0(C0_STATUS);
  
      /*
       * Set Exception level if we are not already running at it
       * the EXL mode depends on the bootloader.
       */
  
      if ((status & C0_STATUS_EXL) == 0) {
          mips32_set_c0(C0_STATUS, status | C0_STATUS_EXL);
      }
  
      sched_run();
  
      __asm volatile ("lw    $sp, 0(%0)" : : "r" (&sched_active_thread->sp));
  
      __exception_restore();
  
      UNREACHABLE();
  }
  
  void thread_arch_yield(void)
  {
      /*
       * throw a syscall exception to get into exception level
       * we context switch at exception level.
       *
       * Note syscall 1 is reserved for UHI see:
       * http://wiki.prplfoundation.org/w/images/4/42/UHI_Reference_Manual.pdf
       */
      __asm volatile ("syscall 2");
  }
  
  struct linkctx* exctx_find(reg_t id, struct gpctx *gp)
  {
      struct linkctx **ctx = (struct linkctx **)&gp->link;
      while (*ctx) {
          if ((*ctx)->id == id) {
              return *ctx;
          }
          ctx = &(*ctx)->next;
      }
      return NULL;
  }
  
  /* unaligned access helper */
  static inline uint32_t
  #ifndef __clang__
  /* Clang does not support attribute optimize */
  __attribute__((optimize("-O3")))
  #endif
  mem_rw(const void *vaddr)
  {
      uint32_t v;
      memcpy(&v, vaddr, sizeof(v));
      return v;
  }
  
  
  #ifdef MIPS_DSP
  extern int _dsp_save(struct dspctx *ctx);
  extern int _dsp_load(struct dspctx *ctx);
  #endif
  /*
   * The nomips16 attribute should not really be needed, it works around a toolchain
   * issue in 2016.05-03.
   */
  void __attribute__((nomips16))
  _mips_handle_exception(struct gpctx *ctx, int exception)
  {
      unsigned int syscall_num = 0;
  #ifdef MIPS_DSP
      struct dspctx dsp_ctx; /* intentionally allocated on current stack */
  #endif
  
      switch (exception) {
  
          case EXC_SYS:
  #ifdef MIPS_MICROMIPS
              /* note major 16bits of opcode is first in instruction stream */
              syscall_num =
                  mem_rw((const void *)(ctx->epc & ~MICROMIPS_ISA_MODE))
                  & 0x3FF;
  #else
              syscall_num = (mem_rw((const void *)ctx->epc) >> 6) & 0xFFFF;
  #endif
  
  #ifdef DEBUG_VIA_UART
  #include <mips/uhi_syscalls.h>
              /*
               * intercept UHI write syscalls (printf) which would normally
               * get routed to debug probe or bootloader handler and output
               * via a UART
               */
  
              if (syscall_num == __MIPS_UHI_SYSCALL_NUM) {
                  if (ctx->t2[1] == __MIPS_UHI_WRITE &&
                      (ctx->a[0] == STDOUT_FILENO || ctx->a[0] == STDERR_FILENO)) {
                      uint32_t status = irq_arch_disable();
                      uart_write(DEBUG_VIA_UART, (uint8_t *)ctx->a[1], ctx->a[2]);
                      ctx->v[0] = ctx->a[2];
                      ctx->epc += 4; /* move PC past the syscall */
                      irq_arch_restore(status);
                      return;
                  }
                  else if (ctx->t2[1] == __MIPS_UHI_FSTAT &&
                           (ctx->a[0] == STDOUT_FILENO || ctx->a[0] == STDERR_FILENO)) {
                      /*
                       * Printf fstat's the stdout/stderr file so
                       * fill out a minimal struct stat.
                       */
                      struct stat *sbuf = (struct stat *)ctx->a[1];
                      memset(sbuf, 0, sizeof(struct stat));
                      sbuf->st_mode = S_IRUSR | S_IWUSR | S_IWGRP;
                      sbuf->st_blksize = BUFSIZ;
                      /* return 0 */
                      ctx->v[0] = 0;
                      ctx->epc += 4; /* move PC past the syscall */
                      return;
                  }
              }
              else
  #endif
              if (syscall_num == 2) {
                  unsigned int return_instruction = 0;
                  struct gpctx *new_ctx;
  #ifdef MIPS_DSP
                  struct dspctx *new_dspctx;
  #endif
                  /*
                   * Syscall 1 is reserved for UHI.
                   */
  
                  /*
                   * save the stack pointer in the thread info
                   * note we want the saved value to include the
                   * saved off context and the 16 bytes padding.
                   * Note we cannot use the current sp value as
                   * the prologue of this function has adjusted it
                   */
                  sched_active_thread->sp = (char *)(ctx->sp
                                                     - sizeof(struct gpctx) - PADDING);
  
  #ifdef MIPS_DSP
                  _dsp_save(&dsp_ctx);
                  _linkctx_append(ctx,&(dsp_ctx.link));
  #endif
  
  #ifdef MIPS_HARD_FLOAT
                  if(currentfpctx) {
                      _linkctx_append(ctx,&(currentfpctx->fp.link));
                  }
  #endif
  
                  sched_run();
  
                  new_ctx = (struct gpctx *)((unsigned int)sched_active_thread->sp + PADDING);
  
  #ifdef MIPS_HARD_FLOAT
                  currentfpctx = (struct fp64ctx *)exctx_find(LINKCTX_TYPE_FP64, new_ctx);
                  if(!currentfpctx) {
                      /* check for half-width FPU ctx in-case hardware doesn't support double. */
                      currentfpctx = (struct fp64ctx *)exctx_find(LINKCTX_TYPE_FP32, new_ctx);
                  }
  #endif
  
  #ifdef MIPS_DSP
                  new_dspctx = (struct dspctx *)exctx_find(LINKCTX_TYPE_DSP, new_ctx);
                  if (new_dspctx)
                      _dsp_load(new_dspctx);
  #endif
  
  #ifdef MIPS_MICROMIPS
                  return_instruction =
                      mem_rw((const void *)(new_ctx->epc & ~MICROMIPS_ISA_MODE));
                  if ((return_instruction & MM_SYSCALL_MASK) == MM_SYSCALL) { /* syscall */
                      new_ctx->epc += 4; /* move PC past the syscall */
                  }
  #else
                  return_instruction = mem_rw((const void *)new_ctx->epc);
                  if ((return_instruction & M32_SYSCALL_MASK) == M32_SYSCALL) { /* syscall */
                      new_ctx->epc += 4; /* move PC past the syscall */
                  }
  #endif
  
                  /*
                   * The toolchain Exception restore code just wholesale copies the
                   * status register from the context back to the register loosing
                   * any changes that may have occured, 'status' is really global state
                   * You dont enable interrupts on one thread and not another...
                   * So we just copy the current status value into the saved value
                   * so nothing changes on the restore
                   */
  
                  new_ctx->status = mips32_get_c0(C0_STATUS);
  
  #ifdef MIPS_HARD_FLOAT
                  /*
                   * Disable FPU so we get an exception on first use to allow
                   * Lazy FPU context save and restore
                   */
                  new_ctx->status &= ~SR_CU1;
  #endif
  
                  __asm volatile ("lw    $sp, 0(%0)" : : "r" (&sched_active_thread->sp));
  
                  /*
                   * Jump straight to the exception restore code
                   * if we return this functions prologue messes up
                   * the stack  pointer
                   */
                  __exception_restore();
  
                  UNREACHABLE();
              }
          break;
  #ifdef MIPS_HARD_FLOAT
          case EXC_CPU:
          {
              int newly_allocd  = false;
  
              mips_bissr(SR_CU1);
              ctx->status |= SR_CU1;
  
              if (!currentfpctx) {
                  currentfpctx = malloc(sizeof(struct fp64ctx));
                  assert(currentfpctx);
                  memset(currentfpctx,0,sizeof(struct fp64ctx));
                  currentfpctx->fp.link.id = LINKCTX_TYPE_FP64;
                  newly_allocd = true;
              }
  
              /* this means no one exec'd fpu since we last run */
              if (oldfpctx == currentfpctx) {
                  return;
              }
  
              if (oldfpctx) {
                  _fpctx_save(&oldfpctx->fp);
              }
  
              if (!newly_allocd) {
                  _fpctx_load(&currentfpctx->fp);
              }
  
              /*
               * next fpu exception must save our context as it's not necessarily
               * the next context switch will cause fpu exception and it's very
               * hard for any future task to determine which was the last one
               * that performed fpu operations. so by saving this pointer now we
               * give this knowledge to that future task
               */
              oldfpctx = currentfpctx;
  
          return;
          }
  #endif
  
              /* default: */
      }
      /* Pass all other exceptions through to the toolchain handler */
      __exception_handle(ctx, exception);
  }