summaryrefslogtreecommitdiff
path: root/gpxe/src/arch/i386/README.i386
blob: b9b79cc4b7058d28439205d9afc872d3a3bee50f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
Etherboot/NILO i386 initialisation path and external call interface
===================================================================

1. Background

GCC compiles 32-bit code.  It is capable of producing
position-independent code, but the resulting binary is about 25%
bigger than the corresponding fixed-position code.  Since one main use
of Etherboot is as firmware to be burned into an EPROM, code size must
be kept as small as possible.

This means that we want to compile fixed-position code with GCC, and
link it to have a predetermined start address.  The problem then is
that we must know the address that the code will be loaded to when it
runs.  There are several ways to solve this:

1. Pick an address, link the code with this start address, then make
   sure that the code gets loaded at that location.  This is
   problematic, because we may pick an address that we later end up
   wanting to use to load the operating system that we're booting.

2. Pick an address, link the code with this start address, then set up
   virtual addressing so that the virtual addresses match the
   link-time addresses regardless of the real physical address that
   the code is loaded to.  This enables us to relocate Etherboot to
   the top of high memory, where it will be out of the way of any
   loading operating system.

3. Link the code with a text start address of zero and a data start
   address also of zero.  Use 16-bit real mode and the
   quasi-position-independence it gives you via segment addressing.
   Doing this requires that we generate 16-bit code, rather than
   32-bit code, and restricts us to a maximum of 64kB in each segment.

There are other possible approaches (e.g. including a relocation table
and code that performs standard dynamic relocation), but the three
options listed above are probably the best available.

Etherboot can be invoked in a variety of ways (ROM, floppy, as a PXE
NBP, etc).  Several of these ways involve control being passed to
Etherboot with the CPU in 16-bit real mode.  Some will involve the CPU
being in 32-bit protected mode, and there's an outside chance that
some may involve the CPU being in 16-bit protected mode.  We will
almost certainly have to effect a CPU mode change in order to reach
the mode we want to be in to execute the C code.

Additionally, Etherboot may wish to call external routines, such as
BIOS interrupts, which must be called in 16-bit real mode.  When
providing a PXE API, Etherboot must provide a mechanism for external
code to call it from 16-bit real mode.

Not all i386 builds of Etherboot will want to make real-mode calls.
For example, when built for LinuxBIOS rather than the standard PCBIOS,
no real-mode calls are necessary.

For the ultimate in PXE compatibility, we may want to build Etherboot
to run permanently in real mode.

There is a wide variety of potential combinations of mode switches
that we may wish to implement.  There are additional complications,
such as the inability to access a high-memory stack when running in
real mode.

2. Transition libraries

To handle all these various combinations of mode switches, we have
several "transition" libraries in Etherboot.  We also have the concept
of an "internal" and an "external" environment.  The internal
environment is the environment within which we can execute C code.
The external environment is the environment of whatever external code
we're trying to interface to, such as the system BIOS or a PXE NBP.

As well as having a separate addressing scheme, the internal
environment also has a separate stack.

The transition libraries are:

a) librm

librm handles transitions between an external 16-bit real-mode
environment and an internal 32-bit protected-mode environment with
virtual addresses.

b) libkir

libkir handles transitions between an external 16-bit real-mode (or
16:16 or 16:32 protected-mode) environment and an internal 16-bit
real-mode (or 16:16 protected-mode) environment.

c) libpm

libpm handles transitions between an external 32-bit protected-mode
environment with flat physical addresses and an internal 32-bit
protected-mode environment with virtual addresses.

The transition libraries handle the transitions required when
Etherboot is started up for the first time, the transitions required
to execute any external code, and the transitions required when
Etherboot exits (if it exits).  When Etherboot provides a PXE API,
they also handle the transitions required when a PXE client makes a
PXE API call to Etherboot.

Etherboot may use multiple transition libraries.  For example, an
Etherboot ELF image does not require librm for its initial transitions
from prefix to runtime, but may require librm for calling external
real-mode functions.

3. Setup and initialisation

Etherboot is conceptually divided into the prefix, the decompressor,
and the runtime image.  (For non-compressed images, the decompressor
is a no-op.)  The complete image comprises all three parts and is
distinct from the runtime image, which exclude the prefix and the
decompressor.

The prefix does several tasks:

  Load the complete image into memory.  (For example, the floppy
  prefix issues BIOS calls to load the remainder of the complete image
  from the floppy disk into RAM, and the ISA ROM prefix copies the ROM
  contents into RAM for faster access.)

  Call the decompressor, if the runtime image is compressed.  This
  decompresses the runtime image.

  Call the runtime image's setup() routine.  This is a routine
  implemented in assembly code which sets up the internal environment
  so that C code can execute.

  Call the runtime image's arch_initialise() routine.  This is a
  routine implemented in C which does some basic startup tasks, such
  as initialising the console device, obtaining a memory map and
  relocating the runtime image to high memory.

  Call the runtime image's arch_main() routine.  This records the exit
  mechanism requested by the prefix and calls main().  (The prefix
  needs to register an exit mechanism because by the time main()
  returns, the memory occupied by the prefix has most likely been
  overwritten.)

When acting as a PXE ROM, the ROM prefix contains an UNDI loader
routine in addition to its usual code.  The UNDI loader performs a
similar sequence of steps:

  Load the complete image into memory.

  Call the decompressor.

  Call the runtime image's setup() routine.

  Call the runtime image's arch_initialise() routine.

  Call the runtime image's install_pxe_stack() routine.

  Return to caller.

The runtime image's setup() routine will perform the following steps:

  Switch to the internal environment using an appropriate transition
  library.  This will record the parameters of the external
  environment.

  Set up the internal environment: load a stack, and set up a GDT for
  virtual addressing if virtual addressing is to be used.

  Switch back to the external environment using the transition
  library.  This will record the parameters of the internal
  environment.

Once the setup() routine has returned, the internal environment has been
set up ready for C code to run.  The prefix can call C routines using
a function from the transition library.

The runtime image's arch_initialise() routine will perform the
following steps:

  Zero the bss

  Initialise the console device(s) and print a welcome message.

  Obtain a memory map via the INT 15,E820 BIOS call or suitable
  fallback mechanism. [not done if libkir is being used]

  Relocate the runtime image to the top of high memory. [not done if
  libkir is being used]

  Install librm to base memory. [done only if librm is being used]

  Call initialise().

  Return to the prefix, setting registers to indicate to the prefix
  the new location of the transition library, if applicable.  Which
  registers these are is specific to the transition library being
  used.

Once the arch_initialise() routine has returned, the prefix will
probably call arch_main().