Merge from Etherboot 5.4
[people/oremanj/gpxe.git] / src / arch / i386 / core / multiboot_loader.c
1 /* Multiboot support
2  *
3  * 2003-07-02 mmap fix and header probe by SONE Takeshi
4  */
5
6 struct multiboot_mods {
7         unsigned mod_start;
8         unsigned mod_end;
9         unsigned char *string;
10         unsigned reserved;
11 };
12
13 struct multiboot_mmap {
14         unsigned int size;
15         unsigned int base_addr_low;
16         unsigned int base_addr_high;
17         unsigned int length_low;
18         unsigned int length_high;
19         unsigned int type;
20 };
21
22 /* The structure of a Multiboot 0.6 parameter block.  */
23 struct multiboot_info {
24         unsigned int flags;
25 #define MULTIBOOT_MEM_VALID       0x01
26 #define MULTIBOOT_BOOT_DEV_VALID  0x02
27 #define MULTIBOOT_CMDLINE_VALID   0x04
28 #define MULTIBOOT_MODS_VALID      0x08
29 #define MULTIBOOT_AOUT_SYMS_VALID 0x10
30 #define MULTIBOOT_ELF_SYMS_VALID  0x20
31 #define MULTIBOOT_MMAP_VALID      0x40
32         unsigned int memlower;
33         unsigned int memupper;
34         unsigned int bootdev;
35         unsigned int cmdline;   /* physical address of the command line */
36         unsigned mods_count;
37         struct multiboot_mods *mods_addr;
38         unsigned syms_num;
39         unsigned syms_size;
40         unsigned syms_addr;
41         unsigned syms_shndx;
42         unsigned mmap_length;
43         unsigned  mmap_addr;
44         /* The structure actually ends here, so I might as well put
45          * the ugly e820 parameters here...
46          */
47         struct multiboot_mmap mmap[E820MAX];
48 };
49
50 /* Multiboot image header (minimal part) */
51 struct multiboot_header {
52         unsigned int magic;
53 #define MULTIBOOT_HEADER_MAGIC 0x1BADB002
54         unsigned int flags;
55         unsigned int checksum;
56 };
57
58 static struct multiboot_header *mbheader;
59 static unsigned int mbimgoffset, mboffset;
60 static unsigned char mbbuffer[12];
61
62 static struct multiboot_info mbinfo;
63
64 static void multiboot_init(void)
65 {
66         mbheader = NULL;
67         mbimgoffset = 0;
68         mboffset = 0;
69 }
70
71 /* Remember this probing function is actually different from the usual probing
72  * functions, since the Multiboot header is somewhere in the first 8KB of the
73  * image and it is byte aligned, but there is not much more known about how to
74  * find it.  In the Etherboot context the most complicated issue is that the
75  * image has to be processed block-by-block, with unknown block size and no
76  * guarantees about block alignment with respect to the image.  */
77 static void multiboot_peek(unsigned char *data, int len)
78 {
79     struct multiboot_header *h;
80
81         /* If we have already searched the first 8KB of the image or if we have
82          * already found a valid Multiboot header, skip this code.  */
83     if ((mboffset == 12) || (mbimgoffset >= 8192))
84                 return;
85
86         if (mbimgoffset + len >= 8192)
87             len = 8192 - mbimgoffset;
88
89         /* This piece of code is pretty stupid, since it always copies data, even
90          * if it is word aligned.  This shouldn't matter too much on platforms that
91          * use the Multiboot spec, since the processors are usually reasonably fast
92          * and this code is only executed for the first 8KB of the image.  Feel
93          * free to improve it, but be prepared to write quite a lot of code that
94          * deals with non-aligned data with respect to the image to load.  */
95         while (len > 0) {
96                 mbimgoffset++;
97                 memcpy(mbbuffer + mboffset, data, 1);
98                 mboffset++;
99                 data++;
100                 len--;
101                 if (mboffset == 4) {
102                         /* Accumulated a word into the buffer.  */
103                         h = (struct multiboot_header *)mbbuffer;
104                         if (h->magic != MULTIBOOT_HEADER_MAGIC) {
105                                 /* Wrong magic, this cannot be the start of the header.  */
106                                 mboffset = 0;
107                         }
108                 } else if (mboffset == 12) {
109                         /* Accumulated the minimum header data into the buffer.  */
110                         h = (struct multiboot_header *)mbbuffer;
111                         if (h->magic + h->flags + h->checksum != 0) {
112                                 /* Checksum error, not a valid header.  Check for a possible
113                                  * header starting in the current flag/checksum field.  */
114                                 if (h->flags == MULTIBOOT_HEADER_MAGIC) {
115                                         mboffset -= 4;
116                                         memmove(mbbuffer, mbbuffer + 4, mboffset);
117                                 } else if (h->checksum == MULTIBOOT_HEADER_MAGIC) {
118                                         mboffset -= 8;
119                                         memmove(mbbuffer, mbbuffer + 8, mboffset);
120                                 } else {
121                                         mboffset = 0;
122                                 }
123                         } else {
124                             printf("Multiboot... ");
125                             mbheader = h;
126                                 if ((h->flags & 0xfffc) != 0) {
127                                         printf("\nERROR: Unsupported Multiboot requirements flags\n");
128                                         longjmp(restart_etherboot, -2);
129                                 }
130                                 break;
131                         }
132                 }
133         }
134         mbimgoffset += len;
135 }
136
137 static inline void multiboot_boot(unsigned long entry)
138 {
139         unsigned char cmdline[512], *c;
140         int i;
141         if (!mbheader)
142                 return;
143         /* Etherboot limits the command line to the kernel name,
144          * default parameters and user prompted parameters.  All of
145          * them are shorter than 256 bytes.  As the kernel name and
146          * the default parameters come from the same BOOTP/DHCP entry
147          * (or if they don't, the parameters are empty), only two
148          * strings of the maximum size are possible.  Note this buffer
149          * can overrun if a stupid file name is chosen.  Oh well.  */
150         c = cmdline;
151         for (i = 0; KERNEL_BUF[i] != '\0'; i++) {
152                 switch (KERNEL_BUF[i]) {
153                 case ' ':
154                 case '\\':
155                 case '"':
156                         *c++ = '\\';
157                         break;
158                 default:
159                         break;
160                 }
161                 *c++ = KERNEL_BUF[i];
162         }
163         if (addparam != NULL) {
164                 *c++ = ' ';
165                 memcpy(c, addparam, addparamlen);
166                 c += addparamlen;
167         }
168         (void)sprintf(c, " -retaddr %#lX", virt_to_phys(xend32));
169
170         mbinfo.flags = MULTIBOOT_MMAP_VALID | MULTIBOOT_MEM_VALID |MULTIBOOT_CMDLINE_VALID;
171         mbinfo.memlower = meminfo.basememsize;
172         mbinfo.memupper = meminfo.memsize;
173         mbinfo.bootdev = 0;     /* not booted from disk */
174         mbinfo.cmdline = virt_to_phys(cmdline);
175         for (i = 0; i < (int) meminfo.map_count; i++) {
176                 mbinfo.mmap[i].size = sizeof(struct multiboot_mmap)
177                     - sizeof(unsigned int);
178                 mbinfo.mmap[i].base_addr_low = 
179                     (unsigned int) meminfo.map[i].addr;
180                 mbinfo.mmap[i].base_addr_high = 
181                     (unsigned int) (meminfo.map[i].addr >> 32);
182                 mbinfo.mmap[i].length_low = 
183                     (unsigned int) meminfo.map[i].size;
184                 mbinfo.mmap[i].length_high = 
185                     (unsigned int) (meminfo.map[i].size >> 32);
186                 mbinfo.mmap[i].type = meminfo.map[i].type;
187         }
188         mbinfo.mmap_length = meminfo.map_count * sizeof(struct multiboot_mmap);
189         mbinfo.mmap_addr = virt_to_phys(mbinfo.mmap);
190         
191         /* The Multiboot 0.6 spec requires all segment registers to be
192          * loaded with an unrestricted, writeable segment.
193          * xstart32 does this for us.
194          */
195         
196         /* Start the kernel, passing the Multiboot information record
197          * and the magic number.  */
198         os_regs.eax = 0x2BADB002;
199         os_regs.ebx = virt_to_phys(&mbinfo);
200         xstart32(entry);
201         /* A Multiboot kernel by default never returns - there is nothing in the
202          * specification about what happens to the boot loader after the kernel has
203          * been started.  Thus if the kernel returns it is definitely aware of the
204          * semantics involved (i.e. the "-retaddr" parameter).  Do not treat this
205          * as an error, but restart with a fresh DHCP request in order to activate
206          * the menu again in case one is used.  */
207         longjmp(restart_etherboot, 2);
208 }