summaryrefslogtreecommitdiff
path: root/sos-code-article6/hwcore/cpu_context.c
blob: 770452ddc33e06a4c0bf558592947dab288f309b (plain) (blame)
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
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/* Copyright (C) 2005  David Decotigny
   Copyright (C) 2000-2004, The KOS team

   This program 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 2
   of the License, or (at your option) any later version.
   
   This program 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, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
   USA. 
*/


#include <sos/assert.h>
#include <sos/klibc.h>
#include <drivers/bochs.h>
#include <drivers/x86_videomem.h>
#include <hwcore/segment.h>

#include "cpu_context.h"


/**
 * Here is the definition of a CPU context for IA32 processors. This
 * is a SOS convention, not a specification given by the IA32
 * spec. However there is a strong constraint related to the x86
 * interrupt handling specification: the top of the stack MUST be
 * compatible with the 'iret' instruction, ie there must be the
 * err_code (might be 0), eip, cs and eflags of the destination
 * context in that order (see Intel x86 specs vol 3, figure 5-4).
 *
 * @note IMPORTANT: This definition MUST be consistent with the way
 * the registers are stored on the stack in
 * irq_wrappers.S/exception_wrappers.S !!! Hence the constraint above.
 */
struct sos_cpu_state {
  /* (Lower addresses) */

  /* These are SOS convention */
  sos_ui16_t  gs;
  sos_ui16_t  fs;
  sos_ui16_t  es;
  sos_ui16_t  ds;
  sos_ui16_t  cpl0_ss; /* This is ALWAYS the Stack Segment of the
			  Kernel context (CPL0) of the interrupted
			  thread, even for a user thread */
  sos_ui16_t  alignment_padding; /* unused */
  sos_ui32_t  eax;
  sos_ui32_t  ebx;
  sos_ui32_t  ecx;
  sos_ui32_t  edx;
  sos_ui32_t  esi;
  sos_ui32_t  edi;
  sos_ui32_t  ebp;

  /* MUST NEVER CHANGE (dependent on the IA32 iret instruction) */
  sos_ui32_t  error_code;
  sos_vaddr_t eip;
  sos_ui32_t  cs; /* 32bits according to the specs ! However, the CS
		     register is really 16bits long */
  sos_ui32_t  eflags;

  /* (Higher addresses) */
} __attribute__((packed));


/**
 * The CS value pushed on the stack by the CPU upon interrupt, and
 * needed by the iret instruction, is 32bits long while the real CPU
 * CS register is 16bits only: this macro simply retrieves the CPU
 * "CS" register value from the CS value pushed on the stack by the
 * CPU upon interrupt.
 *
 * The remaining 16bits pushed by the CPU should be considered
 * "reserved" and architecture dependent. IMHO, the specs don't say
 * anything about them. Considering that some architectures generate
 * non-zero values for these 16bits (at least Cyrix), we'd better
 * ignore them.
 */
#define GET_CPU_CS_REGISTER_VALUE(pushed_ui32_cs_value) \
  ( (pushed_ui32_cs_value) & 0xffff )


/**
 * Structure of an interrupted Kernel thread's context
 */
struct sos_cpu_kstate
{
  struct sos_cpu_state regs;
} __attribute__((packed));


/**
 * THE main operation of a kernel thread. This routine calls the
 * kernel thread function start_func and calls exit_func when
 * start_func returns.
 */
static void core_routine (sos_cpu_kstate_function_arg1_t *start_func,
			  sos_ui32_t start_arg,
			  sos_cpu_kstate_function_arg1_t *exit_func,
			  sos_ui32_t exit_arg)
     __attribute__((noreturn));

static void core_routine (sos_cpu_kstate_function_arg1_t *start_func,
			  sos_ui32_t start_arg,
			  sos_cpu_kstate_function_arg1_t *exit_func,
			  sos_ui32_t exit_arg)
{
  start_func(start_arg);
  exit_func(exit_arg);

  SOS_ASSERT_FATAL(! "The exit function of the thread should NOT return !");
  for(;;);
}


sos_ret_t sos_cpu_kstate_init(struct sos_cpu_state **ctxt,
			      sos_cpu_kstate_function_arg1_t *start_func,
			      sos_ui32_t  start_arg,
			      sos_vaddr_t stack_bottom,
			      sos_size_t  stack_size,
			      sos_cpu_kstate_function_arg1_t *exit_func,
			      sos_ui32_t  exit_arg)
{
  /* We are initializing a Kernel thread's context */
  struct sos_cpu_kstate *kctxt;

  /* This is a critical internal function, so that it is assumed that
     the caller knows what he does: we legitimally assume that values
     for ctxt, start_func, stack_* and exit_func are allways VALID ! */

  /* Setup the stack.
   *
   * On x86, the stack goes downward. Each frame is configured this
   * way (higher addresses first):
   *
   *  - (optional unused space. As of gcc 3.3, this space is 24 bytes)
   *  - arg n
   *  - arg n-1
   *  - ...
   *  - arg 1
   *  - return instruction address: The address the function returns to
   *    once finished
   *  - local variables
   *
   * The remaining of the code should be read from the end upward to
   * understand how the processor will handle it.
   */

  sos_vaddr_t tmp_vaddr = stack_bottom + stack_size;
  sos_ui32_t *stack = (sos_ui32_t*)tmp_vaddr;

  /* If needed, poison the stack */
#ifdef SOS_CPU_STATE_DETECT_UNINIT_KERNEL_VARS
  memset((void*)stack_bottom, SOS_CPU_STATE_STACK_POISON, stack_size);
#elif defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW)
  sos_cpu_state_prepare_detect_kernel_stack_overflow(stack_bottom, stack_size);
#endif

  /* Simulate a call to the core_routine() function: prepare its
     arguments */
  *(--stack) = exit_arg;
  *(--stack) = (sos_ui32_t)exit_func;
  *(--stack) = start_arg;
  *(--stack) = (sos_ui32_t)start_func;
  *(--stack) = 0; /* Return address of core_routine => force page fault */

  /*
   * Setup the initial context structure, so that the CPU will execute
   * the function core_routine() once this new context has been
   * restored on CPU
   */

  /* Compute the base address of the structure, which must be located
     below the previous elements */
  tmp_vaddr  = ((sos_vaddr_t)stack) - sizeof(struct sos_cpu_kstate);
  kctxt = (struct sos_cpu_kstate*)tmp_vaddr;

  /* Initialize the CPU context structure */
  memset(kctxt, 0x0, sizeof(struct sos_cpu_kstate));

  /* Tell the CPU context structure that the first instruction to
     execute will be that of the core_routine() function */
  kctxt->regs.eip = (sos_ui32_t)core_routine;

  /* Setup the segment registers */
  kctxt->regs.cs
    = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KCODE); /* Code */
  kctxt->regs.ds
    = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Data */
  kctxt->regs.es
    = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Data */
  kctxt->regs.cpl0_ss
    = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Stack */
  /* fs and gs unused for the moment. */

  /* The newly created context is initially interruptible */
  kctxt->regs.eflags = (1 << 9); /* set IF bit */

  /* Finally, update the generic kernel/user thread context */
  *ctxt = (struct sos_cpu_state*) kctxt;

  return SOS_OK;
}


#if defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW)
void
sos_cpu_state_prepare_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt,
						   sos_vaddr_t stack_bottom,
						   sos_size_t stack_size)
{
  sos_size_t poison_size = SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW;
  if (poison_size > stack_size)
    poison_size = stack_size;

  memset((void*)stack_bottom, SOS_CPU_STATE_STACK_POISON, poison_size);
}


void
sos_cpu_state_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt,
					   sos_vaddr_t stack_bottom,
					   sos_size_t stack_size)
{
  unsigned char *c;
  int i;

  /* On SOS, "ctxt" corresponds to the address of the esp register of
     the saved context in Kernel mode (always, even for the interrupted
     context of a user thread). Here we make sure that this stack
     pointer is within the allowed stack area */
  SOS_ASSERT_FATAL(((sos_vaddr_t)ctxt) >= stack_bottom);
  SOS_ASSERT_FATAL(((sos_vaddr_t)ctxt) + sizeof(struct sos_cpu_kstate)
		   <= stack_bottom + stack_size);

  /* Check that the bottom of the stack has not been altered */
  for (c = (unsigned char*) stack_bottom, i = 0 ;
       (i < SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW) && (i < stack_size) ;
       c++, i++)
    {
      SOS_ASSERT_FATAL(SOS_CPU_STATE_STACK_POISON == *c);
    }
}
#endif


/* =======================================================================
 * Public Accessor functions
 */


sos_vaddr_t sos_cpu_context_get_PC(const struct sos_cpu_state *ctxt)
{
  SOS_ASSERT_FATAL(NULL != ctxt);

  /* This is the PC of the interrupted context (ie kernel or user
     context). */
  return ctxt->eip;
}


sos_vaddr_t sos_cpu_context_get_SP(const struct sos_cpu_state *ctxt)
{
  SOS_ASSERT_FATAL(NULL != ctxt);

  /* On SOS, "ctxt" corresponds to the address of the esp register of
     the saved context in Kernel mode (always, even for the interrupted
     context of a user thread). */
  return (sos_vaddr_t)ctxt;
}


void sos_cpu_context_dump(const struct sos_cpu_state *ctxt)
{
  char buf[128];
  snprintf(buf, sizeof(buf),
	   "CPU: eip=%x esp=%x eflags=%x cs=%x ds=%x ss=%x err=%x",
	   (unsigned)ctxt->eip, (unsigned)ctxt, (unsigned)ctxt->eflags,
	   (unsigned)GET_CPU_CS_REGISTER_VALUE(ctxt->cs), (unsigned)ctxt->ds,
	   (unsigned)ctxt->cpl0_ss,
	   (unsigned)ctxt->error_code);
  sos_bochs_putstring(buf); sos_bochs_putstring("\n");
  sos_x86_videomem_putstring(23, 0,
			  SOS_X86_VIDEO_FG_BLACK | SOS_X86_VIDEO_BG_LTGRAY,
			  buf);
}


/* =======================================================================
 * Public Accessor functions TO BE USED ONLY BY Exception handlers
 */


sos_ui32_t sos_cpu_context_get_EX_info(const struct sos_cpu_state *ctxt)
{
  SOS_ASSERT_FATAL(NULL != ctxt);
  return ctxt->error_code;
}


sos_vaddr_t
sos_cpu_context_get_EX_faulting_vaddr(const struct sos_cpu_state *ctxt)
{
  sos_ui32_t cr2;

  /*
   * See Intel Vol 3 (section 5.14): the address of the faulting
   * virtual address of a page fault is stored in the cr2
   * register.
   *
   * Actually, we do not store the cr2 register in a saved
   * kernel thread's context. So we retrieve the cr2's value directly
   * from the processor. The value we retrieve in an exception handler
   * is actually the correct one because an exception is synchronous
   * with the code causing the fault, and cannot be interrupted since
   * the IDT entries in SOS are "interrupt gates" (ie IRQ are
   * disabled).
   */
  asm volatile ("movl %%cr2, %0"
		:"=r"(cr2)
		: );

  return cr2;
}


/* =======================================================================
 * Backtrace facility. To be used for DEBUGging purpose ONLY.
 */


sos_ui32_t sos_backtrace(const struct sos_cpu_state *cpu_state,
			 sos_ui32_t max_depth,
			 sos_vaddr_t stack_bottom,
			 sos_size_t stack_size,
			 sos_backtrace_callback_t * backtracer,
			 void *custom_arg)
{
  int depth;
  sos_vaddr_t callee_PC, caller_frame;

  /*
   * Layout of a frame on the x86 (compiler=gcc):
   *
   * funcA calls funcB calls funcC
   *
   *         ....
   *         funcB Argument 2
   *         funcB Argument 1
   *         funcA Return eip
   * frameB: funcA ebp (ie previous stack frame)
   *         ....
   *         (funcB local variables)
   *         ....
   *         funcC Argument 2
   *         funcC Argument 1
   *         funcB Return eip
   * frameC: funcB ebp (ie previous stack frame == A0) <---- a frame address
   *         ....
   *         (funcC local variables)
   *         ....
   *
   * The presence of "ebp" on the stack depends on 2 things:
   *   + the compiler is gcc
   *   + the source is compiled WITHOUT the -fomit-frame-pointer option
   * In the absence of "ebp", chances are high that the value pushed
   * at that address is outside the stack boundaries, meaning that the
   * function will return -SOS_ENOSUP.
   */

  if (cpu_state)
    {
      callee_PC    = cpu_state->eip;
      caller_frame = cpu_state->ebp;
    }
  else
    {
      /* Skip the sos_backtrace() frame */
      callee_PC    = (sos_vaddr_t)__builtin_return_address(0);
      caller_frame = (sos_vaddr_t)__builtin_frame_address(1);
    }

  for(depth=0 ; depth < max_depth ; depth ++)
    {
      /* Call the callback */
      backtracer(callee_PC, caller_frame + 8, depth, custom_arg);

      /* If the frame address is funky, don't go further */
      if ( (caller_frame < stack_bottom)
	   || (caller_frame + 4 >= stack_bottom + stack_size) )
	return depth;

      /* Go to caller frame */
      callee_PC    = *((sos_vaddr_t*) (caller_frame + 4));
      caller_frame = *((sos_vaddr_t*) caller_frame);
    }
  
  return depth;
}