Chapter 15
Kernel Support Library: liboskit_kern.a

15.1 Introduction

The kernel support library, libkern.a, supplies a variety of functions and other definitions that are primarily of use in OS kernels. (In contrast, the other parts of the OSKit are more generic components useful in a variety of environments including, but not limited to, OS kernels.) The kernel support library contains all the code necessary to create a minimal working “kernel” that boots and sets up the machine for a generic “OS-friendly” environment. For example, on the x86, the kernel support library provides code to get into protected mode, set up default descriptor tables, etc. The library also includes a remote debugging stub, providing convenient source-level debugging of the kernel over a serial line using GDB’s serial-line remote debugging protocol. As always, all components of this library are optional and replaceable, so although some pieces may be unusable in some environments, others should still work fine.

15.1.1 Machine-dependence of code and interfaces

This library contains a much higher percentage of machine-dependent code than the other libraries in the toolkit, primarily because this library deals with heavily machine-dependent facilities such as page tables, interrupt vector tables, trap handling, etc. The library attempts to hide some machine-dependent details from the OS by providing generic, machine-independent interfaces to machine-dependent library code. For example, regardless of the architecture and boot loading mechanism in use, the kernel startup code included in the library always sets up a generic C-compatible execution environment and starts the kernel by calling the well-known main routine, just as in ordinary C programs. However, the library makes no attempt to provide a complete architecture-independence layer, since such a layer would have to make too many assumptions about the OS that is using it. For example, although the library provides page table management routines, these routines have fairly low-level, architecture-specific interfaces.

15.1.2 Generic versus Base Environment code

The functionality provided by the kernel support library is divided into two main classes: the generic support code, and the base environment. The generic support contains simple routines and definitions that are almost completely independent of the particular OS environment in which they are used: for example, the generic support includes symbolic definitions for bits in processor registers and page tables, C wrapper functions to access special-purpose processor registers, etc. The generic support code should be usable in any OS that needs it.

The base environment code, on the other hand, is somewhat less generic in that it is designed to create, and function in, a well-defined default or “base” kernel execution environment. Out of necessity, this code makes more assumptions about how it is used, and therefore it is more likely that parts of it will not be usable to a particular client OS. For example, on the x86 architecture, the base environment code sets up a default global descriptor table containing a “standard” set of basic, flat-model segment descriptors, as well as a few extra slots reserved for use by the client OS. This “base GDT” is likely to be sufficient for many kernels, but may not be usable to kernels that make more exotic uses of the processor’s GDT. In order to allow piecemeal replacement of the base environment as necessary, the assumptions made by the code and the intermodule dependencies are clearly documented in the sections covering the base environment code.

15.1.3 Road Map

Following is a brief summary of the main facilities provided by the library, indexed by the section numbers of the sections describing each facility:

15.2 Machine-independent Facilities

This section includes machine-independent types, constants, macros, and functions that every supported architecture provides. These are used extensively within the OSKit itself as well as by the applications built on the OSKit.

15.2.1 page.h: Page size definitions

SYNOPSIS

#include <oskit/machine/page.h>

DESCRIPTION

This file provides of following symbols, which define the architectural page size of the architecture for which the OSKit is configured:

PAGE_SIZE:
The number of bytes on each page. It can always be assumed to be a power of two.
PAGE_SHIFT:
The number of low address bits not translated by the MMU hardware. PAGE_SIZE is always 2PAGESHIFT.
PAGE_MASK:
A bit mask with the low-order PAGE_SHIFT address bits set. Always equal to PAGESIZE - 1. WARNING: Some systems (like linux) define this to be  ~ (PAGESIZE - 1), be careful that the definitions match what the code expects!

In addition, the following macros are provided for convenience in performing page-related manipulations of addresses:

atop(addr ):
Converts a byte address into a page frame number, by dividing by PAGE_SIZE.
ptoa(page ):
Converts a page frame number into an integer (oskit_addr_t) byte address, by multiplying by PAGE_SIZE.
round_page(addr ):
Returns addr rounded up to the next higher page boundary. If addr is already on a page boundary, it is returned unchanged.
trunc_page(addr ):
Returns addr rounded down to the next lower page boundary. If addr is already on a page boundary, it is returned unchanged.
page_aligned(addr ):
Evaluates to true (nonzero) if addr is page aligned, or false (zero) if it isn’t.

Note that many modern architectures support multiple page sizes. On such architectures, the page size defined in this file is the minimum architectural page size, i.e., the finest granularity over which the MMU has control. Since there seems to be no sufficiently generic and useful way that this header file could provide symbols indicating which “other” page sizes the architecture supports, making good use of larger pages probably must be done in machine-dependent code.

Some operating systems on some architectures do not actually support the minimum architectural page size in software; instead, they aggregate multiple architectural pages together into larger “logical pages” managed by the OS software. On such operating systems, it would be inappropriate for general OS or application code to use the PAGE_SIZE value provided by oskit/page.h, since this value would be smaller (more fine-grained) than the OS software actually supports, and therefore inappropriate. However, this is purely a high-level OS issue; like other parts of the toolkit, no one is required to use this header file if it is inappropriate in a particular situation.

This file was originally derived from Mach’s vm_param.h.

15.2.2 spin_lock.h: Spin locks

SYNOPSIS

#include <oskit/machine/spin_lock.h>

DESCRIPTION

This file provides the architecture-dependent definition of the spin_lock_t “spin lock” data type and associated manipulation macros. This facility provides a basic locking mechanism which can be used with preemptive threading or on a multi-processor.

spin_lock_t:
Typedef for the spin lock data type.
spin_lock_init(s ):
Initialize a spin lock.
spin_lock_locked(s ):
Check if a spin lock is locked.
spin_unlock(s ):
Unlock a spin lock.
spin_try_lock(s ):
Attempt to lock a spin lock. Returns 0 if successful, nonzero if unsuccessful.
spin_lock(s ):
Busy wait until the lock is free. On return the lock has been acquired.

This header file is taken from CMU’s Mach kernel.

15.2.3 queue.h: Generic queues

SYNOPSIS

#include <oskit/queue.h>

struct queue_entry {

  struct queue_entry *next; /* next element */
  struct queue_entry *prev; /* previous element */

};

typedef struct queue_entry *queue_t;
typedef struct queue_entry queue_head_t;
typedef struct queue_entry queue_chain_t;
typedef struct queue_entry *queue_entry_t;

DESCRIPTION

Macros and structures for implementation of a “queue” data type. The implementation uses a doubly-linked list and supports operations to insert and delete anywhere in the list.

queue_init(q ):
Initialize the given queue.
queue_first(q ):
Returns the first entry in the queue.
queue_next(q ):
Returns the entry after an item in the queue.
queue_last(q ):
Returns the last entry in the queue.
queue_prev(q ):
Returns the entry before an item in the queue.
queue_end(q, qe ):
Tests whether a new entry is really the end of the queue.
queue_empty(q ):
Tests whether a queue is empty.
queue_enter(q, elt, type, field ):
Insert a new element at the tail of the queue.
queue_enter_first(head, elt, type, field ):
Insert a new element at the head of the queue.
queue_enter_before(head, nelt, elt, type, field ):
Insert a new element before the indicated element.
queue_enter_after(head, pelt, elt, type, field ):
Insert a new element after the indicated element.
queue_remove(head, elt, type, field ):
Remove an arbitrary item from the queue.
queue_remove_first(head, entry, type, field ):
Remove and return the entry at the head of the queue.
queue_remove_last(head, entry, type, field ):
Remove and return the entry at the tail of the queue.
queue_assign(to, from, type, field ):
Move an element in a queue to a new piece of memory.
queue_iterate(head, elt, type, field ):
Iterate over each item in the queue. Generates a ‘for’ loop, setting elt to each item in turn (by reference).

This header file is taken from CMU’s Mach kernel.

15.2.4 debug.h: debugging support facilities

SYNOPSIS

#include <oskit/debug.h>

DESCRIPTION

This file contains simple macros and functions to assist in debugging. Many of these facilities are intended to be used to “annotate” programs permanently or semi-permanently in ways that reflect the code’s proper or desired behavior. These facilities typically change their behavior depending on whether the preprocessor symbol DEBUG is defined: if it is defined, then extra code is introduced to check invariants and such; when DEBUG is not defined, all of this debugging code is “compiled out” so that it does not result in any size increase or efficiency loss in the resulting compiled code.

The following macros and functions are intended to be used as permanent- or semi-permanent annotations to be sprinkled throughout ordinary code to increase its robustness and clarify its invariants and assumptions to human readers:

assert(cond ):
This is a standard assert macro, like (and compatible with) the one provided in oskit/c/assert.h. If DEBUG is defined, this macro produces code that evaluates cond and calls panic (see Section 14.8.3) if the result is false (zero). When an assertion fails and causes a panic, the resulting message includes the source file name and line number of the assertion that failed, as well as the text of the cond expression used in the assertion. If DEBUG is not defined, this macro evaluates to nothing (an empty statement), generating no code.

Assertions are typically used to codify assumptions made by a code sequence, e.g., about the parameters to a function or the conditions on entry to or exit from a loop. By placing explicit assert statements in well-chosen locations to verify that the code’s invariants indeed hold, a thicker “safety net” is woven into the code, which tends to make bugs manifest themselves earlier and in much more obvious ways, rather than allowing incorrect results to “trickle” through the program’s execution for a long time, sometimes resulting in completely baffling behavior. Assertions can also act as a form of documentation, clearly describing to human readers the exact requirements and assumptions in a piece of code.

otsan():
If DEBUG is defined, this macro unconditionally causes a panic with the message “off the straight and narrow!,” along with the source file name and line number, if it is ever executed. It is intended to be placed at code locations that should never be reached if the code is functioning properly; e.g., as the default case of a switch statement for which the result of the conditional expression should always match one of the explicit case values. If DEBUG is not defined, this macro evaluates to nothing.
do_debug(stmt ):
If DEBUG is defined, this macro evaluates to stmt; otherwise it evaluates to nothing. This macro is useful in situations where an #ifdef DEBUG ... #endif block would otherwise be used over just a few lines of code or a single statement: it produces the same effect, but is smaller and less visually intrusive.

The following macros and functions are primarily intended to be used as temporary scaffolding during debugging, and removed from production code:

void dump_stack_trace(void):
This function dumps a human-readable backtrace of the current function call stack to the console, using printf. The exact content and format of the printed data is architecture-specific; however, the output is typically a list of instruction pointer or program counter values, each pointing into a function on the call stack, presumably to the return point after the function call to the next level. You can find out what function these addresses reside in by running the Unix nm utility on the appropriate executable file image, sorting the resulting symbol list if necessary, and looking up the address in the sorted list. Alternatively, for more precise details, you can look up the exact instruction addresses in a disassembly of the executable file, e.g., by using GNU objdump with the ‘-d’ option.
here():
This macro generates code that simply prints the source file name and line number at which the macro was used. This macro can be extremely useful when trying to nail down the precise time or code location at which a particular bug manifests itself, or to determine the sequence of events leading up to it. By sprinkling around calls to the here macro in appropriate places, the program will dump regular status reports of its location every time it hits one of these macros, effectively producing a log of “interesting” events (“interesting” being defined according to the placement of the here macro invocations). Using the here macro this way is equivalent to the common practice of sprinkling printf’s around and watching the output, except it is easier because the here invocation in each place does not have to be “tailored” to make it distinguishable from the other locations: each use of the here macro is self-identifying.

If DEBUG is not defined, the here macro is not defined at all; this makes it obvious when you’ve accidentally left invocations of this macro in a piece of code after it has been debugged.

debugmsg(printfargs ):
This macro is similar to here, except it allows a formatted message to be printed along with the source file name and line number. printfargs is a complete set of arguments to be passed to the printf function, including parentheses: for example, ‘debugmsg(("foo is %d", foo));’. A newline is automatically appended to the end of the message. This macro is generally useful as a wrapper for printf for printing temporary run-time status messages during execution of a program being debugged.

As with here, if DEBUG is not defined, the debugmsg macro is not defined at all, in order to make it obvious if any invocations are accidentally left in production code.

Note that only panic and dump_stack_trace are real functions; the others are simply macros.

15.2.5 base_critical: simple critical section support

SYNOPSIS

#include <oskit/base_critical.h>

void base_critical_enter(void);

void base_critical_leave(void);

DESCRIPTION

Functions to implements a simple “global critical region.” These functions are used throughout the OSKit to ensure proper serialization for various “touchy” but non-performance critical activities such as panicing, rebooting, debugging, etc. This critical region can safely be entered recursively; the only requirement is that enters match exactly with leaves.

The implementation of this module is machine-dependent, and generally disables interrupts and, on multiprocessors, grabs a recursive spin lock.

15.3 (X86) Generic Low-level Definitions

This section covers useful macros, definitions, and routines that are specific to the Intel x86 processor architecture but that are independent of the interrupt control, bus structure, and other ancillary functions traditionally associated with a “PC.” Those facilities are covered in section 15.4.

15.3.1 asm.h: assembly language support macros

SYNOPSIS

#include <oskit/x86/asm.h>

DESCRIPTION

This file contains convenience macros useful when writing x86 assembly language code in AT&T/GAS syntax. This header file is directly derived from Mach, and similar headers are used in various BSD kernels.

Symbol name extension: The following macros allow assembly language code to be written that coexists with C code compiled for either ELF or a.out format. In a.out format, by convention an underscore (_) is prefixed to each public symbol referenced or defined by the C compiler; however, the underscore prefix is not used in ELF format.

EXT(name ):
Evaluates to _name in a.out format, or just name in ELF. This macro is typically used when referring to public symbols defined in C code.
LEXT(name ):
Evaluates to _name : in a.out format, or name : in ELF. This macro is generally used when defining labels to be exported to C code.
SEXT(name ):
Evaluates to the string literal "_name " in a.out format, or "name " in ELF. This macro can be used in GCC inline assembly code, where the code is contained in a string constant; for example: asm("...; call "SEXT(foo)"; ...");

Alignment: The following macros relate to alignment of code and data:

TEXT_ALIGN:
Evaluates to the preferred alignment of instruction entrypoints (e.g., functions or branch targets), as a power of two. Currently evaluates to 4 (16-byte alignment) if the symbol i486 is defined, or 2 (4-byte alignment) otherwise.
ALIGN:
A synonym for TEXT_ALIGN.
DATA_ALIGN:
Evaluates to the preferred minimum alignment of data structures. Currently it is always defined as 2, although in some cases a larger value may be preferable, such as the processor’s cache line size.
P2ALIGN(alignment ):
Assembly language code can use this macro to work around the fact that the .align directive works differently in different x86 environments: sometimes .align takes a byte count, whereas other times it takes a power of two (bit count). The P2ALIGN macro always takes a power of two: for example, P2ALIGN(2) means 4-byte alignment. By default, the P2ALIGN macro uses the .p2align directive supported by GAS; if a different assembler is being used, then P2ALIGN should be redefined as either .align alignment or .align 1<<(alignment ), depending on the assembler’s interpretation of .align.

XXX S_ARG, B_ARG, frame stuff, . . .

XXX need to make the macros more easily overridable, using ifdefs.

XXX need to clean out old trash still in the header file

XXX IODELAY macro

15.3.2 eflags.h: Processor flags register definitions

SYNOPSIS

#include <oskit/x86/eflags.h>

DESCRIPTION

XXX

This header file can be used in assembly language code as well as C. The flags defined here correspond the the ones in the processor databooks.

EFL_CF:
carry
EFL_PF:
parity of low 8 bits
EFL_AF:
carry out of bit 3
EFL_ZF:
zero
EFL_SF:
sign
EFL_TF:
trace trap
EFL_IF:
interrupt enable
EFL_DF:
direction
EFL_OF:
overflow
EFL_IOPL:
IO privilege level mask. All 0’s is the same as EFL_IOPL_KERNEL, while all 1’s (or just EFL_IOPL) is the same as EFL_IOPL_USER.
EFL_NT:
nested task
EFL_RF:
resume without tracing
EFL_VM:
virtual 8086 mode
EFL_AC:
alignment check
EFL_VIF:
virtual interrupt flag
EFL_VIP:
virtual interrupt pending
EFL_ID:
CPUID instruction support

15.3.3 proc_reg.h: Processor register definitions and accessor functions

SYNOPSIS

#include <oskit/x86/proc_reg.h>

DESCRIPTION

XXX

This header file contains the definitions for the processor’s control registers (CR0, CR4). It also contains macros for getting and setting the processor registers and flags. There is also a macro for reading the processor’s cycle counter (on Pentium and above processors).

This header file is taken from CMU’s Mach kernel.

15.3.4 debug_reg.h: Debug register definitions and accessor functions

SYNOPSIS

#include <oskit/x86/debug_reg.h>

DESCRIPTION

This provides the definitions for the processor’s built-in debug registers. There are also inline functions that allow the hardware-assisted breakpoints to be set.

DR0 through DR3 are the breakpoint address registers; DR6 is the status register, and DR7 is the control register.

get_dr0():
Returns the value in breakpoint address register 0.
get_dr1():
Returns the value in breakpoint address register 1.
get_dr2():
Returns the value in breakpoint address register 2.
get_dr3():
Returns the value in breakpoint address register 3.
get_dr6():
Returns the value in the debug status register.
get_dr7():
Returns the value in the debug control register.
set_dr0(val ):
Sets the value in breakpoint address register 0 to val.
set_dr1(val ):
Sets the value in breakpoint address register 1 to val.
set_dr2(val ):
Sets the value in breakpoint address register 2 to val.
set_dr3(val ):
Sets the value in breakpoint address register 3 to val.
set_dr6(val ):
Sets the value in the debug status register to val.
set_dr7(val ):
Sets the value in the debug control register to val.
set_b0(unsigned addr, unsigned len, unsigned rw ):
Enables breakpoint register 0. Sets dr0 to LINEAR address addr and updates dr7 to enable it. rw must be DR7_RW_INST, DR7_RW_WRITE, DR7_RW_IO, or DR7_RW_DATA indicating the condition to break on. len must be DR7_LEN_1, DR7_LEN_2, or DR7_LEN_4, indicating how many bytes are covered by the register.
set_b1(unsigned addr, unsigned len, unsigned rw ):
Enables breakpoint register 1.
set_b2(unsigned addr, unsigned len, unsigned rw ):
Enables breakpoint register 2.
set_b3(unsigned addr, unsigned len, unsigned rw ):
Enables breakpoint register 3.

15.3.5 fp_reg.h: Floating point register definitions and accessor functions

SYNOPSIS

#include <oskit/x86/fp_reg.h>

DESCRIPTION

XXX

This file contains the structure definition for saving the floating-point state and then restoring it. It also contains definitions for the x87 control and status registers.

This header file is taken from CMU’s Mach kernel.

15.3.6 far_ptr.h: Far (segment:offset) pointers

SYNOPSIS

#include <oskit/x86/far_ptr.h>

DESCRIPTION

This contains struct definitions for creating “far pointers.” Far pointers on the x86 are those that take an explicit segment in addition to the offset value.

struct far_pointer_16:
16-bit pointer structure which contains a 16-bit segment and a 16-bit offset. The address is computed as segment ¡¡ 4 + offset.
struct far_pointer_32:
48-bit pointer which contains a 32-bit offset and a 16-bit segment descriptor. Segmentation is used to determine what linear address is generated by these pointers.

15.3.7 pio.h: Programmed I/O functions

SYNOPSIS

#include <oskit/x86/pio.h>

DESCRIPTION

These are macros for accessing IO-space directly on the x86. These instructions will generate traps if executed in user-mode without permissions (either IOPL in the eflags register or access via the io-bitmap in the tss).

iodelay():
A macro used to delay the processor for a short period of time, generally to wait until programmed io can complete. The actual amount of time is indeterminate, since the delay is accomplished by doing an inb from a nonexistent port, which depends on the processor and chipset.1 The nominal delay value is 1uS for most machines.
inl(port ):
Returns 32-bit value from port
inw(port ):
Returns 16-bit value from port
inb(port ):
Returns 8-bit value from port
inl_p(port ):
inl followed immediately by iodelay
inw_p(port ):
inw followed immediately by iodelay
inb_p(port ):
inb followed immediately by iodelay
outl(port, val ):
Send 32-bit val out port.
outw(port, val ):
Send 16-bit val out port.
outb(port, val ):
Send 8-bit val out port.
outl_p(port ):
outl followed immediately by iodelay
outw_p(port ):
outw followed immediately by iodelay
outb_p(port ):
outb followed immediately by iodelay

The above macros have versions that begin with i16_, which are defined to be the same. It may be desirable to use the i16_ versions in 16-bit code in place of the normal macros for clarity.

This header file is taken from CMU’s Mach kernel.

15.3.8 seg.h: Segment descriptor data structure definitions and constants

SYNOPSIS

#include <oskit/x86/seg.h>

DESCRIPTION

XXX

struct x86_desc:
Normal segment descriptors.
struct x86_gate:
Trap, interrupt, and call gates.
struct pseudo_descriptor:
Used to load the IDT and GDT (and LDT).
sel_idx(sel):
Converts the selector into an index in the descriptor table.
ISPL(s):
Returns the selector’s privilege level.
USERMODE(s, f):
KERNELMODE(s, f):
fill_descriptor(struct x86_desc *desc, unsigned base, unsigned limit, unsigned char access, unsigned char sizebits):
Fill a segment descriptor.
fill_descriptor_base(struct x86_desc *desc, unsigned base):
Set the base address in a segment descriptor.
fill_descriptor_limit(struct x86_desc *desc, unsigned limit):
Set the limit in a segment descriptor.
fill_gate(struct x86_gate *gate, unsigned offset, unsigned short selector, unsigned char access, unsigned char word_count):
Fill an x86 gate descriptor.

This header file is based on a file in CMU’s Mach kernel.

15.3.9 gate_init.h: Gate descriptor initialization support

SYNOPSIS

#include <oskit/x86/gate_init.h>

DESCRIPTION

This file contains the C structures and assembly-language macro definitions used to build x86 gate descriptor tables suitable for use by gate_init (see Section 15.5.10).

struct gate_init_entry:
C structure describing a gate descriptor.
GATE_INITTAB_BEGIN(name):
Starts assembly-language definition of a gate descriptor table.
GATE_ENTRY(n, entry, type):
Initializes an element of a gate descriptor table.
GATE_INITTAB_END:
Defines the end of a gate descriptor table.

The assembly-language macros are designed to be used while writing trap entrypoint routines. See oskit/libkern/x86/base_trap_inittab.S for example code that uses this facility.

15.3.10 trap.h: Processor trap vectors

SYNOPSIS

#include <oskit/x86/trap.h>

DESCRIPTION

XXX

This contains the definitions for the trap numbers returned by the processor when something goes ‘wrong’. These can be used to determine the cause of the trap.

T_DIVIDE_ERROR:
T_DEBUG:
T_NMI:
non-maskable interrupt
T_INT3:
T_OVERFLOW:
overflow test
T_OUT_OF_BOUNDS:
bounds check
T_INVALID_OPCODE:
T_NO_FPU:
T_DOUBLE_FAULT:
T_FPU_FAULT:
T_INVALID_TSS:
T_SEGMENT_NOT_PRESENT:
T_STACK_FAULT:
T_GENERAL_PROTECTION:
T_PAGE_FAULT:
T_PF_PROT: protection violation; T_PF_WRITE: write access; T_PF_USER: from user state
T_FLOATING_POINT_ERROR:
T_ALIGNMENT_CHECK:
T_MACHINE_CHECK:

This header file is taken from CMU’s Mach kernel.

15.3.11 paging.h: Page translation data structures and constants

DESCRIPTION

XXX

This header file is derived from Mach’s intel/pmap.h.

15.3.12 tss.h: Processor task save state structure definition

SYNOPSIS

#include <oskit/x86/tss.h>

         struct x86_tss {
                 int     back_link;      /* previous task's segment, if nested */
                 int     esp0;           /* initial stack pointer ... */
                 int     ss0;            /* and segment for ring 0 */
                 int     esp1;           /* initial stack pointer ... */
                 int     ss1;            /* and segment for ring 1 */
                 int     esp2;           /* initial stack pointer ... */
                 int     ss2;            /* and segment for ring 2 */
                 int     cr3;            /* CR3 - page table physical address */
                 int     eip;            /* eip */
                 int     eflags;         /* eflags */
                 int     eax;            /* eax */
                 int     ecx;            /* ecx */
                 int     edx;            /* edx */
                 int     ebx;            /* ebx */
                 int     esp;            /* current stack pointer (ring 3) */
                 int     ebp;            /* ebp */
                 int     esi;            /* esi */
                 int     edi;            /* edi */
                 int     es;             /* es */
                 int     cs;             /* cs */
                 int     ss;             /* current stack segment (ring 3) */
                 int     ds;             /* ds */
                 int     fs;             /* fs */
                 int     gs;             /* gs */
                 int     ldt;            /* local descriptor table segment */
                 unsigned short  trace_trap;     /* trap on switch to task */
                 unsigned short  io_bit_map_offset; /* offset to IO perm bitmap */
         };
         
DESCRIPTION

XXX

XXX only the 32-bit version

This contains the definition of a 32-bit tss. The tss used by the 80286 is incompatible with this.

This header file is taken from CMU’s Mach kernel.

15.4 (X86 PC) Generic Low-level Definitions

XXX

This section covers useful macros, definitions, and routines for “PC”-specific features.

15.4.1 irq_list.h: Standard hardware interrupt assignments

SYNOPSIS

#include <oskit/x86/pc/irq_list.h>

DESCRIPTION

XXX

Many of the interrupt vectors have pre-defined uses. The rest of them can be assigned to ISA, PCI, or other devices. This file contains the ‘defined’ interrupt usages.

15.4.2 pic.h: Programmable Interrupt Controller definitions

SYNOPSIS

#include <oskit/x86/pc/pic.h>

DESCRIPTION

XXX

This contains definitions for the 8259(A) Programmable Interrupt Controller (PIC). In addition to numerous constants, it also contains the prototypes for several functions and macros.

pic_init(unsigned char master_base, unsigned char slave_base):
MASTER_PIC_BASE and SLAVES_PIC_BASE are also defined, and may be passed in as parameters.
pic_disable_irq(unsigned char irq):
pic_enable_irq(unsigned char irq):
pic_test_irq(unsigned char irq):
pic_enable_all:
pic_disable_all:
pic_ack(irq):

15.4.3 keyboard.h: PC keyboard definitions

SYNOPSIS

#include <oskit/x86/pc/keyboard.h>

DESCRIPTION

XXX

This header file contains the register definitions for the PC keyboard. The port addresses are defined, along with the status and control bits. This would be used by a keyboard device driver, or someone manipulating they keyboard directly. (ie, to turn on and off the keyboard LEDs).

This header file is taken from CMU’s Mach kernel.

15.4.4 rtc.h: NVRAM Register locations

SYNOPSIS

#include <oskit/x86/pc/rtc.h>

DESCRIPTION

This file is taken from FreeBSD (XXX cite?) and contains definitions for the standard NVRAM, or Real Time Clock, register locations.

rtcin(unsigned char addr):
Returns the 8-bit value from location addr.
rtcout(unsigned char addr, unsigned char val):
Writes val to location addr.

15.5 (X86) Processor Identification and Management

15.5.1 cpu_info: CPU identification data structure

SYNOPSIS

#include <oskit/x86/cpuid.h>

struct cpu_info {

  unsigned stepping : 4; /* Stepping ID */
  unsigned model : 4; /* Model */
  unsigned family : 4; /* Family */
  unsigned type : 2; /* Processor type */
  unsigned feature_flags; /* Features supported */
  char vendor_id[12]; /* Vendor ID string */
  unsigned char cache_config[16]; /* Cache information */

};
DESCRIPTION

This structure is used to hold identification information about x86 processors, such as information returned by the CPUID instruction. The cpuid toolkit function, described below, fills in an instance of this structure with information about the current processor.

Note that it is expected that the cpu_info structure will continue to grow in the future as new x86-architecture processors are released, so client code should not depend on this structure in ways that will break if the structure’s size changes.

The family field describes the processor family:

CPU_FAMILY_386:
A 386-class processor.
CPU_FAMILY_486:
A 486-class processor.
CPU_FAMILY_PENTIUM:
A Pentium-class (“586”) processor.
CPU_FAMILY_PENTIUM_PRO:
A Pentium Pro-class (“686”) processor.

The type field is one of the following:

CPU_TYPE_ORIGINAL:
Original OEM processor.
CPU_TYPE_OVERDRIVE:
OverDrive upgrade processor.
CPU_TYPE_DUAL:
Dual processor.

The feature_flags field is a bit field containing the following bits:

CPUF_ON_CHIP_FPU:
Set if the CPU has a built-in floating point unit.
CPUF_VM86_EXT:
Set if the virtual 8086 mode extensions are supported, i.e., the VIF and VIP flags register bits, and the VME and PVI bits in CR4.
CPUF_IO_BKPTS:
Set if I/O breakpoints are supported, i.e., the DR7_RW_IO mode defined in x86/debug_reg.h.
CPUF_4MB_PAGES:
Set if 4MB superpages are supported, i.e., the INTEL_PDE_SUPERPAGE page directory entry bit defined in x86/paging.h.
CPUF_TS_COUNTER:
Set if the on-chip timestamp counter and the RDTSC instruction are available.
CPUF_PENTIUM_MSR:
Set if the Pentium model specific registers are available.
CPUF_PAGE_ADDR_EXT:
Set if the Pentium Pro’s page addressing extensions (36-bit physical addresses and 2MB pages) are available.
CPUF_MACHINE_CHECK_EXCP:
Set if the processor supports the Machine Check exception (vector 18, or T_MACHINE_CHECK in x86/trap.h).
CPUF_CMPXCHG8B:
Set if the processor supports the CMPXCHG8B instruction (also known as “double-compare-and-swap”).
CPUF_LOCAL_APIC:
Set if the processor has a built-in local APIC (Advanced Programmable Interrupt Controller), for symmetric multiprocessor support.
CPUF_MEM_RANGE_REGS:
Set if the processor supports the memory type range registers.
CPUF_PAGE_GLOBAL_EXT:
Set if the processor supports the global global paging extensions, i.e., the INTEL_PDE_GLOBAL page table entry bit defined in x86/paging.h.
CPUF_MACHINE_CHECK_ARCH:
Set if the processor supports Intel’s machine check architecture and the MCG_CAP model-specific register.
CPUF_CMOVCC:
Set if the processor supports the CMOVcc instructions.

The cpuid.h header file also contains symbolic definitions for other constants such as the cache configuration descriptor values; see the header file and the Intel documentation for details on these.

15.5.2 cpuid: identify the current CPU

SYNOPSIS

#include <oskit/x86/cpuid.h>

void cpuid([out] struct cpu_info *out_info);

DESCRIPTION

This function identifies the CPU on which it is running using Intel’s recommended CPU identification procedure, and fills in the supplied structure with the information found.

Note that since the cpuid function is 32-bit code, it wouldn’t run on anything less than an 80386 in the first place; therefore it doesn’t bother to check for earlier processors.

PARAMETERS
out_info:
The CPU information structure to fill in.

15.5.3 cpu_info_format: output a cpu_info structure in ASCII form

SYNOPSIS

#include <oskit/x86/cpuid.h>

void cpu_info_format(struct cpu_info *info, void (*formatter)(void *data, const char *fmt, ...), void *data);

DESCRIPTION

This function takes the information in a cpu_info structure and formats it as human-readable text. The formatter should be a pointer to a printf-like function to be called to format the output data. The formatter function may be called multiple times to output all the relevant information.

PARAMETERS
info:
The filled-in CPU information structure to output.
formatter:
The printf-style formatted output function to call. It will be called with the opaque data pointer provided, a standard C format string (fmt), and optionally a set of data items to format.
data:
An opaque pointer which is simply passed on to the formatter function.

15.5.4 cpu_info_min: return the minimum feature set of two CPU information structures

SYNOPSIS

#include <oskit/x86/cpuid.h>

void cpu_info_min(struct cpu_info *id1, struct cpu_info *id2);

DESCRIPTION

Determine the minimum (least-common-denominator) feature set of the two provided structures and return that. The new feature set is returned in id1.

Typically used on SMP systems to determine the basic feature set common across all processors in the system regardless of type.

PARAMETERS
id1, id2:
The CPU information structures to compare.

15.5.5 cpu_info_dump: pretty-print a CPU information structure to the console

SYNOPSIS

#include <oskit/x86/cpuid.h>

void cpu_info_dump(struct cpu_info *info);

DESCRIPTION

This function is merely a convenient front-end to cpu_info_format; it simply formats the CPU information and outputs it to the console using printf.

15.5.6 i16_enter_pmode: enter protected mode

SYNOPSIS

#include <oskit/x86/pmode.h>

void i16_enter_pmode(int prot_cs);

DESCRIPTION

This 16-bit function switches the processor into protected mode by turning on the Protection Enable (PE) bit in CR0. The instruction that sets the PE bit is followed immediately by a jump instruction to flush the prefetch buffer, as recommended by Intel documentation.

The function also initializes the CS register with the appropriate new protected-mode code segment, whose selector is specified in the prot_cs parameter. The prot_cs must evaluate to a constant, as it is used as an immediate operand in an inline assembly language code fragment.

This routine does not perform any of the other steps in Intel’s recommended mode switching procedure, such as setting up the GDT or reinitializing the data segment registers; these steps must be performed separately. The overall mode switching sequence is necessarily much more dependent on various OS-specific factors such as the layout of the GDT; therefore the OSKit does not attempt to provide a “generic” function to perform the entire switch. Instead, the full switching sequence is provided as part of the base environment setup code; see Section 15.10 for more details.

15.5.7 i16_leave_pmode: leave protected mode

SYNOPSIS

#include <oskit/x86/pmode.h>

void i16_leave_pmode(int real_cs);

DESCRIPTION

This 16-bit function switches the processor out of protected mode and back into real mode by turning off the Protection Enable (PE) bit in CR0. The instruction that clears the PE bit is followed immediately by a jump instruction to flush the prefetch buffer, as recommended by Intel documentation. At the same time, this function also initializes the CS register with the appropriate real-mode code segment, specified by the real_cs parameter.

This routine does not perform any of the other steps in Intel’s recommended mode switching procedure, such as reinitializing the data segment registers; these steps must be performed separately. See Section 15.10 for information on the full mode switch implementation provided by the base environment.

15.5.8 paging_enable: enable page translation

SYNOPSIS

#include <oskit/x86/paging.h>

void paging_enable(oskit_addr_t pdir);

DESCRIPTION

Loads the processor page directory using pdir and turns on paging.

The caller must already have created and initialized an appropriate initial page directory as described in Intel documentation. The OSKit provides convenient facilities that can be used to create x86 page directories and page tables; for more information, see Section 15.9.

This function assumes that pdir equivalently maps the physical memory that contains the currently executing code, the currently loaded GDT and IDT.

15.5.9 paging_disable: disable page translation

SYNOPSIS

#include <oskit/x86/paging.h>

void paging_disable(void);

DESCRIPTION

Turns paging off and flushes the TLB.

This function assumes that the currently loaded page directory equivalently maps the physical memory that contains the currently executing code, the currently loaded GDT and IDT.

15.5.10 gate_init: install gate descriptors

SYNOPSIS

#include <oskit/x86/gate_init.h>

void gate_init(struct x86_gate *dest, const struct gate_init_entry *src, unsigned entry_cs);

DESCRIPTION

Install entries in a processor descriptor table from the specified array of gate descriptors (see Section 15.3.9). Typically used to initialize the processor IDT with trap and interrupt vectors (see Section 15.7.4).

PARAMETERS
dest:
Pointer to the x86 descriptor table to fill in.
src:
Pointer to the gate_init_entry array to copy from.
entry_cs:
Code segment selector to associate with all entries.
DEPENDENCIES
fill_gate:
§ 15.3.8

15.6 (X86) Base Environment

The base environment code for the x86 architecture is designed to assist the OS developer in dealing with much of the “x86 grunge” that OS developers typically would rather not worry about. The OSKit provides easy-to-use primitives to set up and maintain various common flavors of x86 kernel environments without unnecessarily constraining the OS implementation. The base environment support on the x86 architecture is divided into the three main categories: segmentation, paging, and trap handling. The base environment support code in each category is largely orthogonal and easily separable, although it is also designed to work well together.

15.6.1 Memory Model

The x86 architecture supports a very complex virtual memory model involving both segmentation and paging; one of the goals of the OSKit’s base environment support for the x86 is to smooth over some of this complexity, hiding the details that the OS doesn’t want to deal with while still allowing the OS full freedom to use the processor’s virtual memory mechanisms as it sees fit. This section describes the memory models supported and assumed by the base environment.

First, here is a summary of several important terms that are used heavily used in the following text; for full details on virtual, linear, and physical addresses on the x86 architecture, see the appropriate processor manuals.

The OSKit provides a standard mechanism, defined in base_vm.h (see Section 15.6.2), which is used throughout the base environment to maintain considerable independence from the memory model in effect. These facilities allow the base environment support code to avoid various assumptions about the relationships between kernel virtual addresses, linear addresses, and physical addresses. Client OS code can use these facilities as well if desired.

Of course, it is impractical for the base environment code to avoid assumptions about the memory model completely. In particular, the code assumes that, for “relevant” code and data (e.g., the functions implementing the base environment and the data structures they manipulate), kernel virtual addresses can be converted to and from linear or physical addresses by adding or subtracting an offset stored in a global variable. However, the code does not assume that these offsets are always the same (the client OS is allowed to change them dynamically), or that all available physical memory is mapped into the kernel’s virtual address space, or that all linear memory is accessible through the kernel’s data segment descriptors. Detailed information about the memory model assumptions made by particular parts of the base environment support are documented in the appropriate API sections.

If the OSKit’s startup code is being used to start the OS, then the specific memory model in effect initially depends on the startup environment, described in later the appropriate sections. For example, for kernels booted from a MultiBoot boot loader, in the initial memory environment virtual addresses, linear addresses, and physical addresses are all exactly equal (the offsets are zero). On the other hand, for kernels loaded from DOS, linear addresses and physical addresses will still be equal but kernel virtual addresses will be at some offset depending on where in physical memory the kernel was loaded. Regardless of the initial memory setup, the client OS is free to change the memory model later as necessary.

XXX example memory maps

XXX explain how to change memory models at run-time

15.6.2 base_vm.h: definitions for the base virtual memory environment

SYNOPSIS

#include <oskit/machine/base_vm.h>

DESCRIPTION

This header file provides generic virtual memory-related definitions commonly used throughout the base environment, which apply to both segmentation and paging. In particular, this file defines a set of macros and global variables which allow the rest of the base environment code in the toolkit (and the client OS, if it chooses) to maintain independence from the memory model in effect. These facilities allow code to avoid various assumptions about the relationships between kernel virtual addresses, linear addresses, and physical addresses.

The following variable and associated macros are provided to convert between linear and kernel virtual addresses.

linear_base_va:
This global variable defines the address in kernel virtual memory that corresponds to address 0 in linear memory. It is used by the following conversion macros; therefore, changing this variable changes the behavior of the associated macros.
lintokv(la ):
This macro converts linear address la into a kernel virtual address and returns the result as an oskit_addr_t.
kvtolin(va ):
For example, the segmentation initialization code uses kvtolin() to calculate the linear addresses of segmentation structures to be used in segment descriptor or pseudo-descriptor structures provided to the processor.

Similarly, the following variable and associated macros convert between physical and kernel virtual addresses. (Conversions between linear and physical addresses can be done by combining the two sets of macros.)

phys_mem_va:
This global variable defines the address in kernel virtual memory that corresponds to address 0 in physical memory. It is used by the following conversion macros; therefore, changing this variable changes the behavior of the associated macros.
phystokv(pa ):
This macro converts physical address pa into a kernel virtual address and returns the result as an oskit_addr_t. The macro makes the assumption that the specified physical address can be converted to a kernel virtual address this way: in OS kernels that do not direct-map all physical memory into the kernel’s virtual address space, the caller must ensure that the supplied pa refers to a physical address that is mapped. For example, the primitive page table management code provided by the OSKit’s base environment uses this macro to access page table entries given the physical address of the page table; therefore, these functions can only be used if page tables are allocated from physical pages that are direct-mapped into the kernel’s address space.
kvtophys(va ):
This macro converts kernel virtual address va into a physical address and returns the result as an oskit_addr_t. The macro assumes that the virtual address can be converted directly to a physical address this way; the caller must ensure that this is the case. For example, some operating systems only direct-map the kernel’s code and statically allocated data; in such kernels, va should only refer to statically-allocated variables or data structures. This is generally sufficient for the OSKit’s base environment code, which mostly operates on statically-allocated data structures; however, the OS must of course take its chosen memory model into consideration if it uses these macros as well.

XXX real_cs

Note that there is nothing in this header file that defines or relates to “user-mode” address spaces. This is because the base environment code in the OSKit is not concerned with user mode in any way; in fact, it doesn’t even care whether or not the OS kernel implements user address spaces at all. For example, boot loaders or unprotected real-time kernels built using the OSKit probably do not need any notion of user mode at all.

15.6.3 base_cpu_setup: initialize and activate the base CPU environment

SYNOPSIS

#include <oskit/machine/base_cpu.h>

void base_cpu_setup(void);

DESCRIPTION

This function provides a single entrypoint to initialize and activate all of the processor structures necessary for ordinary execution. This includes identifying the CPU, and initializing and activating the base GDT, IDT, and TSS, and reloading all segment registers as recommended by Intel. The call returns with the CS segment set to KERNEL_CS (the default kernel code segment; see 15.7.1 for details), DS, ES, and SS set to KERNEL_DS (the default kernel data segment), and FS and GS set to 0. After the base_cpu_setup call completes, a full working kernel environment is in place: segment registers can be loaded, interrupts and traps can be fielded by the OS, privilege level changes can occur, etc.

This function does not initialize or activate the processor’s paging mechanism, since unlike the other mechanisms, paging is optional on the x86 and not needed in some environments (e.g., boot loaders or embedded kernels).

The base_cpu_setup function is actually just a simple wrapper that calls base_cpu_init followed by base_cpu_load.

Note that it is permissible to call this function (and/or the more primitive functions it is built on) more than once. This is particularly useful when reconfiguring the kernel memory map. For example, a typical MultiBoot (or other 32-bit) kernel generally starts out with paging disabled, so it must run in the low range of linear/physical memory. However, after enabling page translation, the OS may later want to relocate itself to run at a higher address in linear memory so that application programs can use the low part (e.g., v86-mode programs). An easy way to do this with the OSKit is to call base_cpu_setup once at the very beginning, to initialize the basic unpaged kernel environment, and then later, after paging is enabled and appropriate mappings have been established in high linear address space, modify the linear_base_va variable (Section 15.6.2) to reflect the kernel’s new linear address base, and finally call base_cpu_setup again to reinitialize and reload the processor tables according to the new memory map.

DEPENDENCIES
base_cpu_init:
§ 15.6.4
base_cpu_load:
§ 15.6.5

15.6.4 base_cpu_init: initialize the base environment data structures

SYNOPSIS

#include <oskit/machine/base_cpu.h>

void base_cpu_init(void);

DESCRIPTION

This function initializes all of the critical data structures used by the base environment, including base_cpuid, base_idt, base_gdt, and base_tss, but does not actually activate them or otherwise modify the processor’s execution state. The base_cpu_load function must be called later to initialize the processor with these structures. Separate initialization and activation functions are provided to allow the OS to customize the processor data structures if necessary before activating them.

DEPENDENCIES
cpuid:
§ 15.5.2
base_trap_init:
§ 15.8.2
base_gdt_init:
§ 15.7.2
base_tss_init:
§ 15.7.7

15.6.5 base_cpu_load: activate the base processor execution environment

SYNOPSIS

#include <oskit/machine/base_cpu.h>

void base_cpu_load(void);

DESCRIPTION

This function loads the critical base environment data structures (in particular, the GDT, IDT, and TSS) into the processor, and reinitializes all segment registers from the new GDT as recommended in Intel processor documentation. The structures must already have been set up by a call to base_cpu_init and/or custom initialization code in the client OS.

This function returns with the CS segment set to KERNEL_CS (the default kernel code segment; see Section 15.7.1 for details), DS, ES, and SS set to KERNEL_DS (the default kernel data segment), and FS and GS set to 0. After the base_cpu_load call completes, a full working kernel environment is in place: segment registers can be loaded, interrupts and traps can be fielded by the OS, privilege level changes can occur, etc.

DEPENDENCIES
base_gdt_load:
§ 15.7.3
base_idt_load:
§ 15.7.5
base_tss_load:
§ 15.7.8

15.6.6 base_cpuid: global variable describing the processor

SYNOPSIS

#include <oskit/machine/base_cpu.h>

extern struct cpu_info base_cpuid;

DESCRIPTION

This is a global variable that is filled in by base_cpu_init with information about the processor on which base_cpu_init was called. (Alternatively, it can also be initialized manually by the OS simply by calling cpuid(&base_cpuid)). This structure is used by other parts of the kernel support library to determine whether or not certain processor features are available, such as 4MB superpages. See 15.5.1 for details on the contents of this structure.

Note that in a multiprocessor system, this variable will reflect the boot processor. This is generally not a problem, since most SMPs use identical processors, or at least processors in the same generation, so that they appear equivalent to OS software. (For example, it is very unlikely that you’d find an SMP that mixes 486 and Pentium processors), However, if this ever turns out to be a problem, the OS can always override the cpuid or base_cpu_init function, or just modify the contents of the base_cpuid variable after calling base_cpu_init so that it reflects the least common denominator of all the processors.

15.6.7 base_stack.h: default kernel stack

SYNOPSIS

#include <oskit/machine/base_stack.h>

DESCRIPTION

Definitions related to the default kernel stack.

BASE_STACK_SIZE:
Preprocessor constant defining the size in bytes of the default kernel stack.
base_stack_start:
C external variable declaration for the low-address end of the stack. On the x86, this is the end toward which the stack grows.
base_stack_end:
C external variable declaration for the high-address end of the stack (base_stack_start + BASE_STACK_SIZE). On the x86, this is the end where the stack begins.

This header file can be used in assembly language code as well as C.

15.7 (X86) Base Environment: Segmentation Support

Although most modern operating systems use a simple “flat” address space model, the x86 enforces a segmentation model which cannot be disabled directly; instead, it must be set up to emulate a flat address space model if that is what the OS desires. The base environment code provides functionality to set up a simple flat-model processor environment suitable for many types of kernels, both “micro” and “macro.” For example, it provides a default global descriptor table (GDT) containing various flat-model segments for the kernel’s use, as well as a default task state segment (TSS).

Furthermore, even though this base environment is often sufficient, the client OS is not limited to using it exactly as provided by default: the client kernel is given the flexibility to tweak various parameters, such as virtual and linear memory layout, as well as the freedom to operate completely outside of the base environment when necessary. For example, although the base environment provides a default TSS, the OS is free to create its own TSS structures and use them when running applications that need special facilities such as v86 mode or I/O ports. Alternatively, the OS could use the default processor data structures only during startup, and switch to its own complete, customized set after initialization.

The base environment code in the OSKit generally assumes that it is running in a simple flat model, in which only one code segment and one data segment are used for all kernel code and data, respectively, and that the code and data segments are synonymous (they each map to the same range of linear addresses). The OS is free to make more exotic uses of segmentation if it so desires, as long as the OSKit code is run in a consistent environment.

XXX diagram of function call tree?

The base segmentation environment provided by the OSKit is described in more detail in the following API sections.

15.7.1 base_gdt: default global descriptor table for the base environment

SYNOPSIS

#include <oskit/x86/base_gdt.h>

extern struct x86_desc base_gdt[GDTSZ];

DESCRIPTION

This variable is used in the base environment as the default global descriptor table. The default base_gdt definition contains GDTSZ selector slots, including the Intel-reserved, permanently unused slot 0.

The following symbols are defined in base_gdt.h to be segment selectors for the descriptors in the base GDT. These selectors can be converted to indices into the GDT descriptor array base_gdt by dividing by 8 (the processor reserves the low three bits of all selectors for other information).

BASE_TSS:
A selector for the base task state segment (base_tss). The BASE_TSS segment descriptor is initialized by base_gdt_init, but the base_tss structure itself is initialized by base_tss_init and loaded into the processor by base_tss_load; see Section 15.7.6 for more details.
KERNEL_CS:
This is the default kernel code segment selector. It is initialized by base_gdt_init to be a flat-model, 4GB, readable, ring 0 code segment; base_gdt_load loads this segment into the CS register while reinitializing the processor’s segment registers.
KERNEL_DS:
This is the default kernel data segment selector. It is initialized by base_gdt_init to be a flat-model, 4GB, writable, ring 0 data segment; base_gdt_load loads this segment into the DS, ES, and SS registers while reinitializing the processor’s segment registers.
KERNEL_16_CS:
This selector is identical to KERNEL_CS except that it is a 16-bit code segment (the processor defaults to 16-bit operand and addressing modes rather than 32-bit while running code in this segment), and it has a 64KB limit rather than 4GB. This selector is used when switching between real and protected mode, to provide an intermediate 16-bit protected mode execution context. It is unused in kernels that never execute in real mode (e.g., typical MultiBoot kernels).
KERNEL_16_DS:
This selector is a data segment synonym for KERNEL_16_CS; it is generally only used when switching from protected mode back to real mode. It is used to ensure that the segment registers contain sensible real-mode values before performing the switch, as recommended in Intel literature.
LINEAR_CS:
This selector is set up to be a ring 0 code segment that directly maps the entire linear address space: in other words, it has an offset of zero and a 4GB limit. In some environments, where kernel virtual addresses are the same as linear addresses, this selector is a synonym for KERNEL_CS.
LINEAR_DS:
This is a data segment otherwise identical to LINEAR_CS.
USER_CS:
This selector is left unused and uninitialized by the OSKit; nominally, it is intended to be used as a code segment for unprivileged user-level code.
USER_DS:
This selector is left unused and uninitialized by the OSKit; nominally, it is intended to be used as a data segment for unprivileged user-level code.

If the client OS wants to make use of the base GDT but needs more selector slots for its own purposes, it can define its own instance of the base_gdt variable so that it has room for more than GDTSZ elements; base_gdt_init will initialize only the first “standard” segment descriptors, leaving the rest for the client OS’s use.

On multiprocessor systems, the client OS may want each processor to have its own GDT. In this case, the OS can create a separate clone of the base GDT for each additional processor besides the boot processor, and leave the boot processor using the base GDT. Alternatively, the OS could use the base GDT only during initialization, and switch all processors to custom GDTs later; this approach provides the most flexibility to the OS, since the custom GDTs can be arranged in whatever way is most convenient.

15.7.2 base_gdt_init: initialize the base GDT to default values

SYNOPSIS

#include <oskit/x86/base_gdt.h>

void base_gdt_init(void); void i16_base_gdt_init(void);

DESCRIPTION

This function initializes the standard descriptors in the base GDT as described in Section 15.7.1.

For all of the standard descriptors except LINEAR_CS and LINEAR_DS, the kvtolin macro is used to compute the linear address to plug into the offset field of the descriptor: for BASE_TSS, this is kvtolin(&base_tss); for the kernel code and data segments, it is kvtolin(0) (i.e., the linear address corresponding to the beginning of kernel virtual address space). LINEAR_CS and LINEAR_DS are always given an offset of 0.

A 16-bit version of this function, i16_base_gdt_init, is also provided so that the GDT can be initialized properly before the processor has been switched to protected mode. (Switching to protected mode on the x86 according to Intel’s recommended procedure requires a functional GDT to be already initialized and activated.)

DEPENDENCIES
fill_descriptor:
§ 15.3.8
kvtolin:
§ 15.6.2
base_gdt:
§ 15.7.1
base_tss:
§ 15.7.6

15.7.3 base_gdt_load: load the base GDT into the CPU

SYNOPSIS

void base_gdt_load(void); void i16_base_gdt_load(void);

DESCRIPTION

This function loads the base GDT into the processor’s GDTR, and then reinitializes all segment registers from the descriptors in the newly loaded GDT. It returns with the CS segment set to KERNEL_CS (the default kernel code segment; see Section 15.7.1 for details), DS, ES, and SS set to KERNEL_DS (the default kernel data segment), and FS and GS set to 0.

DEPENDENCIES
kvtolin:
§ 15.6.2
base_gdt:
§ 15.7.1

15.7.4 base_idt: default interrupt descriptor table

SYNOPSIS

#include <oskit/x86/base_idt.h>

extern struct x86_desc base_idt[IDTSZ];

DESCRIPTION

This global variable is used in the base environment as the default interrupt descriptor table. The default definition of base_idt in the library contains the architecturally-defined maximum of 256 interrupt vectors (IDTSZ).2

The base_idt.h header file does not define any symbols representing interrupt vector numbers. The lowest 32 vectors are the processor trap vectors defined by Intel; since these are not specific to the base environment, they are defined in the generic header file x86/trap.h (see Section 15.3.10). Standard hardware interrupt vectors are PC-specific, and therefore are defined separately in x86/pc/irq_list.h (see Section 15.4.1). For the same reason, there is no base_idt_init function, only separate functions to initialize the trap vectors in the base IDT (base_trap_init, Section 15.8.2), and hardware interrupt vectors in the IDT (base_irq_init, Section 15.12.3).

15.7.5 base_idt_load: load the base IDT into the current processor

SYNOPSIS

#include <oskit/x86/base_idt.h>

void base_idt_load(void);

DESCRIPTION

This function loads the base_idt into the processor, so that subsequent traps and hardware interrupts will vector through it. It uses the kvtolin macro to compute the proper linear address of the IDT to be loaded into the processor.

DEPENDENCIES
kvtolin:
§ 15.6.2
base_idt:
§ 15.7.4

15.7.6 base_tss: default task state segment

SYNOPSIS

#include <oskit/x86/base_tss.h>

extern struct x86_tss base_tss;

DESCRIPTION

The base_tss variable provides a default task state segment that the OS can use for privilege level switching if it does not otherwise use the x86’s task switching mechanisms. The x86 architecture requires every protected-mode OS to have at least one TSS even if no task switching is done; however, many x86 kernels do not use the processor’s task switching features because it is faster to context switch manually. Even if special TSS segments are used sometimes (e.g., to take advantage of the I/O bitmap feature when running MS-DOS programs), the OS can still use a common TSS for all tasks that do not need to use these special features; this is the strategy taken by the Mach kernel, for example. The base_tss provided by the toolkit serves in this role as a generic “default” TSS.

The base_tss is a minimal TSS, in that it contains no I/O bitmap or interrupt redirection map. XXX The toolkit also supports an alternate default TSS with a full I/O permission bitmap, but it isn’t fully integrated or documented yet.

15.7.7 base_tss_init: initialize the base task state segment

SYNOPSIS

#include <oskit/x86/base_tss.h>

void base_tss_init(void);

DESCRIPTION

The base_tss_init function initializes the base_tss to a valid minimal state. It sets the I/O permission bitmap offset to point past the end of the TSS, so that it will be interpreted by the processor as empty (no permissions for any I/O ports). It also initializes the ring 0 stack segment selector (ss0) to KERNEL_DS, and the ring 0 stack pointer (esp0) to the current stack pointer value at the time of the function call, to provide a minimal working context for trap handling. Once the OS kernel sets up a “real” kernel stack, it should reinitialize base_tss.esp0 to point to that.

DEPENDENCIES
base_tss:
§ 15.7.6

15.7.8 base_tss_load: load the base TSS into the current processor

SYNOPSIS

#include <oskit/x86/base_tss.h>

void base_tss_load(void);

DESCRIPTION

This function activates the base_tss in the processor using the LTR instruction, after clear the busy bit in the BASE_TSS segment descriptor to ensure that a spurious trap isn’t generated.

DEPENDENCIES
base_gdt:
§ 15.7.1

15.8 (X86) Base Environment: Trap Handling

The first 32 vectors in the IDT are used to handle processor exceptions (“traps”). In the base OSKit environment, these vectors are initialized from the base_trap_inittab array (15.8.3) using the base_trap_init function (15.8.2). By default, each exception vector in the processor IDT is set to point to a common assembly language stub that saves a standard trap frame (15.8.1), and calls a designated high-level handler specified in the base_trap_handlers table (15.8.4). Initially, all the entries in this table point to base_trap_default_handler (15.8.5). Custom trap handlers can be installed by changing the appropriate entry in the table. The default action for all traps can be changed by overriding base_trap_default_handler.

This affords client OSes with a variety of choices for modifying the behavior of trap handling. By using the base trap environment unchanged (i.e., the client OS is not expecting or handling traps), all traps will produce a trap dump and panic. This behavior is sufficient for most simple OSKit applications. By setting entries base_trap_handlers, the client can provide its own C language trap handlers while still using the default trap_state structure. The OSKit remote GDB debugging package (15.17.5) does this. Finally, the client OS can override base_trap_inittab to allow for different high-level handlers for every exception type and/or to permit the use of a different trap state format.

15.8.1 trap_state: saved state format used by the default trap handler

SYNOPSIS

#include <oskit/x86/base_trap.h>

 struct trap_state
 {
         /* Saved segment registers */
         unsigned int    gs;
         unsigned int    fs;
         unsigned int    es;
         unsigned int    ds;
 
         /* PUSHA register state frame */
         unsigned int    edi;
         unsigned int    esi;
         unsigned int    ebp;
         unsigned int    cr2;    /* we save cr2 over esp for page faults */
         unsigned int    ebx;
         unsigned int    edx;
         unsigned int    ecx;
         unsigned int    eax;
 
         /* Processor trap number, 0-31.  */
         unsigned int    trapno;
 
         /* Error code pushed by the processor, 0 if none.  */
         unsigned int    err;
 
         /* Processor state frame */
         unsigned int    eip;
         unsigned int    cs;
         unsigned int    eflags;
         unsigned int    esp;
         unsigned int    ss;
 
         /* Virtual 8086 segment registers */
         unsigned int    v86_es;
         unsigned int    v86_ds;
         unsigned int    v86_fs;
         unsigned int    v86_gs;
 };
DESCRIPTION

This structure defines the saved state frame pushed on the stack by the default trap entrypoints provided by the base environment (see Section 15.8.3). It is also used by the trap_dump routine, which is used in the default environment to dump the saved register state and panic if an unexpected trap occurs; and by gdb_trap, the default trap handler for remote GDB debugging.

This client OS is not obligated to use this structure as the saved state frame for traps it handles; if this structure is not used, then the OS must also override (or not use) the dependent routines mentioned above.

The structure elements from err down corresponds to the basic trap frames pushed on the stack by the x86 processor. (For traps in which the processor does not push an error code, the default trap entrypoint code sets err to zero.) The structure elements from esp down are only pushed by traps from lower privilege (rings 1-3), and the structure elements from v86_es down are only pushed by traps from v86 mode.

The rest of the state frame is pushed manually by the default trap entrypoint code. The saved integer register state is organized in a format compatible with the processor’s PUSHA instruction. However, in the slot that would otherwise hold the pushed ESP (which is useless since it is the trap handler’s stack pointer rather than the trapping code’s stack pointer), the default trap handler saves the CR2 register (page fault linear address) during page faults.

This trap state structure is borrowed from Mach.

15.8.2 base_trap_init: initialize the processor trap vectors in the base IDT

SYNOPSIS

#include <oskit/x86/base_trap.h>

void base_trap_init(void);

DESCRIPTION

This function initializes the processor trap vectors in the base IDT to the default trap entrypoints defined in base_trap_inittab.

DEPENDENCIES
gate_init:
§ 15.5.10
base_idt:
§ 15.7.4
base_trap_inittab:
§ 15.8.3

15.8.3 base_trap_inittab: initialization table for the default trap entrypoints

SYNOPSIS

#include <oskit/x86/base_trap.h>

extern struct gate_init_entry base_trap_inittab[];

DESCRIPTION

This gate initialization table (see Section 15.3.9) encapsulates the base environment’s default trap entrypoint code. This module provides IDT entrypoints for all of the processor-defined trap vectors; each entrypoint pushes a standard state frame on the stack (see Section 15.8.1), and then calls the C function pointed to by the corresponding entry in base_trap_handlers array (see Section 15.8.4). Through these entrypoints, the OSKit provides the client OS with a convenient, uniform method of handling all processor traps in ordinary high-level C code.

If a trap occurs and the trap entrypoint code finds that the corresponding entry in base_trap_handlers is null, or if it points to a handler routine but the handler returns a nonzero value indicating failure, the entrypoint code calls trap_dump_panic (see Section 15.8.7) to dump the register state to the console and panic the kernel. This behavior is typically appropriate in kernels that do not expect traps to occur during proper operation (e.g., boot loaders or embedded operating systems), where a trap probably indicates a serious software bug.

On the other hand, if a trap handler is present and returns success (zero), the entrypoint code restores the saved state and resumes execution of the trapping code. The trap handler may change the contents of the trap_state structure passed by the entrypoint code; in this case, final contents of the structure on return from the trap handler will be the state restored.

All of the IDT entries initialized by the base_trap_inittab are trap gates rather than interrupt gates; therefore, if hardware interrupts are enabled when a trap occurs, then interrupts will still be enabled during the trap handler unless the trap handler explicitly disables them. If the OS wants interrupts to be disabled during trap handling, it can change the processor trap vectors in the IDT (vectors 0-31) into interrupt gates, or it can simply use its own trap entrypoint code instead.

DEPENDENCIES
struct trap_state:
§ 15.8.1
base_trap_handlers:
§ 15.8.4
trap_dump_panic:
§ 15.8.7

15.8.4 base_trap_handlers: Array of handler routines for hardware traps

SYNOPSIS

#include <oskit/x86/base_trap.h>

void (*base_trap_handlers[BASE_TRAP_COUNT]) (struct trap_state *ts );

DESCRIPTION

Contains a function pointer for every hardware trap vector. By default, all entries in this table point to base_trap_default_handler, which will simply dump the register state to the console and panic. The client OS can set entries in this table to point to its own trap handler function(s), or to alternative trap handlers supplied by the OSKit, such as the remote GDB debugging trap handler, gdb_trap (see Section 15.17.5).

PARAMETERS
state:
A pointer to the trap state structure to dump.
RETURNS

The trap handler returns zero (success) to resume execution, or nonzero (failure) to cause the entrypoint code to dump the register state and panic the kernel.

15.8.5 base_trap_default_handler: default trap handler for unexpected traps

SYNOPSIS

#include <oskit/x86/pc/base_trap.h>

void base_trap_default_handler(struct trap_state *state);

DESCRIPTION

This routine is the default handler for all traps in the base environment. It simply displays displays the trap state information and then calls panic.

It is expected that the client OS will override entries in the base_trap_handlers array for traps it cares about. Alternatively, the client OS may override the base_trap_default_handler entrypoint entirely.

PARAMETERS
state:
A pointer to the processor state at the time of the interrupt.
DEPENDENCIES
struct trap_state:
§ 15.8.1

15.8.6 trap_dump: dump a saved trap state structure

SYNOPSIS

#include <oskit/x86/base_trap.h>

void trap_dump(const struct trap_state *state);

DESCRIPTION

This function dumps the contents of the specified trap state frame to the console using the printf function, in a simple human-readable form. The function is smart enough to determine whether the trap occurred from supervisor mode, user mode, or v86 mode, and interpret the saved state accordingly. For example, for traps from rings 1-3 or from v86 mode, the the original stack pointer is part of the saved state frame; however, for traps from ring 0, the original stack pointer is simply the end of the stack frame pushed by the processor, since no stack switch occurs in this case.

In addition, for traps from ring 0, this routine also provides a hex dump of the top of the kernel stack as it appeared when the trap occurred; this stack dump can aid in tracking down the cause of a kernel bug. trap_dump does not attempt to dump the stack for traps from user or v86 mode, because there seems to be no sufficiently generic way for it to access the appropriate user stack; in addition, in this case the trap might have been caused by a user-stack-related exception, in which case attempting to dump the user stack could lead to a recursive trap.

PARAMETERS
state:
A pointer to the trap state structure to dump.
DEPENDENCIES
struct trap_state:
§ 15.8.1
printf:
§ 14.6

15.8.7 trap_dump_panic: dump a saved trap state structure

SYNOPSIS

#include <oskit/x86/base_trap.h>

void trap_dump_panic(const struct trap_state *state);

DESCRIPTION

This function simply calls trap_dump (Section 15.8.6) to dump the specified trap state frame, and then calls panic (Section 14.8.3). It is invoked by the default trap entrypoint code (Section 15.8.3) if a trap occurs when there is no interrupt handler, or if there is an interrupt handler but it returns a failure indication.

PARAMETERS
state:
A pointer to the trap state structure to dump.
DEPENDENCIES
trap_dump:
§ 15.8.6
panic:
§ 14.8.3

15.9 (X86) Base Environment: Page Translation

XXX diagram of function call tree?

XXX Although a “base” x86 paging environment is defined, it is not automatically initialized by base_cpu_init, and paging is not activated by base_cpu_load. This is because unlike segmentation, paging is an optional feature on the x86 architecture, and many simple “kernels” such as boot loaders would prefer to ignore it completely. Therefore, client kernels that do want the base paging environment must call the functions to initialize and activate it manually, after the basic CPU segmentation environment is set up.

XXX describe assumptions made about use of page tables, e.g. 4MB pages whenever possible, always modify/unmap _exactly_ the region that was mapped.

XXX assumes that mappings are only changed or unmapped with the same size and offset as the original mapping.

XXX does not attempt to support page table sharing in any way, since this code has no clue about the relationship between address spaces; it only knows about page directories and page tables.

15.9.1 base_paging_init: create minimal kernel page tables and enable paging

SYNOPSIS

#include <oskit/x86/base_paging.h>

void base_paging_init(void);

DESCRIPTION

This function can be used to set up a minimal paging environment. It first allocates and clears an initial page directory using ptab_alloc (see Section 15.9.7), sets base_pdir_pa to point to it (see Section 15.9.2), then direct-maps all known physical memory into this address space starting at linear address 0, allocating additional page tables as needed. Finally, this function enables the processor’s paging mechanism, using the base page directory as the initial page directory.

The global variable phys_mem_max (see Section 15.11.2) is assumed to indicate the top of physical memory; all memory from 0 up to at least this address is mapped. The function actually rounds phys_mem_max up to the next 4MB superpage boundary, so that on Pentium and higher processors, all physical memory can be mapped using 4MB superpages even if known physical memory does not end exactly on a 4MB boundary. Note that phys_mem_max does not necessarily need to reflect all physical memory in the machine; for example, it is perfectly reasonable for the client OS to set it to some artificially lower value so that only that part of physical memory is direct-mapped.

On Pentium and higher processors, this function sets the PSE (page size extensions) bit in CR4 in addition to the PG (paging) bit, so that the 4MB page mappings used to map physical memory will work properly.

DEPENDENCIES
base_pdir_pa:
§ 15.9.2
ptab_alloc:
§ 15.9.7
pdir_map_range:
§ 15.9.11
base_cpuid:
§ 15.6.6
paging_enable:
§ 15.5.8

15.9.2 base_pdir_pa: initial kernel page directory

SYNOPSIS

#include <oskit/x86/base_paging.h>

extern oskit_addr_t base_pdir_pa;

DESCRIPTION

This variable is initialized by base_paging_init (see Section 15.9.1) to contain the physical address of the base page directory. This is the value that should be loaded into the processor’s page directory base register (CR3) in order to run in the linear address space defined by this page directory. (The base page directory is automatically activated in this way during initialization; the client OS only needs to load the CR3 register itself if it wants to switch among multiple linear address spaces.) The pdir_find_pde function (Section 15.9.3) and other related functions can be used to manipulate the page directory and its associated page tables.

Initially, the base page directory and its page tables directly map physical memory starting at linear address 0. The client OS is free to change the mappings after initialization, for example by adding new mappings outside of the physical address range, or by relocating the physical memory mappings to a different location in the linear address space as described in Section 15.6.3.

Most “real” operating systems will need to create other, separate page directories and associated page tables to represent different address spaces or protection domains. However, the base page directory may still be useful, e.g., as a template for initializing the common kernel portion of other page directories, or as a “kernel-only” address space for use by kernel tasks, etc.

15.9.3 pdir_find_pde: find an entry in a page directory given a linear address

SYNOPSIS

#include <oskit/x86/base_paging.h>

pd_entry_t *pdir_find_pde(oskit_addr_t pdir_pa, oskit_addr_t la);

DESCRIPTION

This primitive macro uses the appropriate bits in linear address la (bits 22-31) to look up a particular entry in the specified page directory. Note that this function takes the physical address of a page directory, but returns a kernel virtual address (i.e., an ordinary pointer to the selected page directory entry).

PARAMETERS
pdir_pa:
Physical address of the page directory.
la:
Linear address to be used to select a page directory entry.
RETURNS

Returns a pointer to the selected page directory entry.

DEPENDENCIES
phystokv:
§ 15.6.2

15.9.4 ptab_find_pte: find an entry in a page table given a linear address

SYNOPSIS

#include <oskit/x86/base_paging.h>

pd_entry_t *ptab_find_pte(oskit_addr_t ptab_pa, oskit_addr_t la);

DESCRIPTION

This macro uses the appropriate bits in la (bits 12-21) to look up a particular entry in the specified page table. This macro is just like pdir_find_pde, except that it selects an entry based on the page table index bits in the linear address rather than the page directory index bits (bits 22-31). Note that this function takes the physical address of a page table, but returns a kernel virtual address (an ordinary pointer).

PARAMETERS
ptab_pa:
Physical address of the page table.
la:
Linear address to be used to select a page table entry.
RETURNS

Returns a pointer to the selected page table entry.

DEPENDENCIES
phystokv:
§ 15.6.2

15.9.5 pdir_find_pte: look up a page table entry from a page directory

SYNOPSIS

#include <oskit/x86/base_paging.h>

pt_entry_t *pdir_find_pte(oskit_addr_t pdir_pa, oskit_addr_t la);

DESCRIPTION

This function is a combination of pdir_find_pde and ptab_find_pte: it descends through both levels of the x86 page table hierarchy and finds the page table entry for the specified linear address.

This function assumes that if the page directory entry selected by bits 22-31 of la is valid (the INTEL_PDE_VALID bit is set), then that entry actually refers to a page table, and is not a 4MB page mapping. The caller must ensure that this is the case.

PARAMETERS
pdir_pa:
Physical address of the page directory.
la:
Linear address to use to select the appropriate page directory and page table entries.
RETURNS

Returns a pointer to the selected page table entry, or NULL if there is no page table for this linear address.

DEPENDENCIES
pdir_find_pde:
§ 15.9.3
ptab_find_pte:
§ 15.9.4

15.9.6 pdir_get_pte: retrieve the contents of a page table entry

SYNOPSIS

#include <oskit/x86/base_paging.h>

pt_entry_t pdir_get_pte(oskit_addr_t pdir_pa, oskit_addr_t la);

DESCRIPTION

This function is a simple extension of pdir_find_pte: instead of returning the address of the selected page table entry, it returns the contents of the page table entry: i.e., the physical page frame in bits 12-31 and the associated INTEL_PTE_* flags in bits 0-11. If there is no page table in the page directory for the specified linear address, then this function returns 0, the same as if there was a page table but the selected page table entry was zero (invalid).

As with pdir_find_pte, this function assumes that if the page directory entry selected by bits 22-31 of la is valid (the INTEL_PDE_VALID bit is set), then that entry actually refers to a page table, and is not a 4MB page mapping.

PARAMETERS
pdir_pa:
Physical address of the page directory.
la:
Linear address to use to select the appropriate page directory and page table entries.
RETURNS

Returns the selected page table entry, or zero if there is no page table for this linear address. Also returns zero if the selected page table entry exists but is zero.

DEPENDENCIES
pdir_find_pte:
§ 15.9.5

15.9.7 ptab_alloc: allocate a page table page and clear it to zero

SYNOPSIS

#include <oskit/x86/base_paging.h>

int ptab_alloc([out] oskit_addr_t *out_ptab_pa);

DESCRIPTION

All of the following page mapping routines call this function to allocate new page tables as needed to create page mappings. It attempts to allocate a single page of physical memory, and if successful, returns 0 with the physical address of that page in *out_ptab_pa. The newly allocated page is cleared to all zeros by this function. If this function is unsuccessful, it returns nonzero.

The default implementation of this function assumes that the OSKit’s minimal C library (libc) and list-based memory manager (liblmm) are being used to manage physical memory, and allocates page table pages from the malloc_lmm memory pool (see Section 14.5.1). However, in more complete OS environments, e.g., in which low physical memory conditions should trigger a page-out rather than failing immediately, this routine can be overridden to provide the desired behavior.

PARAMETERS
out_ptab_pa:
The address of a variable of type oskit_addr_t into which this function will deposit the physical address of the allocated page, if the allocation was successful.
RETURNS

Returns zero if the allocation was successful, or nonzero on failure.

DEPENDENCIES
lmm_alloc_page:
§ 25.6.8
malloc_lmm:
§ 14.5.1
memset:
§ 14.4.18
kvtophys:
§ 15.6.2

15.9.8 ptab_free: free a page table allocated using ptab_alloc

SYNOPSIS

#include <oskit/x86/base_paging.h>

void ptab_free(oskit_addr_t ptab_pa);

DESCRIPTION

The page mapping and unmapping functions described in the following sections call this routine to free a page table that is no longer needed; thus, this function is the partner of ptab_alloc (see Section 15.9.7). The default implementation again assumes that the malloc_lmm memory pool is being used to manage physical memory. If the client OS overrides ptab_alloc to use a different allocation mechanism, it should also override ptab_free correspondingly.

PARAMETERS
ptab_pa:
The physical address of the page table page to free.
DEPENDENCIES
lmm_free_page:
§ 25.6.10

15.9.9 pdir_map_page: map a 4KB page into a linear address space

SYNOPSIS

#include <oskit/x86/base_paging.h>

int pdir_map_page(oskit_addr_t pdir_pa, oskit_addr_t la, pt_entry_t mapping);

DESCRIPTION

This function creates a single 4KB page mapping in the linear address space represented by the specified page directory. If the page table covering the specified linear address does not exist (i.e., the selected page directory entry is invalid), then a new page table is allocated using ptab_alloc and inserted into the page directory before the actual page mapping is inserted into the page table. Any new page tables created by this function are mapped into the page directory with permissions INTEL_PTE_USER | INTEL_PTE_WRITE: full permissions are granted at the page directory level, although the specified mapping value, which is inserted into the selected page table entry, may restrict permissions at the individual page granularity.

This function assumes that if the page directory entry selected by bits 22-31 of la is valid (the INTEL_PDE_VALID bit is set), then that entry actually refers to a page table, and is not a 4MB page mapping. In other words, the caller should not attempt to create a 4KB page mapping in a part of the linear address space already covered by a valid 4MB superpage mapping. The caller must first unmap the 4MB superpage mapping, then map the 4KB page (which will cause a page table to be allocated). If the caller follows the guidelines described in Section 15.9, then this requirement should not be a problem.

PARAMETERS
pdir_pa:
Physical address of the page directory acting as the root of the linear address space in which to make the requested page mapping.
la:
Linear address at which to make the mapping. Only bits 12-31 are relevant to this function; bits 0-11 are ignored.
mapping:
Contains the page table entry value to insert into the appropriate page table entry: the page frame number is in bits 12-31, and the INTEL_PTE_* flags are in bits 0-11. XXX The caller must include INTEL_PTE_VALID; other flags may be set according to the desired behavior. (To unmap pages, use pdir_unmap_page instead; see Section 15.9.10)
RETURNS

If all goes well and the mapping is successful, this function returns zero. If this function needed to allocate a new page table but the ptab_alloc function failed (returned nonzero), then this function passes back the return value from ptab_alloc.

DEPENDENCIES
pdir_find_pde:
§ 15.9.3
ptab_find_pte:
§ 15.9.4
ptab_alloc:
§ 15.9.7

15.9.10 pdir_unmap_page: unmap a single 4KB page mapping

SYNOPSIS

#include <oskit/x86/base_paging.h>

void pdir_unmap_page(oskit_addr_t pdir_pa, oskit_addr_t la);

DESCRIPTION

This function invalidates a single 4KB page mapping in the linear address space represented by the specified page directory. The la parameter should fall in a page previous mapped with pdir_map_page, otherwise the result of the call is undefined. XXX Is this overly restrictive?

This function assumes that if the page directory entry selected by bits 22-31 of la is valid (the INTEL_PDE_VALID bit is set), then that entry actually refers to a page table, and is not a 4MB page mapping. In other words, the caller should not attempt to destroy a 4KB page mapping in a part of the linear address space covered by a valid 4MB superpage mapping. Use pmap_unmap_range to remove a 4MB superpage mapping.

PARAMETERS
pdir_pa:
Physical address of the page directory acting as the root of the linear address space from which the requested page mapping is to be removed.
la:
Linear address contained in the page to be removed. Only bits 12-31 are relevant to this function; bits 0-11 are ignored.
DEPENDENCIES
pdir_find_pde:
§ 15.9.3
ptab_find_pte:
§ 15.9.4
ptab_free:
§ 15.9.8

15.9.11 pdir_map_range: map a contiguous range of physical addresses

SYNOPSIS

#include <oskit/x86/base_paging.h>

int pdir_map_range(oskit_addr_t pdir_pa, oskit_addr_t la, oskit_addr_t pa, oskit_size_t size, pt_entry_t mapping_bits);

DESCRIPTION

This function maps a range of linear addresses in the linear address space represented by the specified page directory onto a contiguous range of physical addresses. The linear (source) address, physical (destination) address, and mapping size must be multiples of the 4KB architectural page size, but other than that no restrictions are imposed on the location or size of the mapping range. If the processor description in the global base_cpuid variable (see Section 15.6.6) indicates that page size extensions are available, and the physical and linear addresses are properly aligned, then this function maps as much of the range as possible using 4MB superpage mappings instead of 4KB page mappings. Where 4KB page mappings are needed, this function allocates new page tables as necessary using ptab_alloc. Any new page tables created by this function are mapped into the page directory with permissions INTEL_PTE_USER | INTEL_PTE_WRITE: full permissions are granted at the page directory level, although the mapping_bits may specify more restricted permissions for the actual page mappings.

This function assumes that no valid mappings already exist in the specified linear address range; if any mappings do exist, this function may not work properly. If the caller follows the guidelines described in Section 15.9, always unmapping previous mappings before creating new ones, then this requirement should not be a problem.

PARAMETERS
pdir_pa:
Physical address of the page directory acting as the root of the linear address space in which to make the requested mapping.
la:
Starting linear address at which to make the mapping. Must be page-aligned.
pa:
Starting physical address to map to. Must be page-aligned.
size:
Size of the linear-to-physical mapping to create. Must be page-aligned.
mapping_bits:
Permission bits to OR into each page or superpage mapping entry. The caller must include INTEL_PTE_VALID; other flags may be set according to the desired behavior. (To unmap ranges, use pdir_unmap_range instead; see Section 15.9.13)
RETURNS

If all goes well and the mapping is successful, this function returns zero. If this function needed to allocate a new page table but the ptab_alloc function failed (returned nonzero), then this function passes back the return value from ptab_alloc.

DEPENDENCIES
pdir_find_pde:
§ 15.9.3
ptab_find_pte:
§ 15.9.4
ptab_alloc:
§ 15.9.7
base_cpuid:
§ 15.6.6

15.9.12 pdir_prot_range: change the permissions on a mapped memory range

SYNOPSIS

#include <oskit/x86/base_paging.h>

void pdir_prot_range(oskit_addr_t pdir_pa, oskit_addr_t la, oskit_size_t size, pt_entry_t new_mapping_bits);

DESCRIPTION

This function can be used to modify the permissions and other attribute bits associated with a mapping range previously created with pdir_map_range. The la and size parameters must be exactly the same as those passed to the pdir_map_range used to create the mapping.

PARAMETERS
pdir_pa:
Physical address of the page directory acting as the root of the linear address space containing the mapping to modify.
la:
Starting linear address of the mapping to modify. Must be exactly the same as the address specified to the pdir_map_range call used to create this mapping.
size:
Size of the mapping to modify. Must be exactly the same as the size specified to the pdir_map_range call used to create this mapping.
new_mapping_bits:
New permission flags to insert into each page or superpage mapping entry. The caller must include INTEL_PTE_VALID; other flags may be set according to the desired behavior. (To unmap ranges, use pdir_unmap_range; see Section 15.9.13)
DEPENDENCIES
pdir_find_pde:
§ 15.9.3
ptab_find_pte:
§ 15.9.4

15.9.13 pdir_unmap_range: remove a mapped range of linear addresses

SYNOPSIS

#include <oskit/x86/base_paging.h>

void pdir_unmap_range(oskit_addr_t pdir_pa, oskit_addr_t la, oskit_size_t size);

DESCRIPTION

This function removes a mapping range previously created using pdir_map_range. The la and size parameters must be exactly the same as those passed to the pdir_map_range used to create the mapping.

PARAMETERS
pdir_pa:
Physical address of the page directory acting as the root of the linear address space containing the mapping to destroy.
la:
Starting linear address of the mapping to destroy. Must be exactly the same as the address specified to the pdir_map_range call used to create this mapping.
size:
Size of the mapping to destroy. Must be exactly the same as the size specified to the pdir_map_range call used to create this mapping.
DEPENDENCIES
pdir_find_pde:
§ 15.9.3
ptab_find_pte:
§ 15.9.4

15.9.14 pdir_clean_range: free unused page table pages in a page directory

SYNOPSIS

#include <oskit/x86/base_paging.h>

void pdir_clean_range(oskit_addr_t pdir_pa, oskit_addr_t la, oskit_size_t size);

DESCRIPTION

This function scans the portion of the given page directory covering the given address range, looking for associated page table pages that are unnecessary and frees them. “Unnecessary” page table pages are those which contain only entries for which INTEL_PTE_VALID is not set. These pages are freed with ptab_free and the corresponding page directory entries marked as invalid.

PARAMETERS
pdir_pa:
Physical address of the page directory.
la:
Starting linear address of the region to clean. Must be page-aligned.
size:
Size (in bytes) of the region to clean. The value passed is rounded up to whole pages. Use “0” for la and “ (oskit_addr_t)0” for size to clean the entire page directory.
DEPENDENCIES
pdir_find_pde:
§ 15.9.3
ptab_find_pte:
§ 15.9.4
ptab_free:
§ 15.9.8

15.9.15 pdir_dump: dump the contents of a page directory and all its page tables

SYNOPSIS

#include <oskit/x86/base_paging.h>

void pdir_dump(oskit_addr_t pdir_pa);

DESCRIPTION

This function is primarily intended for debugging purposes: it dumps the mappings described by the specified page directory and all associated page tables in a reasonably compact, human-readable form, using printf. 4MB superpage as well as 4KB page mappings are handled properly, and contiguous ranges of identical mappings referring to successive physical pages or superpages are collapsed into a single line for display purposes. The permissions and other page directory/page table entry flags are expanded out as human-readable flag names.

PARAMETERS
pdir_pa:
Physical address of the page directory describing the linear address space to dump.
DEPENDENCIES
ptab_dump:
§ 15.9.16
printf:
§ 14.6
phystokv:
§ 15.6.2

15.9.16 ptab_dump: dump the contents of a page table

SYNOPSIS

#include <oskit/x86/base_paging.h>

void ptab_dump(oskit_addr_t ptab_pa, oskit_addr_t base_la);

DESCRIPTION

This is primarily a helper function for pdir_dump, but it can also be used independently, to dump the contents of an individual page table. For output purposes, the page table is assumed to reside at base_la in “some” linear address space: in other words, this parameter provides the topmost ten bits in the linear addresses dumped by this routine. Contiguous ranges of identical mappings referring to successive physical pages are collapsed into a single line for display purposes. The permissions and other page directory/page table entry flags are expanded out as human-readable flag names.

PARAMETERS
pdir_pa:
Physical address of the page table to dump.
base_la:
Linear address at which this page table resides, for purposes of displaying linear source addresses. Must be 4MB aligned.
DEPENDENCIES
printf:
§ 14.6
phystokv:
§ 15.6.2

15.10 (X86) Base Environment: Protected-mode entry and exit

15.11 (X86 PC) Base Environment: Physical Memory Management

The physical memory address space on PCs is divided into distinct regions which have certain attributes. The lowest 1MB of physical memory is “special” in that only it can be accessed from real mode. The lowest 16MB of physical memory is special in that only it can be accessed by the built-in DMA controller. On some PCs, there may be an additional boundary imposed by the motherboard. Memory above this boundary (e.g., 64MB) may not be cacheable by the L2 cache. The base environment uses the OSKit LMM library 25 to accommodate these differences. Physical memory is managed by a single LMM malloc_lmm with separate regions for each “type” of memory. Hence, LMM allocation requests can be made with appropriate flag values to obtain memory with the desired characteristics.

15.11.1 phys_lmm.h: Physical memory management for PCs

SYNOPSIS

#include <oskit/x86/pc/phys_lmm.h>

DESCRIPTION

There are three priority values assigned to regions of physical memory as they are made available at boot time (via lmm_add_region). In increasing order of priority (i.e., increasing preference for allocation):

LMM_PRI_1MB:
For physical memory below 1MB.
LMM_PRI_16MB:
For physical memory below 16MB.
LMM_PRI_HIGH:
For physical memory above 16MB.

These priorities prevent the simple memory allocation interfaces from handing out the more precious low-address memory. To enable savvy applications to explicitly allocate memory of a given type, the following flag values are assigned at boot time and can be passed to LMM allocation routines:

LMMF_1MB:
Set on memory below 1MB; i.e., the LMM_PRI_1MB region.
LMMF_16MB:
Set on memory below 16MB; i.e., the LMM_PRI_1MB and LMM_PRI_16MB regions.

Thus, if neither flag is set, memory can be allocated from any of the three regions with preference given to LMM_PRI_HIGH, followed by LMM_PRI_16MB and LMM_PRI_1MB. If just LMMF_16MB is set, memory can be allocated from either LMM_PRI_16MB or LMM_PRI_1MB in that order. If both flags are set, memory can only be allocated from the LMM_PRI_1MB region.

15.11.2 phys_mem_max: Highest physical memory address

SYNOPSIS

#include <oskit/x86/pc/phys_lmm.h>

extern oskit_addr_t phys_mem_max;

DESCRIPTION

This variable records the highest physical memory address; i.e., the end address of the highest free block added to malloc_lmm. This is the highest address that the kernel should ever have to deal with.

Not all addresses between 0 and phys_mem_max are necessarily available for allocation, there may be holes in the available physical memory space.

15.11.3 phys_lmm_init: Initialize kernel physical memory LMM

SYNOPSIS

#include <oskit/x86/pc/phys_lmm.h>

void phys_lmm_init(void);

DESCRIPTION

This routine sets up malloc_lmm with three physical memory regions, one for each of the memory types described in phys_lmm.h. No actual memory is added to those regions. You can then call phys_lmm_add() to add memory to those regions. In the base environment, base_multiboot_init_mem handles this chore.

DEPENDENCIES
malloc_lmm:
§ 14.5.1
lmm_add_region:
§ 25.6.2
phystokv:
§ 15.6.2

15.11.4 phys_lmm_add: Add memory to the kernel physical memory LMM

SYNOPSIS

#include <oskit/x86/pc/phys_lmm.h>

void phys_lmm_add(oskit_addr_t min_pa, oskit_size_t size);

DESCRIPTION

Add a chunk of physical memory to the appropriate region(s) on the malloc_lmm. The provided memory block may be arbitrarily aligned and may cross region boundaries (e.g., the 16MB boundary); it will be shrunken and split apart as necessary. If the address of the end of the block is greater than the current value in phys_max_mem, this address is recorded.

Note that phys_lmm_add takes a physical address, not a virtual address as the underlying LMM routines do. This routine will perform the conversion as needed with phystokv.

PARAMETERS
min_pa:
Physical address of the start of the chunk being added.
size:
Size in bytes of the chunk being added.
DEPENDENCIES
malloc_lmm:
§ 14.5.1
lmm_add_free:
§ 25.6.3
phystokv:
§ 15.6.2
phys_mem_max:
§ 15.11.2

15.12 (X86 PC) Base Environment: Interrupt Support

In the base environment, each hardware interrupt vector in the processor IDT points to a small assembly language stub that saves a standard trap frame (15.8.1), disables and acknowledges the hardware interrupt, and calls a designated high-level handler routine specified in the base_irq_handlers table (15.12.2). Initially, all the entries in this table point to base_irq_default_handler (15.12.5). Custom interrupt handlers can be installed by changing the appropriate entry in the table. The default action for all interrupts can be changed by overriding base_irq_default_handler.

The base environment also includes support for a single “software interrupt.” A software interrupt is delivered after all pending hardware interrupts have been processed but before returning from the interrupt context. A software interrupt can be posted at any time with base_irq_softint_request (15.12.7) but will only be triggered upon return from a hardware interrupt; i.e., processing of a software interrupt requested from a non-interrupt context is deferred until a hardware interrupt occurs. The software interrupt handler is contained in the function pointer base_irq_softint_handler (15.12.8), and initially points to the function base_irq_softint_default_handler, but can be replaced by a custom version provided by the kernel.

15.12.1 base_irq.h: Hardware interrupt definitions for standard PCs

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

DESCRIPTION
BASE_IRQ_COUNT:
Number of interrupt request lines.
BASE_IRQ_MASTER_BASE:
Default location in the IDT for programming the PIC.
BASE_IRQ_SLAVE_BASE:
Default location in the IDT for programming the PIC.
irq_master_base:
Variable storing the current master PIC interrupt vector base.
irq_slave_base:
Variable storing the current slave PIC interrupt vector base.
fill_irq_gate(irq_num, entry, selector, access):
Fill the base_idt descriptor for the indicated IRQ with an interrupt gate containing the given entry, selector and access information.

15.12.2 base_irq_handlers: Array of handler routines for hardware interrupts

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

void (*base_irq_handlers[BASE_IRQ_COUNT]) (struct trap_state *ts );

DESCRIPTION

Contains a function pointer for every hardware interrupt vector. By default, all entries in this table point to base_irq_default_handler. Custom interrupt handlers can be installed by changing the appropriate table entry.

Interrupt handlers can freely examine and modify the processor state (15.8.1) of the interrupted activity, e.g., to implement threads and preemption. On entry, the processor’s IDT interrupt vector number is in ts->trapno and the hardware IRQ number that caused the interrupt is in ts->err.

DEPENDENCIES
base_irq_default_handler:
§ 15.12.5

15.12.3 base_irq_init: Initialize hardware interrupts

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

void base_irq_init(void);

DESCRIPTION

Initializes the system to properly handle hardware interrupts. It loads the appropriate entries in the base IDT (15.7.4) with the gate descriptor information from base_irq_inittab, programs the PICs to the standard vector base addresses (see Section 15.12.1), and disables all interrupt request lines.

Processor interrupts must be disabled when this routine is called.

NOTE: This routine no longer enables interrupts!

DEPENDENCIES
base_idt:
§ 15.7.4
base_irq_inittab:
§ 15.12.4
gate_init:
§ 15.5.10
pic_init:
§ 15.4.2
pic_disable_all:
§ 15.4.2
irq_master_base:
§ 15.12.1
irq_slave_base:
§ 15.12.1

15.12.4 base_irq_inittab: initialization table for default interrupt entrypoints

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

extern struct gate_init_entry base_irq_inittab[];

DESCRIPTION

This gate initialization table (15.3.9) encapsulates the base environment’s default interrupt entrypoint code. This module provides IDT entrypoints for all the standard PC hardware interrupt vectors; each entrypoint pushes a standard state frame on the stack (15.8.1), disables and acknowledges the hardware interrupt, and then calls the C handler function pointed to by the appropriate entry of the base_irq_handlers array (15.12.2). Upon return from the handler, the interrupt code checks for a pending software interrupt and dispatches to function pointer contained in base_irq_softint_handler.

DEPENDENCIES
base_irq_handlers:
§ 15.12.2
base_irq_nest:
§ 15.12.6
base_irq_softint_handler:
§ 15.12.8

15.12.5 base_irq_default_handler: default IRQ handler for unexpected interrupts

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

void base_irq_default_handler(struct trap_state *state);

DESCRIPTION

This routine is the default handler for all interrupts in the base environment. It simply displays a warning message and returns.

It is expected that the client OS will override this default behavior for all interrupts it cares about, leaving this routine to be called only for unexpected interrupts.

PARAMETERS
state:
A pointer to the processor state at the time of the interrupt.
DEPENDENCIES
struct trap_state:
§ 15.8.1
printf:
§ 14.6

15.12.6 base_irq_nest: interrupt nesting counter and software interrupt flag

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

extern unsigned char base_irq_nest;

DESCRIPTION

Hardware interrupt nesting counter, used to ensure that the software interrupt handler isn’t called until all outstanding hardware interrupts have been processed. In addition, this variable also acts as the software interrupt pending flag: if the high bit is clear, a software interrupt is pending.

15.12.7 base_irq_softint_request: request a software interrupt

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

void base_irq_softint_request(void);

DESCRIPTION

This routine requests a software interrupt and is typically called from a hardware interrupt handler to schedule lower priority processing.

After requesting a software interrupt, the function pointed to by base_irq_softint_handler will be called when all hardware interrupt handlers have completed processing and base_irq_nest is zero. If an interrupt is scheduled from a non-interrupt context, the handler will not be called until the next hardware interrupt occurs and has been processed.

Only a single software interrupt may be pending at a time.

DEPENDENCIES
base_irq_nest:
§ 15.12.6

15.12.8 base_irq_softint_handler: Pointer to handler for software interrupts

SYNOPSIS

#include <oskit/x86/pc/base_irq.h>

void (*base_irq_softint_handler) (struct trap_state *ts );

DESCRIPTION

Pointer to a software interrupt handler called by the interrupt entry/exit stub code when a software interrupt has been requested and needs to be run. The default value of this pointer is the function base_irq_softint_default_handler, which simply returns; to use software interrupts, the kernel must override it.

The handler is free to examine and modify the processor state in state.

PARAMETERS
state:
A pointer to the processor state at the time of the interrupt.
DEPENDENCIES
struct trap_state:
§ 15.8.1

15.13 (X86 PC) Base Environment: Console Support

The base console environment allows “console” input and output using either the display and keyboard or a serial line. Additionally it allows a remote kernel debugging with GDB over a serial line. Selection of the display or serial port as console and which serial ports to use for the console and GDB are controlled by command line options or environment variables as described in this section.

The base console environment uses a simple polled interface for serial port input and output as well as for keyboard input. The video display output interface is a simple, dumb text terminal. See the appropriate sections for details.

In the base interface, all input via stdin (e.g., getchar, scanf) and all output via stdout or stderr (e.g., putchar, printf) use the console.

For simplicity, a set of vanilla console functions is provided that direct input and output to/from the appropriate device. For example, console_putchar will invoke com_cons_putchar if the console device is a serial port, or direct_cons_putchar if the console device is a display. Other vanilla console I/O routines include console_getchar, console_puts, and console_putbytes (a raw block output function that does not append a newline). All behave as expected. These routines are provided to so that higher level I/O code does not need to be concerned with which type of device is currenty the console. Both the minimal C library (section § 14) and the FreeBSD C library (Section 21) take advantage of this redirection.

15.13.1 base_console.h: definitions for base console support

SYNOPSIS

#include <oskit/x86/pc/base_console.h>

DESCRIPTION

The following variable are used in the base_console code:

serial_console:
Set non-zero if a serial port is being used as the console. Default value is zero, but may be turned on by either a command line option or the CONS_COM environment variable.
cons_com_port:
If serial_console is non-zero, this variable indicates the COM port to use for console input and output. Default value is 1, but may be changed by setting the CONS_COM environment variable. Possible values are 1, 2, 3 or 4.
enable_gdb:
If non-zero, enables the GDB trap handler; i.e., makes remote debugging possible. The default value is zero.
gdb_com_port:
If enable_gdb is non-zero, this variable indicates the COM port to use for remote GDB interaction. The default value is 1 (the console and remote GDB can share the same serial port, but do not have to). Possible values are 1, 2, 3 or 4.

Refer to Section 15.13.2 for more information on command line options and environment variables. See Section 15.17 for more on remote GDB.

15.13.2 base_console_init: Initialize the base console

SYNOPSIS

#include <oskit/x86/base_console.h>

void base_console_init(int argc, char **argv);

DESCRIPTION

This function parses the multiboot command line and optionally initializes the serial lines.

Command line options recognized by the base_console code include:

Environment variables recognized include:

CONS_COM:
Serial port number (1, 2, 3 or 4) to use as the console. Sets cons_com_port to this value and sets serial_console non-zero.
GDB_COM:
Serial port number (1, 2, 3 or 4) to use as the remote GDB interface. Sets gdb_com_port to this value and sets enable_gdb non-zero.
BAUD:
Baud rate to use for both the console and GDB serial ports. Any of the standard values in termios.h (section 14.4.31) are valid.
PARAMETERS
argc:
Count of command line arguments in argv.
argv:
Vector of command line arguments.
DEPENDENCIES
getenv:
§ 14.4.17
atoi:
§ 14.4.17
base_cooked_termios:
§ 15.13.3
base_raw_termios:
§ 15.13.4
strcmp:
§ 14.4.18
printf:
§ 14.6
base_gdt_load:
§ 15.7.3
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5
com_cons_init:
§ 15.13.8
com_cons_flush:
§ 15.13.11
com_cons_getchar:
§ 15.13.9
com_cons_putchar:
§ 15.13.10
gdb_pc_com_init:
§ 15.18.9
gdb_serial_getchar:
§ 15.18.4
gdb_serial_putchar:
§ 15.18.5
gdb_serial_puts:
§ 15.18.6
gdb_serial_exit:
§ 15.18.3
direct_cons_getchar:
§ 15.13.5
direct_cons_putchar:
§ 15.13.6

15.13.3 base_cooked_termios: Default termios setting for cooked-mode console

SYNOPSIS

#include <termios.h>

extern struct termios base_cooked_termios;

DESCRIPTION

A POSIX termios structures with values suitable for a basic cooked-mode console tty. Used in the base environment when initializing a serial-port console in com_cons_init.

DEPENDENCIES
termios.h:
§ 14.4.31

15.13.4 base_raw_termios: Default termios setting for raw-mode console

SYNOPSIS

#include <termios.h>

extern struct termios base_raw_termios;

DESCRIPTION

A POSIX termios structures with values suitable for a basic raw-mode console tty. Used in the base environment when initializing a serial-port for remote GDB debuging in com_cons_init.

DEPENDENCIES
termios.h:
§ 14.4.31

15.13.5 direct_cons_getchar: wait for and read a character from the keyboard

SYNOPSIS

#include <oskit/x86/pc/direct_console.h>

int direct_cons_getchar(void);

DESCRIPTION

Read a character from the PC keyboard. If none is available, this routine loops polling the keyboard status register until a character is available.

Supports only a subset of the available key presses. In particular, only the shifted and unshifted printable ASCII characters along with Escape, Backspace, Tab, and Carriage return. It does not support the remaining control characters or multi-character (function) keys.

RETURNS

Returns the character read.

DEPENDENCIES
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5
inb:
§ 15.3.7

15.13.6 direct_cons_putchar: write a character to the video console

SYNOPSIS

#include <oskit/x86/pc/direct_console.h>

void direct_cons_putchar(unsigned char c);

DESCRIPTION

Outputs the indicated character on the video console. Handles “\n” (newline), “\r” (carriage return), “\b” (backspace), and “\t” (tab) in addition to printable ASCII characters. Tabs are expanded to spaces (with stops are every 8 columns) and lines are automatically wrapped at 80 characters. Newline implies a carriage return.

PARAMETERS
c:
Character to be printed.
DEPENDENCIES
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5
phystokv:
§ 15.6.2
outb_p:
§ 15.3.7
memcpy:
§ 14.4.18

15.13.7 direct_cons_trygetchar: read an available character from the keyboard

SYNOPSIS

#include <oskit/x86/pc/direct_console.h>

int direct_cons_trygetchar(void);

DESCRIPTION

Quick poll for an available input character. Returns a character or -1 if no character was available.

Due to the large delay between when a character is typed and when the scan code arrives at the keyboard controller (4-5 ms), there are a variety of situations in which this routine may return -1 even though a character has been typed:

In other words, this routine never delays in an attempt to wait for the next scan code to arrive when one is not currently available. Hence the utility of this routine is questionable.

RETURNS

Returns a character if available, -1 otherwise.

DEPENDENCIES
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5
inb:
§ 15.3.7

15.13.8 com_cons_init: initialize a serial port

SYNOPSIS

#include <oskit/x86/pc/com_cons.h>

void com_cons_init(int port, struct termios *com_params);

DESCRIPTION

This routine must be called once to initialize a COM port for use by other com_cons routines. The supplied termios structure indicates the baud rate and other settings. If com_params is zero, a default of 9600 baud, 8 bit characters, no parity, and 1 stop bit is used.

PARAMETERS
port:
COM port to initialize. Must be 1, 2, 3 or 4.
com_params:
Pointer to a termios structures with the tty settings to use.
DEPENDENCIES
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5
base_cooked_termios:
§ 15.13.3
inb:
§ 15.3.7
outb:
§ 15.3.7

15.13.9 com_cons_getchar: wait for and read a character from a serial port

SYNOPSIS

#include <oskit/x86/pc/com_cons.h>

int com_cons_getchar(int port);

DESCRIPTION

Read a character from the indicated serial port. If none is available, this routine loops polling the status register until a character is available.

PARAMETERS
port:
COM port to read from. Must be 1, 2, 3 or 4.
RETURNS

Returns the character read.

DEPENDENCIES
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5
inb:
§ 15.3.7

15.13.10 com_cons_putchar: write a character to a serial port

SYNOPSIS

#include <oskit/x86/pc/com_cons.h>

void com_cons_putchar(int port, int ch);

DESCRIPTION

Outputs the indicated character on the specified serial port.

PARAMETERS
port:
COM port to write to. Must be 1, 2, 3 or 4.
ch:
Character to be printed.
DEPENDENCIES
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5
inb:
§ 15.3.7
outb:
§ 15.3.7

15.13.11 com_cons_flush: delay until all output is flushed on a serial line

SYNOPSIS

#include <oskit/x86/pc/com_cons.h>

void com_cons_flush(int port);

DESCRIPTION

Waits until the transmit FIFOs are empty on the serial port specified. This is useful as it allows the programmer to know that the message sent out has been received. This is necessary before resetting the UART or changing settings if it is desirable for any data already “sent” to actually be transmitted.

PARAMETERS
port:
COM port to flush. Must be 1, 2, 3 or 4.
DEPENDENCIES
inb:
§ 15.3.7

15.13.12 com_cons_enable_receive_interrupt: enable receive interrupts on a serial port

SYNOPSIS

#include <oskit/x86/pc/com_cons.h>

void com_cons_enable_receive_interrupt(int port);

DESCRIPTION

Special function to enable receive character interrupts on a serial port.

Since the base COM console operates by polling, there is no need to handle serial interrupts in order to do basic I/O. However, if you want to be notified up when a character is received, call this function immediately after com_cons_init, and make sure the appropriate IDT entry is initialized properly.

For example, the serial debugging code for the PC COM port uses this so that the program can be woken up when the user presses the interrupt character (^C) from the remote debugger.

PARAMETERS
port:
COM port to enable receive interrupts on. Must be 1, 2, 3 or 4.
DEPENDENCIES
inb:
§ 15.3.7

15.14 (X86 PC) MultiBoot Startup

MultiBoot is a standardized interface between boot loaders and 32-bit operating systems on x86 PC platforms, which attempts to solve the traditional problem that every single operating system tends to come with its own boot loader or set of boot loaders which are completely incompatible with boot loaders written for any other operating system. The MultiBoot standard allows any MultiBoot-compliant operating system to be loaded from any MultiBoot-supporting boot loader. MultiBoot is also designed to provide advanced features needed by many modern operating systems, such as direct 32-bit protected-mode startup, and support for boot modules, which are arbitrary files loaded by the boot loader into physical memory along with the kernel and passed to the kernel on startup. These boot modules may be dynamically loadable device drivers, application program executables, files on an initial file system, or anything else the OS may need before it has full device access. The MultiBoot standard is already supported by several boot loaders and operating systems, and is gradually becoming more widespread. For details on the MultiBoot standard see Section 15.14.12.

The MultiBoot standard is separate from and largely independent of the OSKit. However, if MultiBoot is used, the toolkit can leverage it to provide a powerful, flexible, and extremely convenient method of booting custom operating systems that use the OSKit. The toolkit provides startup code which allows MultiBoot-compliant OS kernels to be built easily, and which handles the details of finding and managing physical memory on startup, interpreting the command line passed by the boot loader, finding and using boot modules, etc. If you use the OSKit’s MultiBoot startup support, your kernel automatically inherits a complete, full-featured 32-bit protected-mode startup environment and the ability to use various existing boot loaders, without being constrained by the limitations of traditional OS-specific boot loaders.

15.14.1 Startup code organization

The MultiBoot startup code in the OSKit has two components. The first component is contained in the object file multiboot.o, installed by the toolkit in the prefix /lib/oskit/ directory. This object file contains the actual MultiBoot header and entrypoint; it must be linked into the kernel as the very first object file, so that its contents will be at the very beginning of the resulting executable. (This object file takes the place of the crt0.o or crt1.o normally used when linking ordinary applications in a Unix-like system.) The second component is contained in the libkern.a library; it contains the rest of the MultiBoot startup code as well as various utility routines for the use of the client OS.

XXX diagram of MultiBoot kernel executable image

The toolkit’s MultiBoot startup code will work when using either ELF or a.out format. ELF is the format recommended for kernel images by the MultiBoot standard; however, the a.out format is also supported through the use of some special header information embedded in the multiboot.o code linked at the very beginning of the kernel’s text segment. This information allows the MultiBoot boot loader to determine the location and sizes of the kernel’s text, data, and bss sections in the kernel executable without knowing the details of the particular a.out flavor in use (e.g., Linux, NetBSD, FreeBSD, Mach, VSTa, etc.), all of which are otherwise mutually incompatible.

15.14.2 Startup sequence

After the MultiBoot boot loader loads the kernel executable image, it searches through the beginning of the image for the MultiBoot header which provides important information about the OS being loaded. The boot loader performs its activities, then shuts itself down and jumps to the OS kernel entrypoint defined in the kernel’s MultiBoot header. In one processor register the boot loader passes to the kernel the address of a MultiBoot information structure, containing various information passed from the boot loader to the OS, organized in a standardized format defined by the MultiBoot specification.

In the OSKit’s MultiBoot startup code, the kernel entrypoint is a short code fragment in multiboot.o which sets up the initial stack and performs other minimal initialization so that ordinary 32-bit C code can be run safely.3 This code fragment then calls the C function multiboot_main, with a pointer to the MultiBoot information structure as its argument. Normally, the multiboot_main function comes from libkern.a; it performs other high-level initialization to create a convenient, stable 32-bit environment, and then calls the familiar main routine, which the client OS must provide.

15.14.3 Memory model

Once the OS kernel receives control in its main routine, the processor has been set up in the base environment defined earlier in Section 15.6. The base_gdt, base_idt, and base_tss have been set up and activated, so that segmentation operations work and traps can be handled. Paging is disabled, and all kernel code and data segment descriptors are set up with an offset of zero, so that virtual addresses, linear addresses, and physical addresses are all the same. The client OS is free to change this memory layout later, e.g., by enabling paging and reorganizing the linear address space as described in Section 15.6.3.

As part of the initialization performed by multiboot_main, the OSKit’s MultiBoot startup code uses information passed to the OS by the boot loader, describing the location and amount of physical memory available, to set up the malloc_lmm memory pool (see Section 14.5.1). This allows the OS kernel to allocate and manage physical memory using the normal C-language memory allocation mechanisms, as well as directly using the underlying LMM memory manager library functions. The physical memory placed on the malloc_lmm pool during initialization is guaranteed not to contain any of the data structures passed by the boot loader which the OS may need to use, such as the command line or the boot modules; this way, the kernel can freely allocate and use memory right from the start without worrying about accidentally “stepping on” boot loader data that it will need to access later on. In addition, the physical memory placed on the malloc_lmm is divided into the three separate regions defined in phys_lmm.h (see Section 15.11.1): one for low memory below 1MB, one for “DMA” memory below 16MB, and one for all physical memory above this line. This division allows the kernel to allocate “special” memory when needed for device access or for calls to real-mode BIOS routines, simply by specifying the appropriate flags in the LMM allocation calls.

15.14.4 Command-line arguments

The MultiBoot specification allows an arbitrary ASCII string to be passed from the boot loader to the OS as a “command line” for the OS to interpret as it sees fit. As passed from the boot loader to the OS, this is a single null-terminated ASCII string. However, the default MultiBoot initialization code provided by the OSKit performs some preprocessing of the command line before the actual OS receives control in its main routine. In particular, it parses the single command line string into an array of individual argument strings so that the arguments can be passed to the OS through the normal C-language argc/argv parameters to main. In addition, any command-line arguments containing an equals sign (‘=’) are added to the environ array rather than the argv array, effectively providing the OS with a minimal initial environment that can be specified by the user (through the boot loader) and examined by the OS using the normal getenv mechanism (see Section 14.4.17).

Note that this command-line preprocessing mechanism matches the kernel command-line conventions established by Linux, although it provides more convenience and flexibility to the OS by providing this information to the OS through standard C-language facilities, and by not restricting the “environment variables” to be comma-separated lists of numeric constants, as Linux does. This mechanism also provides much more flexibility than traditional BSD/Mach command-line mechanisms, in which the boot loader itself does most of the command-line parsing, and basically only passes a single fixed “flags” word to the OS.

15.14.5 Linking MultiBoot kernels

Since MultiBoot kernels initially run in physical memory, with paging disabled and segmentation effectively “neutralized,” the kernel must be linked at an address within the range of physical memory present on typical PCs. Normally the best place to link the kernel is at 0x100000, or 1MB, which is the beginning of extended memory just beyond the real-mode ROM BIOS. Since the processor is already in 32-bit protected mode when the MultiBoot boot loader starts the OS, running above the 1MB “boundary” is not a problem. By linking at 1MB, the kernel has plenty of “room to grow,” having essentially all extended memory available to it in one contiguous chunk.

In some cases, it may be preferable to link the kernel at a lower address, below the 1MB boundary, for example if the kernel needs to run on machines without any extended memory, or if the kernel contains code that needs to run in real mode. This is also allowed by the MultiBoot standard. However, note that the kernel should generally leave at least the first 0x500 bytes of physical memory untouched, since this area contains important BIOS data structures that will be needed if the kernel ever makes calls to the BIOS, or if it wants to glean information about the machine from this area such as hard disk configuration data.

15.14.6 multiboot.h: Definitions of MultiBoot structures and constants

SYNOPSIS

#include <oskit/x86/multiboot.h>

DESCRIPTION

This header file is not specific to the MultiBoot startup code provided by the OSKit; it merely contains generic symbolic structure and constant definitions corresponding to the data structures specified in the MultiBoot specification. The following C structures are defined:

struct multiboot_header:
Defines the MultiBoot header structure which is located near the beginning of all MultiBoot-compliant kernel executables.
struct multiboot_info:
Defines the general information structure passed from the boot loader to the OS when control is passed to the OS.
struct multiboot_module:
One of the elements of the multiboot_info structure is an optional array of boot modules which the boot loader may provide; each element of the boot module array is reflected by this structure.
struct multiboot_addr_range:
Another optional component of the multiboot_info structure is a pointer to an array of address range descriptors, described by this structure, which define the layout of physical memory on the machine. (XXX name mismatch.)

For more information on these structures and the associated constants, see the multiboot.h header file and the MultiBoot specification.

XXX should move this to x86/pc/multiboot.h?

15.14.7 boot_info: MultiBoot information structure

SYNOPSIS

#include <oskit/x86/pc/base_multiboot.h>

extern struct multiboot_info boot_info;

DESCRIPTION

The first thing that multiboot_main does on entry from the minimal startup code in multiboot.o is copy the MultiBoot information structure passed by the boot loader into a global variable in the kernel’s bss segment. Copying the information structure this way allows it to be accessed more conveniently by the kernel, and makes it unnecessary for the memory initialization code (base_multiboot_init_mem; see Section 15.14.9) to carefully “step over” the information structure when determining what physical memory is available for general use.

After the OS has received control in its main routine, it is free to examine the boot_info structure and use it to locate other data passed by the boot loader, such as the boot modules. The client OS must not attempt to access the original copy of the information structure passed by the boot loader, since that copy of the structure may be overwritten as memory is dynamically allocated and used. However, this should not be a problem, since a pointer to the original copy of the multiboot_info structure is never even passed to the OS by the MultiBoot startup code; it is only accessible to the OS if it overrides the multiboot_main function.

15.14.8 multiboot_main: general MultiBoot initialization

SYNOPSIS

#include <oskit/x86/pc/base_multiboot.h>

void multiboot_main(oskit_addr_t boot_info_pa);

DESCRIPTION

This is the first C-language function to run, invoked by the minimal startup code fragment in multiboot.o. The default implementation merely copies the MultiBoot information structure passed by the boot loader into the global variable boot_info (see Section 15.14.7), and then calls the following routines to set up the base environment and start the OS:

base_cpu_setup:
Initializes the base GDT, IDT, and TSS, so that the processor’s segmentation facilities can be used and processor traps can be handled.
base_multiboot_init_mem:
Finds all physical memory available for general use and adds it to the malloc_lmm so that OS code can allocate memory dynamically.
base_multiboot_init_cmdline:
Performs basic preprocessing on the command line string passed by the boot loader, splitting it up into standard C argument and environment variable lists.
main:
This call is what invokes the actual OS code, using standard C-language startup conventions.
exit:
As per C language conventions, if the main routine ever returns, exit is called immediately, using the return value from main as the exit code.

If the client OS does not wish some or all of the above to be performed, it may override the multiboot_main function with a version that does what it needs, or, alternatively, it may instead override the specific functions of interest called by multiboot_main.

PARAMETERS
boot_info_pa:
The physical address of the MultiBoot information structure as created and passed by the boot loader.
RETURNS

This function had better never return.

DEPENDENCIES
phystokv:
§ 15.6.2
boot_info:
§ 15.14.7
base_cpu_setup:
§ 15.6.3
base_multiboot_init_mem:
§ 15.14.9
base_multiboot_init_cmdline:
§ 15.14.10
exit:
§ 14.8.1

15.14.9 base_multiboot_init_mem: physical memory initialization

SYNOPSIS

#include <oskit/x86/pc/base_multiboot.h>

void base_multiboot_init_mem(void);

DESCRIPTION

This function finds all physical memory available for general use and adds it to the malloc_lmm pool, as described in Section 15.14.3. It is normally called automatically during initialization by multiboot_main (see Section 15.14.8).

This function uses the lower and upper memory size fields in the MultiBoot information structure to determine the total amount of physical memory available; it then adds all of this memory to the malloc_lmm pool except for the following “special” areas:

This function uses phys_lmm_init to initialize the malloc_lmm, and phys_lmm_add to add available physical memory to it (see Section 15.11.1); as a consequence, this causes the physical memory found to be split up automatically according to the three main functional “classes” of PC memory: low 1MB memory accessible to real-mode software, low 16MB memory accessible to the built-in DMA controller, and “all other” memory. This division allows the OS to allocate “special” memory when needed for device access or for calls to real-mode BIOS routines, simply by specifying the appropriate flags in the LMM allocation calls.

XXX currently doesn’t use the memory range array.

DEPENDENCIES
phystokv:
§ 15.6.2
boot_info:
§ 15.14.7
phys_lmm_init:
§ 15.11.3
phys_lmm_add:
§ 15.11.4
strlen:
§ 14.4.18

15.14.10 base_multiboot_init_cmdline: command-line preprocessing

SYNOPSIS

#include <oskit/x86/pc/base_multiboot.h>

void base_multiboot_init_cmdline(void);

DESCRIPTION

This function breaks up the kernel command line string passed by the boot loader into independent C-language-compatible argument strings. Option strings are separated by any normal whitespace characters (spaces, tabs, newlines, etc.). In addition, strings containing an equals sign (‘=’) are added to the environ array rather than the argv array, effectively providing the OS with a minimal initial environment that can be specified by the user (through the boot loader) and examined by the OS using the normal getenv mechanism (see Section 14.4.17).

XXX example.

XXX currently no quoting support.

XXX currently just uses “kernel” as argv[0].

DEPENDENCIES
phystokv:
§ 15.6.2
strlen:
§ 14.4.18
strtok:
§ 14.4.18
malloc:
§ 14.5.2
memcpy:
§ 14.4.18
panic:
§ 14.8.3

15.14.11 base_multiboot_find: find a MultiBoot boot module by name

SYNOPSIS

#include <oskit/x86/pc/base_multiboot.h>

struct multiboot_module *base_multiboot_find(const char *string);

DESCRIPTION

This is not an initialization function, but rather a utility function for the use of the client OS. Given a particular string, it searches the array of boot modules passed by the boot loader for a boot module with a matching string. This function can be easily used by the OS to locate specific boot modules by name.

If multiple boot modules have matching strings, then the first one found is returned. If any boot modules have no strings attached (no pun intended), then those boot modules will never be “found” by this function, although they can still be found by hunting through the boot module array manually.

PARAMETERS
string:
The string to match against the strings attached to the boot modules.
RETURNS

If successful, returns a pointer to the multiboot_module entry matched; from this structure, the actual boot module data can be found using the mod_start and mod_end elements, which contain the start and ending physical addresses of the boot module data, respectively.

If no matching boot module can be found, this function returns NULL.

DEPENDENCIES
phystokv:
§ 15.6.2
boot_info:
§ 15.14.7
strcmp:
§ 14.4.18

15.14.12 Multiboot Specification

                   Excerpt from "MultiBoot Standard"
                              Version 0.6
 
                             March 29, 1996
      _________________________________________________________________
 
 (This contains the essential MultiBoot specification, omitting background
 and related info found in ftp://flux.cs.utah.edu/flux/multiboot/.)
 
 Contents
      * Terminology
      * Scope and Requirements
      * Details
      * Authors
 
      The following items are not part of the standards document,
      but are included for prospective OS and bootloader writers.
      * Example OS Code
      * Example Bootloader Code
 
      _________________________________________________________________
 
 Terminology
 
    Throughout this document, the term "boot loader" means whatever
    program or set of programs loads the image of the final operating
    system to be run on the machine. The boot loader may itself consist of
    several stages, but that is an implementation detail not relevant to
    this standard. Only the "final" stage of the boot loader - the stage
    that eventually transfers control to the OS - needs to follow the
    rules specified in this document in order to be "MultiBoot compliant";
    earlier boot loader stages can be designed in whatever way is most
    convenient.
 
    The term "OS image" is used to refer to the initial binary image that
    the boot loader loads into memory and transfers control to to start
    the OS. The OS image is typically an executable containing the OS
    kernel.
 
    The term "boot module" refers to other auxiliary files that the boot
    loader loads into memory along with the OS image, but does not
    interpret in any way other than passing their locations to the OS when
    it is invoked.
 
      _________________________________________________________________
 
 Scope and Requirements
 
   Architectures
 
    This standard is primarily targetted at PC's, since they are the most
                                                                                         
                                                                                         
    common and have the largest variety of OS's and boot loaders. However,
    to the extent that certain other architectures may need a boot
    standard and do not have one already, a variation of this standard,
    stripped of the x86-specific details, could be adopted for them as
    well.
 
   Operating systems
 
    This standard is targetted toward free 32-bit operating systems that
    can be fairly easily modified to support the standard without going
    through lots of bureaucratic rigmarole. The particular free OS's that
    this standard is being primarily designed for are Linux, FreeBSD,
    NetBSD, Mach, and VSTa. It is hoped that other emerging free OS's will
    adopt it from the start, and thus immediately be able to take
    advantage of existing boot loaders. It would be nice if commercial
    operating system vendors eventually adopted this standard as well, but
    that's probably a pipe dream.
 
   Boot sources
 
    It should be possible to write compliant boot loaders that load the OS
    image from a variety of sources, including floppy disk, hard disk, and
    across a network.
 
    Disk-based boot loaders may use a variety of techniques to find the
    relevant OS image and boot module data on disk, such as by
    interpretation of specific file systems (e.g. the BSD/Mach boot
    loader), using precalculated "block lists" (e.g. LILO), loading from a
    special "boot partition" (e.g. OS/2), or even loading from within
    another operating system (e.g. the VSTa boot code, which loads from
    DOS). Similarly, network-based boot loaders could use a variety of
    network hardware and protocols.
 
    It is hoped that boot loaders will be created that support multiple
    loading mechanisms, increasing their portability, robustness, and
    user-friendliness.
 
   Boot-time configuration
 
    It is often necessary for one reason or another for the user to be
    able to provide some configuration information to the OS dynamically
    at boot time. While this standard should not dictate how this
    configuration information is obtained by the boot loader, it should
    provide a standard means for the boot loader to pass such information
    to the OS.
 
   Convenience to the OS
 
    OS images should be easy to generate. Ideally, an OS image should
    simply be an ordinary 32-bit executable file in whatever file format
    the OS normally uses. It should be possible to 'nm' or disassemble OS
    images just like normal executables. Specialized tools should not be
    needed to create OS images in a "special" file format. If this means
    shifting some work from the OS to the boot loader, that is probably
                                                                                         
                                                                                         
    appropriate, because all the memory consumed by the boot loader will
    typically be made available again after the boot process is created,
    whereas every bit of code in the OS image typically has to remain in
    memory forever. The OS should not have to worry about getting into
    32-bit mode initially, because mode switching code generally needs to
    be in the boot loader anyway in order to load OS data above the 1MB
    boundary, and forcing the OS to do this makes creation of OS images
    much more difficult.
 
    Unfortunately, there is a horrendous variety of executable file
    formats even among free Unix-like PC-based OS's - generally a
    different format for each OS. Most of the relevant free OS's use some
    variant of a.out format, but some are moving to ELF. It is highly
    desirable for boot loaders not to have to be able to interpret all the
    different types of executable file formats in existence in order to
    load the OS image - otherwise the boot loader effectively becomes
    OS-specific again.
 
    This standard adopts a compromise solution to this problem. MultiBoot
    compliant boot images always either (a) are in ELF format, or (b)
    contain a "magic MultiBoot header", described below, which allows the
    boot loader to load the image without having to understand numerous
    a.out variants or other executable formats. This magic header does not
    need to be at the very beginning of the executable file, so kernel
    images can still conform to the local a.out format variant in addition
    to being MultiBoot compliant.
 
   Boot modules
 
    Many modern operating system kernels, such as those of VSTa and Mach,
    do not by themselves contain enough mechanism to get the system fully
    operational: they require the presence of additional software modules
    at boot time in order to access devices, mount file systems, etc.
    While these additional modules could be embedded in the main OS image
    along with the kernel itself, and the resulting image be split apart
    manually by the OS when it receives control, it is often more
    flexible, more space-efficient, and more convenient to the OS and user
    if the boot loader can load these additional modules independently in
    the first place.
 
    Thus, this standard should provide a standard method for a boot loader
    to indicate to the OS what auxiliary boot modules were loaded, and
    where they can be found. Boot loaders don't have to support multiple
    boot modules, but they are strongly encouraged to, because some OS's
    will be unable to boot without them.
 
      _________________________________________________________________
 
 Details
 
 There are three main aspects of the boot-loader/OS image interface this
 standard must specify:
 
      * The format of the OS image as seen by the boot loader.
                                                                                         
                                                                                         
      * The state of the machine when the boot loader starts the OS.
      * The format of the information passed by the boot loader to the OS.
 
   OS Image Format
 
 An OS image is generally just an ordinary 32-bit executable file in the
 standard format for that particular OS, except that it may be linked at a
 non-default load address to avoid loading on top of the PC's I/O region or
 other reserved areas, and of course it can't use shared libraries or other
 fancy features. Initially, only images in a.out format are supported; ELF
 support will probably later be specified in the standard.
 
 Unfortunately, the exact meaning of the text, data, bss, and entry fields of
 a.out headers tends to vary widely between different executable flavors, and it
 is sometimes very difficult to distinguish one flavor from another (e.g. Linux
 ZMAGIC executables and Mach ZMAGIC executables). Furthermore, there is no
 simple, reliable way of determining at what address in memory the text segment
 is supposed to start. Therefore, this standard requires that an additional
 header, known as a 'multiboot_header', appear somewhere near the beginning of
 the executable file. In general it should come "as early as possible", and is
 typically embedded in the beginning of the text segment after the "real"
 executable header. It _must_ be contained completely within the first 8192
 bytes of the executable file, and must be longword (32-bit) aligned. These
 rules allow the boot loader to find and synchronize with the text segment in
 the a.out file without knowing beforehand the details of the a.out variant. The
 layout of the header is as follows:
 
         +-------------------+
 0       | magic: 0x1BADB002 |   (required)
 4       | flags             |   (required)
 8       | checksum          |   (required)
         +-------------------+
 8       | header_addr       |   (present if flags[16] is set)
 12      | load_addr         |   (present if flags[16] is set)
 16      | load_end_addr     |   (present if flags[16] is set)
 20      | bss_end_addr      |   (present if flags[16] is set)
 24      | entry_addr        |   (present if flags[16] is set)
         +-------------------+
 
 All fields are in little-endian byte order, of course. The first field is the
 magic number identifying the header, which must be the hex value 0x1BADB002.
 
 The flags field specifies features that the OS image requests or requires of
 the boot loader. Bits 0-15 indicate requirements; if the boot loader sees any
 of these bits set but doesn't understand the flag or can't fulfill the
 requirements it indicates for some reason, it must notify the user and fail to
 load the OS image. Bits 16-31 indicate optional features; if any bits in this
 range are set but the boot loader doesn't understand them, it can simply ignore
 them and proceed as usual. Naturally, all as-yet-undefined bits in the flags
 word must be set to zero in OS images. This way, the flags fields serves for
 version control as well as simple feature selection.
 
 If bit 0 in the flags word is set, then all boot modules loaded along with the
 OS must be aligned on page (4KB) boundaries. Some OS's expect to be able to map
                                                                                         
                                                                                         
 the pages containing boot modules directly into a paged address space during
 startup, and thus need the boot modules to be page-aligned.
 
 If bit 1 in the flags word is set, then information on available memory via at
 least the 'mem_*' fields of the multiboot_info structure defined below must be
 included. If the bootloader is capable of passing a memory map (the 'mmap_*'
 fields) and one exists, then it must be included as well.
 
 If bit 16 in the flags word is set, then the fields at offsets 8-24 in the
 multiboot_header are valid, and the boot loader should use them instead of the
 fields in the actual executable header to calculate where to load the OS image.
 This information does not need to be provided if the kernel image is in ELF
 format, but it should be provided if the images is in a.out format or in some
 other format. Compliant boot loaders must be able to load images that either
 are in ELF format or contain the load address information embedded in the
 multiboot_header; they may also directly support other executable formats, such
 as particular a.out variants, but are not required to.
 
 All of the address fields enabled by flag bit 16 are physical addresses. The
 meaning of each is as follows:
 
      * header_addr -- Contains the address corresponding to the beginning
        of the multiboot_header - the physical memory location at which
        the magic value is supposed to be loaded. This field serves to
        "synchronize" the mapping between OS image offsets and physical
        memory addresses.
      * load_addr -- Contains the physical address of the beginning of the
        text segment. The offset in the OS image file at which to start
        loading is defined by the offset at which the header was found,
        minus (header_addr - load_addr). load_addr must be less than or
        equal to header_addr.
      * load_end_addr -- Contains the physical address of the end of the
        data segment. (load_end_addr - load_addr) specifies how much data
        to load. This implies that the text and data segments must be
        consecutive in the OS image; this is true for existing a.out
        executable formats.
      * bss_end_addr -- Contains the physical address of the end of the
        bss segment. The boot loader initializes this area to zero, and
        reserves the memory it occupies to avoid placing boot modules and
        other data relevant to the OS in that area.
      * entry -- The physical address to which the boot loader should jump
        in order to start running the OS.
 
 The checksum is a 32-bit unsigned value which, when added to the other required
 fields, must have a 32-bit unsigned sum of zero.
 
   Machine State
 
 When the boot loader invokes the 32-bit operating system, the machine must have
 the following state:
 
      * CS must be a 32-bit read/execute code segment with an offset of 0
        and a limit of 0xffffffff.
      * DS, ES, FS, GS, and SS must be a 32-bit read/write data segment
                                                                                         
                                                                                         
        with an offset of 0 and a limit of 0xffffffff.
      * The address 20 line must be usable for standard linear 32-bit
        addressing of memory (in standard PC hardware, it is wired to 0 at
        bootup, forcing addresses in the 1-2 MB range to be mapped to the
        0-1 MB range, 3-4 is mapped to 2-3, etc.).
      * Paging must be turned off.
      * The processor interrupt flag must be turned off.
      * EAX must contain the magic value 0x2BADB002; the presence of this
        value indicates to the OS that it was loaded by a
        MultiBoot-compliant boot loader (e.g. as opposed to another type
        of boot loader that the OS can also be loaded from).
      * EBX must contain the 32-bit physical address of the multiboot_info
        structure provided by the boot loader (see below).
 
 All other processor registers and flag bits are undefined. This includes, in
 particular:
 
      * ESP: the 32-bit OS must create its own stack as soon as it needs
        one.
      * GDTR: Even though the segment registers are set up as described
        above, the GDTR may be invalid, so the OS must not load any
        segment registers (even just reloading the same values!) until it
        sets up its own GDT.
      * IDTR: The OS must leave interrupts disabled until it sets up its
        own IDT.
 
 However, other machine state should be left by the boot loader in "normal
 working order", i.e. as initialized by the BIOS (or DOS, if that's what the
 boot loader runs from). In other words, the OS should be able to make BIOS
 calls and such after being loaded, as long as it does not overwrite the BIOS
 data structures before doing so. Also, the boot loader must leave the PIC
 programmed with the normal BIOS/DOS values, even if it changed them during the
 switch to 32-bit mode.
 
   Boot Information Format
 
 Upon entry to the OS, the EBX register contains the physical address of a
 'multiboot_info' data structure, through which the boot loader communicates
 vital information to the OS. The OS can use or ignore any parts of the
 structure as it chooses; all information passed by the boot loader is advisory
 only.
 
 The multiboot_info structure and its related substructures may be placed
 anywhere in memory by the boot loader (with the exception of the memory
 reserved for the kernel and boot modules, of course). It is the OS's
 responsibility to avoid overwriting this memory until it is done using it.
 
 The format of the multiboot_info structure (as defined so far) follows:
 
         +-------------------+
 0       | flags             |   (required)
         +-------------------+
 4       | mem_lower         |   (present if flags[0] is set)
 8       | mem_upper         |   (present if flags[0] is set)
                                                                                         
                                                                                         
         +-------------------+
 12      | boot_device       |   (present if flags[1] is set)
         +-------------------+
 16      | cmdline           |   (present if flags[2] is set)
         +-------------------+
 20      | mods_count        |   (present if flags[3] is set)
 24      | mods_addr         |   (present if flags[3] is set)
         +-------------------+
 28 - 40 | syms              |   (present if flags[4] or flags[5] is set)
         +-------------------+
 44      | mmap_length       |   (present if flags[6] is set)
 48      | mmap_addr         |   (present if flags[6] is set)
         +-------------------+
 
 The first longword indicates the presence and validity of other fields in the
 multiboot_info structure. All as-yet-undefined bits must be set to zero by the
 boot loader. Any set bits that the OS does not understand should be ignored.
 Thus, the flags field also functions as a version indicator, allowing the
 multiboot_info structure to be expanded in the future without breaking
 anything.
 
 If bit 0 in the multiboot_info.flags word is set, then the 'mem_*' fields are
 valid. 'mem_lower' and 'mem_upper' indicate the amount of lower and upper
 memory, respectively, in kilobytes. Lower memory starts at address 0, and upper
 memory starts at address 1 megabyte. The maximum possible value for lower
 memory is 640 kilobytes. The value returned for upper memory is maximally the
 address of the first upper memory hole minus 1 megabyte. It is not guaranteed
 to be this value.
 
 If bit 1 in the multiboot_info.flags word is set, then the 'boot_device' field
 is valid, and indicates which BIOS disk device the boot loader loaded the OS
 from. If the OS was not loaded from a BIOS disk, then this field must not be
 present (bit 3 must be clear). The OS may use this field as a hint for
 determining its own "root" device, but is not required to. The boot_device
 field is layed out in four one-byte subfields as follows:
 
         +-------+-------+-------+-------+
         | drive | part1 | part2 | part3 |
         +-------+-------+-------+-------+
 
 The first byte contains the BIOS drive number as understood by the BIOS INT
 0x13 low-level disk interface: e.g. 0x00 for the first floppy disk or 0x80 for
 the first hard disk.
 
 The three remaining bytes specify the boot partition. 'part1' specifies the
 "top-level" partition number, 'part2' specifies a "sub-partition" in the
 top-level partition, etc. Partition numbers always start from zero. Unused
 partition bytes must be set to 0xFF. For example, if the disk is partitioned
 using a simple one-level DOS partitioning scheme, then 'part1' contains the DOS
 partition number, and 'part2' and 'part3' are both zero. As another example, if
 a disk is partitioned first into DOS partitions, and then one of those DOS
 partitions is subdivided into several BSD partitions using BSD's "disklabel"
 strategy, then 'part1' contains the DOS partition number, 'part2' contains the
 BSD sub-partition within that DOS partition, and 'part3' is 0xFF.
                                                                                         
                                                                                         
 
 DOS extended partitions are indicated as partition numbers starting from 4 and
 increasing, rather than as nested sub-partitions, even though the underlying
 disk layout of extended partitions is hierarchical in nature. For example, if
 the boot loader boots from the second extended partition on a disk partitioned
 in conventional DOS style, then 'part1' will be 5, and 'part2' and 'part3' will
 both be 0xFF.
 
 If bit 2 of the flags longword is set, the 'cmdline' field is valid, and
 contains the physical address of the the command line to be passed to the
 kernel. The command line is a normal C-style null-terminated string.
 
 If bit 3 of the flags is set, then the 'mods' fields indicate to the kernel
 what boot modules were loaded along with the kernel image, and where they can
 be found. 'mods_count' contains the number of modules loaded; 'mods_addr'
 contains the physical address of the first module structure. 'mods_count' may
 be zero, indicating no boot modules were loaded, even if bit 1 of 'flags' is
 set. Each module structure is formatted as follows:
 
         +-------------------+
 0       | mod_start         |
 4       | mod_end           |
         +-------------------+
 8       | string            |
         +-------------------+
 12      | reserved (0)      |
         +-------------------+
 
 The first two fields contain the start and end addresses of the boot module
 itself. The 'string' field provides an arbitrary string to be associated with
 that particular boot module; it is a null-terminated ASCII string, just like
 the kernel command line. The 'string' field may be 0 if there is no string
 associated with the module. Typically the string might be a command line (e.g.
 if the OS treats boot modules as executable programs), or a pathname (e.g. if
 the OS treats boot modules as files in a file system), but its exact use is
 specific to the OS. The 'reserved' field must be set to 0 by the boot loader
 and ignored by the OS.
 
 NOTE: Bits 4 & 5 are mutually exclusive.
 
 If bit 4 in the multiboot_info.flags word is set, then the following fields in
 the multiboot_info structure starting at byte 28 are valid:
 
         +-------------------+
 28      | tabsize           |
 32      | strsize           |
 36      | addr              |
 40      | reserved (0)      |
         +-------------------+
 
 These indicate where the symbol table from an a.out kernel image can be found.
 'addr' is the physical address of the size (4-byte unsigned long) of an array
 of a.out-format 'nlist' structures, followed immediately by the array itself,
 then the size (4-byte unsigned long) of a set of null-terminated ASCII strings
                                                                                         
                                                                                         
 (plus sizeof(unsigned long) in this case), and finally the set of strings
 itself. 'tabsize' is equal to it's size parameter (found at the beginning of
 the symbol section), and 'strsize' is equal to it's size parameter (found at
 the beginning of the string section) of the following string table to which the
 symbol table refers. Note that 'tabsize' may be 0, indicating no symbols, even
 if bit 4 in the flags word is set.
 
 If bit 5 in the multiboot_info.flags word is set, then the following fields in
 the multiboot_info structure starting at byte 28 are valid:
 
         +-------------------+
 28      | num               |
 32      | size              |
 36      | addr              |
 40      | shndx             |
         +-------------------+
 
 These indicate where the section header table from an ELF kernel is, the size
 of each entry, number of entries, and the string table used as the index of
 names. They correspond to the 'shdr_*' entries ('shdr_num', etc.) in the
 Executable and Linkable Format (ELF) specification in the program header. All
 sections are loaded, and the physical address fields of the elf section header
 then refer to where the sections are in memory (refer to the i386 ELF
 documentation for details as to how to read the section header(s)). Note that
 'shdr_num' may be 0, indicating no symbols, even if bit 5 in the flags word is
 set.
 
 If bit 6 in the multiboot_info.flags word is set, then the 'mmap_*' fields are
 valid, and indicate the address and length of a buffer containing a memory map
 of the machine provided by the BIOS. 'mmap_addr' is the address, and
 'mmap_length' is the total size of the buffer. The buffer consists of one or
 more of the following size/structure pairs ('size' is really used for skipping
 to the next pair):
 
         +-------------------+
 -4      | size              |
         +-------------------+
 0       | BaseAddrLow       |
 4       | BaseAddrHigh      |
 8       | LengthLow         |
 12      | LengthHigh        |
 16      | Type              |
         +-------------------+
 
 where 'size' is the size of the associated structure in bytes, which can be
 greater than the minimum of 20 bytes. 'BaseAddrLow' is the lower 32 bits of the
 starting address, and 'BaseAddrHigh' is the upper 32 bits, for a total of a
 64-bit starting address. 'LengthLow' is the lower 32 bits of the size of the
 memory region in bytes, and 'LengthHigh' is the upper 32 bits, for a total of a
 64-bit length. 'Type' is the variety of address range represented, where a
 value of 1 indicates available RAM, and all other values currently indicated a
 reserved area.
 
 The map provided is guaranteed to list all standard RAM that should be
                                                                                         
                                                                                         
 available for normal use.
 
   __________________________________________________________________________
 
 Authors
 
 Bryan Ford
 Flux Research Group
 Dept. of Computer Science
 University of Utah
 Salt Lake City, UT 84112
 multiboot@flux.cs.utah.edu
 baford@cs.utah.edu
 
 Erich Stefan Boleyn
 924 S.W. 16th Ave, #202
 Portland, OR, USA  97205
 (503) 226-0741
 erich@uruk.org
 
 We would also like to thank the many other people have provided comments,
 ideas, information, and other forms of support for our work.
 
   __________________________________________________________________________
 
 Example OS code can be found in the OSKit in the "kern/x86" directory and
 in the oskit/x86/multiboot.h file.
 
   __________________________________________________________________________
 
 Example Bootloader Code (from Erich Boleyn) - The GRUB bootloader
 project (http://www.uruk.org/grub) will be fully Multiboot-compliant,
 supporting all required and optional features present in this
 standard.  A final release has not been made, but the GRUB beta release
 (which is quite stable) is available from ftp://ftp.uruk.org/public/grub/.

15.15 (X86 PC) Raw BIOS Startup

The BIOS startup code is written and functional but not yet documented or integrated into the OSKit source tree.

15.16 (X86 PC) DOS Startup

The DOS startup code is written and functional but not yet documented or integrated into the OSKit source tree.

15.17 Remote Kernel Debugging with GDB

In addition to the libkern functionality described above which is intended to facilitate implementing kernels, the library also provides complete, easy-to-use functionality to facilitate debugging kernels. The OSKit does not itself contain a complete kernel debugger (at least, not yet), but it contains extensive support for remote debugging using GDB, the GNU debugger. This remote debugging support allows you to run the debugger on one machine, and run the actual OS kernel being debugged on a different machine. The two machines can be of different architectures. A small “debugging stub” is linked into the OS kernel; this piece of code handles debugging-related traps and interrupts and communicates with the remote debugger, acting as a “slave” that simply interprets and obeys the debugger’s commands.

This section describes remote debugging in general, applicable to any mechanism for communicating with the remote kernel (e.g., serial line or ethernet). The next section (15.18) describes kernel debugging support specific to the serial line mechanism (currently the only one implemented).

XXX diagram

One of the main advantages of remote debugging is that you can use a complete, full-featured source-level debugger, since it can run on a stable, well-established operating system such as Unix; a debugger running on the same machine as the kernel being debugged would necessarily have to be much smaller and simpler because of the lack of a stable underlying OS it can rely on. Another advantage is that remote debugging is less invasive: since most of the debugging code is on a different machine, and the remote debugging stub linked into the OS is much smaller than even a simple stand-alone debugger, there is much less that can “go wrong” with the debugging code when Strange Things start to happen due to subtle kernel bugs. The main disadvantage of remote debugging, of course, is that it requires at least two machines with an appropriate connection between them.

The GNU debugger, GDB, supports a variety of remote debugging protocols. The most common and well-supported is the serial-line protocol, which operates over an arbitrary serial line (typically null-modem) connection operating at any speed supported by the two machines involved. The serial-line debugging protocol supports a multitude of features such as multiple threads, signals, and data compression. GDB also supports an Ethernet-based remote debugging protocol and a variety of existing vendor- and OS-specific protocols.

Ths OS kit’s GDB support has been tested with GDB versions 4.15 and 4.16; probably a version >= 4.15 is required.

15.17.1 Organization of remote GDB support code

The GDB remote debugging support provided by the OSKit is broken into two components: the protocol-independent component and the protocol-specific component. The protocol-independent component encapsulates all the processor architecture-specific code to handle processor traps and convert them into the “signals” understood by GDB, to convert saved state frames to and from GDB’s standard representation for a given architecture, and to perform “safe” memory reads and writes on behalf of the remote user so that faulting accesses will terminate cleanly without causing recursive traps.

The protocol-specific component of the toolkit’s remote GDB support encapsulates the code necessary to talk to the remote debugger using the appropriate protocol. Although this code is specific to a particular protocol, it is architecture-neutral. The OSKit currently supports only the standard serial-line protocol, although support for other protocols is planned (particularly the remote Ethernet debugging protocol) and should be easy to add.

15.17.2 Using the remote debugging code

If you are using the base environment’s default trap handler, then activating the kernel debugger is extremely easy: it is simply necessary to call an appropriate initialization routine near the beginning of your kernel code; all subsequent traps that occur will be dispatched to the remote debugger. For example, on a PC, to activate serial-line debugging over COM1 using default serial parameters, simply make the call ‘gdb_pc_com_init(1, 0)’. Some example kernels are provided with the OSKit that demonstrate how to initialize and use the remote debugging facilities; see Section 1.6.1 for more information.

If you want a trap to occur immediately after initialization of the debugging mechanism, to transfer control to the remote debugger from the start and give you the opportunity to set breakpoints and such, simply invoke the gdb_breakpoint macro immediately after the call to initialize the remote debugger (see Section 15.17.11).

If your kernel uses its own trap entrypoint mechanisms or its own serial line communication code (e.g., “real” interrupt-driven serial device drivers instead of the simple polling code used by default by the toolkit), then you will have to write a small amount of “glue” code to interface the generic remote debugging support code in the toolkit with your specific OS mechanisms. However, this glue code should generally be extremely small and simple, and you can use the default implementations in the OSKit as templates to work from or use as examples.

15.17.3 Debugging address spaces other than the kernel’s

Although the OSKit’s remote debugging support code is most directly and obviously useful for debugging the OS kernel itself, most of the code does not assume that the kernel is the entity being debugged. In fact, it is quite straightforward to adapt the mechanism to allow remote debugging of other entities, such as user-level programs running on top of the kernel. To make the debugging stub operate on a different address space than the kernel’s, it is simply necessary to override the gdb_copyin and gdb_copyout routines with alternate versions that transfer data to or from the appropriate address space. Operating systems that support a notion of user-level address spaces generally have some kind of “copyin” and “copyout” routines anyway to provide safe access to user address spaces; the replacement gdb_copyin and gdb_copyout routines can call those standard user space access routines. In addition, the trap handling mechanism may need to be set up so that only traps occurring in a particular context (e.g., within a particular user process or thread) will be dispatched to the remote debugger.

15.17.4 gdb_state: processor register state frame used by GDB

SYNOPSIS

#include <oskit/gdb.h>

struct gdb_state {

  ...; /* architecture-specific definitions */

};
DESCRIPTION

This structure represents the processor register state for the target architecture in the form in which GDB expects it. GDB uses a standard internal data structure for each processor architecture to represent the register state of a program being debugged, and most of GDB’s architecture-neutral remote debugging protocols use this standard structure. The gdb_state structure defined by the OSKit is defined to match GDB’s corresponding register state structure for each supported architecture.

15.17.5 gdb_trap: default trap handler for remote GDB debugging

SYNOPSIS

#include <oskit/gdb.h>

int gdb_trap(struct trap_state *trap_state);

DESCRIPTION

This function is intended to be installed as the kernel trap handler for all traps by setting each of the entries in the base_trap_handlers array to point to it (see Section 15.8.4), when remote GDB debugging is desired. (Alternatively, the client OS can use its own trap handlers which chain to gdb_trap when appropriate.) This function converts the contents of the trap_state structure saved by the base trap entrypoint code into the gdb_state structure used by GDB. It also converts the architecture-specific processor trap vector number into a suitable machine-independent signal number which can be interpreted by the remote debugger.

After converting the register state and trap vector appropriately, this function calls the appropriate protocol-specific GDB stub through the gdb_signal function pointer variable (see Section 15.17.9). Finally, it converts the final register state, possibly modified by the remote debugger, back into the original trap_state format and returns an appropriate success or failure code as described below.

On architectures that don’t provide a way for the kernel to “validate” memory accesses before performing them, such as the x86, this function also provides support for “recovering” from faulting memory accesses during calls to gdb_copyin or gdb_copyout (see Sections 15.17.6 and 15.17.7). This is typically implemented using a “recovery pointer” which is set before a “safe” memory access and cleared afterwards; gdb_trap checks this recovery pointer, and if set, modifies the trap state appropriately and returns from the trap without invoking the protocol-specific GDB stub.

If the client OS uses its own trap entrypoint code which saves register state in a different format when handling traps, then the client OS will also need to override the gdb_trap function with a version that understands its custom saved state format.

PARAMETERS
trap_state:
A pointer to the saved register state representing the processor state at the time the trap occurred. The saved state must be in the default format defined by the OSKit’s base environment.
RETURNS

The gdb_trap function returns success (zero) when the remote debugger instructs the local stub to resume execution at the place it was stopped and “consume” the trap that caused the debugger to be invoked; this is the normal case.

This function returns failure (nonzero) if the remote debugger passed the same or a different signal back to the local GDB stub, instructing the local kernel to handle the trap (signal) itself. If the default trap entrypoint mechanism provided by the base environment in use, then this simply causes the kernel to panic with a register dump, since the default trap code does not know how to “handle” signals by itself. However, if the client OS uses its own trap entrypoint mechanism or interposes its own trap handler over gdb_trap, then it may wish to interpret a nonzero return code from gdb_trap as a request for the trap to be handled using the “normal” mechanism, (e.g., dispatched to the application being debugged).

DEPENDENCIES
trap_state:
§ 15.8.1
gdb_state:
§ 15.17.4
gdb_signal:
§ 15.17.9
gdb_trap_recover:
§ 15.17.8

15.17.6 gdb_copyin: safely read data from the subject’s address space

SYNOPSIS

#include <oskit/gdb.h>

int gdb_copyin(oskit_addr_t src_va, void *dest_buf, oskit_size_t size);

DESCRIPTION

The protocol-specific local GDB stub calls this function in order to read data in the address space of the program being debugged. The default implementation of this function provided by libkern assumes that the kernel itself is the program being debugged; thus, it acts basically like an ordinary memcpy. However, the client can override this function with a version that accesses a different address space, such as a user process’s address space, in order to support remote debugging of entities other than the kernel.

If a fault occurs while trying to read the specified data, this function catches the fault cleanly and returns an error code rather than allowing a recursive trap to be dispatched to the debugger. This way, if the user of the debugger accidentally attempts to follow an invalid pointer or display unmapped or nonexistent memory, it will merely cause the debugger to report an error rather than making everything go haywire.

PARAMETERS
src_va:
The virtual address in the address space of the program being debugged (the kernel’s address space, by default) from which to read data.
dest_buf :
A pointer to the kernel buffer to copy data into. This buffer is provided by the caller, typically the local GDB stub,
size:
The number of bytes of data to read into the destination buffer.
RETURNS

Returns zero if the transfer completed successfully, or nonzero if some or all of the source region is not accessible.

DEPENDENCIES
gdb_trap_recover:
§ 15.17.8

15.17.7 gdb_copyout: safely write data into the subject’s address space

SYNOPSIS

#include <oskit/gdb.h>

int gdb_copyout(const void *src_buf, oskit_addr_t dest_va, oskit_size_t size);

DESCRIPTION

The protocol-specific local GDB stub calls this function in order to write data into the address space of the program being debugged. The default implementation of this function provided by libkern assumes that the kernel itself is the program being debugged; thus, it acts basically like an ordinary memcpy. However, the client can override this function with a version that accesses a different address space, such as a user process’s address space, in order to support remote debugging of entities other than the kernel.

If a fault occurs while trying to write the specified data, this function catches the fault cleanly and returns an error code rather than allowing a recursive trap to be dispatched to the debugger. This way, if the user of the debugger accidentally attempts to write to unmapped or nonexistent memory, it will merely cause the debugger to report an error rather than making everything go haywire.

PARAMETERS
src_buf :
A pointer to the kernel buffer containing the data to write.
dest_va:
The virtual address in the address space of the program being debugged (the kernel’s address space, by default) at which to write the data.
size:
The number of bytes of data to transfer.
RETURNS

Returns zero if the transfer completed successfully, or nonzero if some or all of the destination region is not writable.

DEPENDENCIES
gdb_trap_recover:
§ 15.17.8

15.17.8 gdb_trap_recover: recovery pointer for safe memory transfer routines

15.17.9 gdb_signal: vector to GDB trap/signal handler routine

SYNOPSIS

#include <oskit/gdb.h>

extern void (*gdb_signal)(int *inout_signo, struct gdb_state *inout_gdb_state );

DESCRIPTION

Before gdb_trap is called for the first time, this function pointer must be initialized to point to an appropriate GDB debugging stub, such as gdb_serial_signal (see Section 15.18.2). This function is called to notify the remote debugger that a relevant processor trap or interrupt has occurred, and to wait for further instructions from the remote debugger. When the function returns, execution will be resumed as described in Section 15.17.5.

PARAMETERS
inout_signo:
On entry, the variable referenced by this pointer contains the signal number to transmit to the remote debugger. On return, this variable may have been modified to indicate what signal should be dispatched to the program being debugged. For example, if the variable is the same on return as on entry, then it means the remote debugger instructed the stub to “pass through” the signal to the application. If *signo is 0 on return from this function, it means the remote debugger has “consumed” the signal and execution of the subject program should be resumed immediately.
inout_gdb_state:
On entry, this structure contains a snapshot of the processor state at the time the relevant trap or interrupt occurred. On return, the remote debugger may have modified this state; the new state should be used when resuming execution.

15.17.10 gdb_set_trace_flag: enable or disable single-stepping in a state frame

SYNOPSIS

#include <oskit/gdb.h>

void gdb_set_trace_flag(int trace_enable, [in/out] struct gdb_state *state);

DESCRIPTION

This architecture-specific function merely modifies the specified processor state structure to enable or disable single-stepping according to the trace_enable parameter. On architectures that have some kind of trace flag, this function simply sets or clears that flag as appropriate. On other architectures, this behavior is achieved through other means. This function is called by machine-independent remote debugging stubs such as gdb_serial_signal before resuming execution of the subject program, according to whether the remote debugger requested that the program “continue” or “step” one instruction.

PARAMETERS
trace_enable:
True if single-stepping should be enabled, or false otherwise.
state:
The state frame to modify.

15.17.11 gdb_breakpoint: macro to generate a manual instruction breakpoint

SYNOPSIS

#include <oskit/gdb.h>

void gdb_breakpoint(void);

DESCRIPTION

This is simply an architecture-specific macro which causes an instruction causing a breakpoint trap to be emitted at the corresponding location in the current function. This macro can be used to set “manual breakpoints” in program code, as well as to give control to the debugger at the very beginning of program execution as described in Section 15.17.2.

15.18 Serial-line Remote Debugging with GDB

The GDB serial-line debugging protocol is probably the most powerful and commonly-used remote debugging protocol supported by GDB; this is the only protocol for which the OSKit currently has direct support. The GDB serial-line debugging stub supplied with the OSKit is fully architecture-independent, and supports most of the major features of the GDB serial-line protocol.

For technical information on the remote serial-line GDB debugging protocol, or information on how to run and use the remote debugger itself, consult the appropriate sections of the GDB manual. This section merely describes how remote serial-line debugging is supported by the OSKit.

Note that source code for several example serial-line debugging stubs are supplied in the GDB distribution (gdb/*-stub.c); in fact, this code was used as a template and example for the OSKit’s serial-line debugging stub. However, these stubs are highly machine-dependent and make many more assumptions about how they are used. For example, they assume that they have exclusive control of the processor’s trap vector table, and are therefore only generally usable in an embedded environment where traps are never supposed to occur during normal operation and therefore all traps can be fielded directly by the debugger. In contrast, the serial-line debugging stub provided in the OSKit is much more generic and cleanly decomposed, and therefore should be usable in a much wider range of environments.

15.18.1 Redirecting console output to the remote debugger

If the machine on which the kernel is being debugged is truly “remote,” e.g., in a different room or a completely different building, and you don’t have easy access to the machine’s “real” console, it is possible to make the kernel use the remote debugger as its “console” for printing status messages and such. To do this, simply write your kernel’s “console” output functions (e.g., putchar and puts, if you’re using the OSKit’s minimal C library for console output routines such as printf) so that they call gdb_serial_putchar and gdb_serial_puts, described in Sections 15.18.5 and 15.18.6, respectively. The OSKit base console environment (section 15.13) does this as necessary.

This mechanism only works for console output: console input cannot be obtained from the remote debugger’s console because the GDB serial-line debugging protocol does not currently support it. However, console input can be obtained “outside the protocol” as described in section 15.18.4.

15.18.2 gdb_serial_signal: primary event handler in the GDB stub

SYNOPSIS

#include <oskit/gdb_serial.h>

void gdb_serial_signal([in/out] int *signo, [in/out] struct gdb_state *state);

DESCRIPTION

This is the main trap/signal handler routine in the serial-line debugging stub; it should be called whenever a relevant processor trap occurs. This function notifies the remote debugger about the event that caused the processor to stop, and then waits for instructions from the remote debugger. The remote debugger may then cause the stub to perform various actions, such as examine memory, modify the register state, or kill the program being debugged. Eventually, the remote debugger will probably instruct the stub to resume execution, in which case this function returns with the signal number and trap state modified appropriately.

If this function receives a “kill” (‘k’) command from the remote debugger, then it breaks the remote debugging connection and then calls panic to reboot the machine. XXX may not be appropriate when debugging a user task; should call an intermediate function.

PARAMETERS
signo:
On entry, the variable referenced by this pointer contains the signal number to transmit to the remote debugger. On return, this variable may have been modified to indicate what signal should be dispatched to the program being debugged. For example, if the variable is the same on return as on entry, then it means the remote debugger instructed the stub to “pass through” the signal to the application. If *signo is 0 on return from this function, it means the remote debugger has “consumed” the signal and execution of the subject program should be resumed immediately.
state:
On entry, this structure contains a snapshot of the processor state at the time the relevant trap or interrupt occurred. On return, the remote debugger may have modified this state; the new state should be used when resuming execution.
DEPENDENCIES
gdb_serial_send:
§ 15.18.8
gdb_serial_recv:
§ 15.18.7
gdb_copyin:
§ 15.17.6
gdb_copyout:
§ 15.17.7
gdb_set_trace_flag:
§ 15.17.10
panic:
§ 14.8.3

15.18.3 gdb_serial_exit: notify the remote debugger that the subject is dead

SYNOPSIS

#include <oskit/gdb_serial.h>

void gdb_serial_exit(int exit_code);

DESCRIPTION

This function sends a message to the remote debugger indicating that the program being debugged is terminating. This message causes the debugger to display an appropriate message on the debugger’s console along with the exit_code, and causes it to break the connection (i.e., stop listening for further messages on the serial port). If no remote debugging connection is currently active, this function does nothing.

The client OS should typically call this function just before it reboots for any reason, so that the debugger does not hang indefinitely waiting for a response from a kernel that is no longer running. Alternatively, if the remote debugging facility is being used to debug a user-mode process running under the kernel, then this function should be called when that process terminates.

Note that despite its name, this function does return. It does not by itself cause the machine to “exit” or reboot or hang or whatever; it merely notifies the debugger that the subject program is about to terminate.

PARAMETERS
exit_code:
Exit code to pass back to the remote debugger. Typically this value is simply printed on the remote debugger’s console.
DEPENDENCIES
gdb_serial_send:
§ 15.18.8
gdb_serial_recv:
§ 15.18.7

15.18.4 gdb_serial_getchar: input a character from the remote debugger’s console

SYNOPSIS

#include <oskit/gdb_serial.h>

int gdb_serial_getchar(void);

static char inbuf[256];

DESCRIPTION

Unfortunately, the GDB protocol doesn’t support console input. However, we can simulate it with a rather horrible kludge: when the kernel first does a read from the console we take a breakpoint, allowing the user to fill an input buffer with a command such as:

 call strcpy(inbuf, "hello\r")

The supplied characters will be returned from successive calls to gdb_serial_getchar, until inbuf is emptied, at which point we hit a breakpoint again.

RETURNS

Returns the next available character in the inbuf array.

DEPENDENCIES
gdb_breakpoint:
§ 15.17.11
base_critical_enter:
§ 15.2.5
base_critical_leave:
§ 15.2.5

15.18.5 gdb_serial_putchar: output a character to the remote debugger’s console

SYNOPSIS

#include <oskit/gdb_serial.h>

void gdb_serial_putchar(int ch);

DESCRIPTION

If a remote debugging connection is currently active, this function sends the specified character to the remote debugger in a special “output” (‘O’) message which causes that character to be sent to the debugger’s standard output. This allows the serial line used for remote debugging to double as a remote serial console, as described in Section 15.18.1.

Note that using gdb_serial_putchar by itself to print messages can be very inefficient, because a separate message is used for each character, and each of these messages must be acknowledged by the remote debugger before the next character can be sent. When possible, it is much faster to print strings of text using gdb_serial_puts (see Section 15.18.6). If you are using the implementation of printf in the OSKit’s minimal C library (see Section 14.6), you can make this happen automatically by overriding puts with a version that calls gdb_serial_puts directly instead of calling putchar successively on each character.

If this function is called while no remote debugging connection is active, but the gdb_serial_send and gdb_serial_receive pointers are initialized to point to serial-line communication functions, then this function simply sends the specified character out the serial port using gdb_serial_send. This way, if the kernel attempts to print any messages before a connection has been established or after the connection has been dropped (e.g., by calling gdb_serial_exit), they won’t confuse the debugger or cause the kernel to hang as they otherwise would, and they may be seen by the remote user if the serial port is being monitored at the time.

If the gdb_serial_send and gdb_serial_receive pointers are uninitialized (still NULL) when this function is called, it does nothing.

PARAMETERS
ch:
The character to send to the remote debugger’s console.
DEPENDENCIES
gdb_serial_send:
§ 15.18.8
gdb_serial_recv:
§ 15.18.7

15.18.6 gdb_serial_puts: output a line to the remote debugger’s console

SYNOPSIS

#include <oskit/gdb_serial.h>

void gdb_serial_puts(const char *s);

DESCRIPTION

If a remote debugging connection is currently active, this function sends the specified string, followed by a newline character, to the remote debugger in a special “output” (‘O’) message which causes the line to be sent to the debugger’s standard output. This allows the serial line used for remote debugging to double as a remote serial console, as described in Section 15.18.1.

If this function is called while no remote debugging connection is active, but the gdb_serial_send and gdb_serial_receive pointers are initialized to point to serial-line communication functions, then this function simply sends the specified line out the serial port using gdb_serial_send. This way, if the kernel attempts to print any messages before a connection has been established or after the connection has been dropped (e.g., by calling gdb_serial_exit), they won’t confuse the debugger or cause the kernel to hang as they otherwise would, and they may be seen by the remote user if the serial port is being monitored at the time.

If the gdb_serial_send and gdb_serial_receive pointers are uninitialized (still NULL) when this function is called, it does nothing.

PARAMETERS
s:
The string to send to the remote debugger’s console. A newline is automatically appended to this string.
DEPENDENCIES
gdb_serial_send:
§ 15.18.8
gdb_serial_recv:
§ 15.18.7

15.18.7 gdb_serial_recv: vector to GDB serial line receive function

SYNOPSIS

#include <oskit/gdb_serial.h>

int (*gdb_serial_recv)(void);

DESCRIPTION

Before the remote serial-line debugging stub can be used, this global variable must be initialized to point to a function to call to read a character from the serial port. The function should not return until a character has been received; the GDB stub has no notion of timeouts or interruptions.

Calling functions in the GDB serial-line debugging stub before this variable is initialized (i.e., while it is still null) is guaranteed to be harmless.

RETURNS

Returns the character received.

15.18.8 gdb_serial_send: vector to GDB serial line send function

SYNOPSIS

#include <oskit/gdb_serial.h>

void (*gdb_serial_send)(int ch );

DESCRIPTION

Before the remote serial-line debugging stub can be used, this global variable must be initialized to point to a function to call to send out a character on the serial port.

Calling functions in the GDB serial-line debugging stub before this variable is initialized (i.e., while it is still null) is guaranteed to be harmless.

RETURNS

Returns the character received.

15.18.9 gdb_pc_com_init: (X86 PC) set up serial-line debugging over a COM port

SYNOPSIS

#include <oskit/gdb.h>

void gdb_pc_com_init(int com_port, struct termios *com_params);

DESCRIPTION

This is a simple “wrapper” function which ties together all of the OSKit’s remote debugging facilities to automatically create a complete remote debugging environment for a specific, typical configuration: namely, remote serial-line debugging on a PC through a COM port. This function can be used as-is if this configuration happens to suit your purposes, or it can be used as an example for setting up the debugging facilities for other configurations.

Specifically, this function does the following:

PARAMETERS
com_port:
The COM port number through which to communicate: must be 1, 2, 3, or 4.
com_params:
A pointer to a termios structure defining the required serial port communication parameters. If this parameter is NULL, the serial port is set up for 9600,8,N,1 by default.
DEPENDENCIES
gdb_trap:
§ 15.17.5
gdb_signal:
§ 15.17.9
gdb_serial_signal:
§ 15.18.2
gdb_serial_recv:
§ 15.18.7
gdb_serial_send:
§ 15.18.8
com_cons_init:
§ 15.13.8
com_cons_getchar:
§ 15.13.9
com_cons_putchar:
§ 15.13.10
com_cons_enable_receive_interrupt:
§ 15.13.12
base_idt:
§ 15.7.4
base_raw_termios:
§ 15.13.4

15.19 Annotations

Kernel annotations are “markers” that can be placed in code or static data. Annotations are static and are collected into a special section of the object/executable file. How this section is created is object-file format specific and is normally handled by the default startup files (e.g, crt0.o).

Annotations are organized in tables which is sorted by a key value (typically the address being marked) at boot time via anno_init.

The basic annotation structures look like:

struct anno_table {

  struct anno_entry *start; /* first entry */
  struct anno_entry *end; /* last entry */

};

struct anno_entry {

  oskit_addr_t val1; /* lookup value */
  oskit_addr_t val2; /* context dependent value */
  oskit_addr_t val3; /* context dependent value */
  struct anno_table *table; /* associated anno_table */

};

Annotation tables contain a pointer to the first and last entries they contain. All entries in a table are contiguous and sorted by the key value.

Annotation entries specify the table they belong to, the key value used for lookups, and two uninterpreted values.

Though annotations can be structured arbitrarily, the OSKit supports two common kernel annotation uses: “trap tables” and “interrupt tables.” These are described in the following anno_trap and anno_intr sections.

Currently, annotation support only works with the ELF binary file format (i.e., it does not work with a.out).

15.19.1 anno.h: (X86) generic macros to place annotations in kernel code.

SYNOPSIS

#include <oskit/machine/anno.h>

ANNO_ENTRY(table, val1, val2, val3)

ANNO_TEXT(table, val2, val3)

DESCRIPTION

Contains architecture-dependent, assembly-code callable macros for placing annotations in kernel text and initialized data. No C-callable versions are included since most C compilers may reorder code making exact placement of annotations impossible.

ANNO_ENTRY creates a generic annotation entry associated with the indicated table and filled with the specified values.

ANNO_TEXT records an annotation in the given table for the current point in the text (code) segment. The current value of the program counter is placed in val1. The specified values for val2 and val3 are recorded.

15.19.2 anno_dump: dump all annotation tables

SYNOPSIS

#include <oskit/anno.h>

void anno_dump(void);

DESCRIPTION

Dumps, using printf, all registered annotation tables and entries. Should not be called before anno_init.

DEPENDENCIES
printf:
§ 14.6

15.19.3 anno_find_exact: find annotation table exactly entry matching a value.

SYNOPSIS

#include <oskit/anno.h>

struct anno_entry *anno_find_exact(struct anno_table *tab, oskit_addr_t val1);

DESCRIPTION

Find an entry in the specified annotation table whose val1 field exactly matches the specified value.

PARAMETERS
tab:
annotation table to search
val1:
value to search for
RETURNS

Returns a pointer to the anno_entry matching the value, or zero if no entry matches.

15.19.4 anno_find_lower: find greatest annotation table entry below a value.

SYNOPSIS

#include <oskit/anno.h>

struct anno_entry *anno_find_lower(struct anno_table *tab, oskit_addr_t val1);

DESCRIPTION

Find an entry in the specified annotation table with the largest val1 field less than or equal to the specified value. If no entry has a lower or equal value, returns zero.

PARAMETERS
tab:
annotation table to search
val1:
value to search for
RETURNS

Returns a pointer to the appropriate anno_entry, or zero if no lower entry is found.

15.19.5 anno_init: initialize annotation tables and sort the entries.

SYNOPSIS

#include <oskit/anno.h>

void anno_init(void);

DESCRIPTION

This routine should be called once at program startup; it sorts all of the annotation entries appropriately and initializes the annotation tables they reside in.

15.19.6 anno_intr: (X86) interrupt annotations

SYNOPSIS

#include <oskit/anno.h>

struct anno_table anno_intr;

ANNO_INTR(routine, val3)

void anno_intr_handler(struct anno_entry *anno, struct trap_state *tstate);

DESCRIPTION

The interrupt annotation table anno_intr contains entries which associate a handler function with ranges of addresses in the kernel. Each entry defines an action to be performed if an asynchronous exception occurs between the address associated with this entry and that associated with the following entry. When an interrupt occurs, the default OSKit interrupt handler (in base_irq_inittab.S) uses anno_find_lower to locate the correct annotation entry based on the instruction pointer at the time of the interrupt. This handler function is invoked prior to calling the standard interrupt handling function.

ANNO_INTR is a macro in oskit/x86/anno.h. It records an annotation in anno_intr for the current point in the code segment. The given routine and val3 arguments are stored in the entry’s val2 and val3 fields respectively. To disable interrupt redirection for a piece of code, place an ANNO_INTR(0,0) call before it.

The annotation interrupt handler function is called in the context of the interrupted thread with a pointer to the associated annotation entry and a pointer to the architecture-specific trap state for the thread. On return from the annotation handler, the actual interrupt handler is called.

An example of interrupt annotation usage in a kernel is an efficient alternative to using “spls” (i.e., raising processor priority) to protect critical sections from interrupts. By marking the critical section with an annotation entry, the kernel detects when an interrupt occurs within it and invokes an associated roll-back or roll-forward routine to back out of or complete the critical section before invoking the interrupt handler.

15.19.7 anno_trap: (X86) trap annotations

SYNOPSIS

#include <oskit/anno.h>

struct anno_table anno_trap;

ANNO_TRAP(routine, val3)

int anno_trap_handler(struct anno_entry *anno, struct trap_state *tstate);

DESCRIPTION

The trap annotation table anno_trap contains entries which associate a handler function with specific addresses in the kernel. These addresses correspond to points where synchronous exceptions are expected to occur. When such an exception occurs, the default OSKit trap handler (in base_trap_inittab.S) uses anno_find_exact to locate an annotation entry based on the instruction pointer at the time of the fault. This handler function is invoked instead of the standard kernel trap handler in those instances.

ANNO_TRAP is a macro in oskit/x86/anno.h. It records an annotation in anno_trap for the current point in the code segment. The given routine and val3 arguments are stored in the entry’s val2 and val3 fields respectively.

The annotation trap handler function is called in the context of the faulting thread with a pointer to the matching annotation entry and a pointer to the architecture-specific trap state for the thread. If the handler returns zero, the OSKit trap handler will restore state from the trap state structure and resume execution. If it returns non-zero, it is considered a failed fault and the kernel will panic.

An example of trap annotation usage is a kernel copyin routine which must read data in the user’s address space. Associating an annotation entry with the instruction which moves data from the user’s address space enables the kernel to catch any access violation caused and reflect it to the user.

15.20 Boot Module Filesystem

The Boot Module (BMOD) filesystem is derived from the trivial memory-based filesystem, memfs (Chapter 38), exporting the OSKit filesystem interface. The initial contents of the BMOD filesystem are loaded from the Multiboot boot image. This allows an OSKit kernel to load a filesystem at boot time, possibly not even requiring an actual disk-based filesystem.

XXX multiboot strings are parsed to create hierarchical filesystem. XXX new BMODs may be added, multiboot BMODs may be modified or destroyed. XXX no guarantee on alignment of multiboot created BMOD files.

15.20.1 start_fs_bmod: Create the BMOD filesystem

SYNOPSIS

#include <oskit/fs/memfs.h> #include <oskit/startup.h>

oskit_dir_t *start_fs_bmod(void);

DESCRIPTION

Initialize the BMOD filesystem. Create the supporting memfs, and load it with the contents of the multiboot BMODs.

RETURNS

Returns a handle to the root of the BMOD filesystem

15.21 Signals

The signal handling facilities allow the client OS to provide compatibility with POSIX style signal handling semantics. The support provided is extremely basic and is intended to be used in conjunction with the OSKit’s FreeBSD C library (see Section 21) by arranging for unexpected hardware traps to be converted into an appropriate signal and delivered through the C library. By default, the FreeBSD C library will not arrange for signals to be delivered unless the oskit_init_libc initialization routine is called (see Section 14.7.1). The exception are kernels linked with the POSIX threads module, which will always call the initialization routine.

15.21.1 oskit_sendsig_init: initialize kernel signal delivery

SYNOPSIS

#include <oskit/c/signal.h>

void oskit_sendsig_init(int (*deliver_function)(int signo, int code, struct trap_state *ts));

DESCRIPTION

Initialize the kernel signal delivery mechanism, providing an upper level signal delivery routine. This delivery function will usually be an entrypoint in the C library that provides the appropriate signal semantics to the application. This entrypoint is responsible for converting the machine dependent trap state information into a suitable signal context structure, as defined by the API of the library in use. Since a pointer to the trap state structure is passed along, the callee is free to modify the machine state in way it wishes.

DEPENDENCIES
oskit_init_libc:
§ 14.7.1
oskit_init_libc:
§ 21.8.1

15.21.2 oskit_sendsig: deliver a signal

SYNOPSIS

#include <oskit/c/signal.h>
#include <oskit/x86/base_trap.h>

int oskit_sendsig(int signo, struct trap_state *state);

DESCRIPTION

Propagate a machine trap to the signal handling entrypoint provided to oskit_sendsig_init() above. This routine is intended to be called by modules that have replaced a particular trap handler, and wish to propagate the trap to the application in the form of a signal. If the C library has not called oskit_sendsig_init(), the routine returns without doing anything.

PARAMETERS
signo:
The signal number.
state:
A pointer to the trap state structure.
RETURNS

Returns non-zero if a C library handler has not been installed, and thus the signal could not be propagated.

15.21.3 sendsig_trap_handler: convert trap into a signal

SYNOPSIS

#include <oskit/c/signal.h>
#include <oskit/x86/base_trap.h>

void oskit_sendsig(struct trap_state *state);

DESCRIPTION

Convert the machine dependent trap state structure state (see Section 15.8.1) into a signal code, and pass that, along with the trap state, to the C library via oskit_sendsig above.

This routine is provided as a default trap handler that can be plugged into the base_trap_handlers array (see Section 15.8.4). Unexpected hardware traps are thus converted into signals and delivered to the application through the C library.

PARAMETERS
state:
A pointer to the trap state structure.