summaryrefslogtreecommitdiff
path: root/gpxe/src/arch/i386/core/freebsd_loader.c
blob: 464f6d9392e017ce458ef3bfcf2649002167a3f9 (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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/* bootinfo */
#define BOOTINFO_VERSION 1
#define NODEV           (-1)    /* non-existent device */
#define PAGE_SHIFT      12              /* LOG2(PAGE_SIZE) */
#define PAGE_SIZE       (1<<PAGE_SHIFT) /* bytes/page */
#define PAGE_MASK       (PAGE_SIZE-1)
#define N_BIOS_GEOM     8

struct bootinfo {
        unsigned int            bi_version;
        const unsigned char     *bi_kernelname;
        struct nfs_diskless     *bi_nfs_diskless;
                                /* End of fields that are always present. */
#define bi_endcommon            bi_n_bios_used
        unsigned int            bi_n_bios_used;
        unsigned long           bi_bios_geom[N_BIOS_GEOM];
        unsigned int            bi_size;
        unsigned char           bi_memsizes_valid;
        unsigned char           bi_pad[3];
        unsigned long           bi_basemem;
        unsigned long           bi_extmem;
        unsigned long           bi_symtab;
        unsigned long           bi_esymtab;
	/* Note that these are in the FreeBSD headers but were not here... */
	unsigned long           bi_kernend;		/* end of kernel space */
	unsigned long           bi_envp;		/* environment */
	unsigned long           bi_modulep;		/* preloaded modules */
};

static struct bootinfo bsdinfo;

#ifdef ELF_IMAGE
static Elf32_Shdr *shdr;	/* To support the FreeBSD kludge! */
static Address symtab_load;
static Address symstr_load;
static int symtabindex;
static int symstrindex;
#endif

static enum {
	Unknown, Tagged, Aout, Elf, Aout_FreeBSD, Elf_FreeBSD,
} image_type = Unknown;

static unsigned int off;


#ifdef ELF_IMAGE
static void elf_freebsd_probe(void)
{
	image_type = Elf;
	if (	(estate.e.elf32.e_entry & 0xf0000000) && 
		(estate.e.elf32.e_type == ET_EXEC))
	{
		image_type = Elf_FreeBSD;
		printf("/FreeBSD");
		off = -(estate.e.elf32.e_entry & 0xff000000);
		estate.e.elf32.e_entry += off;
	}
	/* Make sure we have a null to start with... */
	shdr = 0;
	
	/* Clear the symbol index values... */
	symtabindex = -1;
	symstrindex = -1;
	
	/* ...and the load addresses of the symbols  */
	symtab_load = 0;
	symstr_load = 0;
}

static void elf_freebsd_fixup_segment(void)
{
	if (image_type == Elf_FreeBSD) {
		estate.p.phdr32[estate.segment].p_paddr += off;
	}
}

static void elf_freebsd_find_segment_end(void)
{
	/* Count the bytes read even for the last block
	 * as we will need to know where the last block
	 * ends in order to load the symbols correctly.
	 * (plus it could be useful elsewhere...)
	 * Note that we need to count the actual size,
	 * not just the end of the disk image size.
	 */
	estate.curaddr += 
		(estate.p.phdr32[estate.segment].p_memsz - 
		estate.p.phdr32[estate.segment].p_filesz);
}

static int elf_freebsd_debug_loader(unsigned int offset)
{
	/* No more segments to be loaded - time to start the
	 * nasty state machine to support the loading of
	 * FreeBSD debug symbols due to the fact that FreeBSD
	 * uses/exports the kernel's debug symbols in order
	 * to make much of the system work!  Amazing (arg!)
	 *
	 * We depend on the fact that for the FreeBSD kernel,
	 * there is only one section of debug symbols and that
	 * the section is after all of the loaded sections in
	 * the file.  This assumes a lot but is somewhat required
	 * to make this code not be too annoying.  (Where do you
	 * load symbols when the code has not loaded yet?)
	 * Since this function is actually just a callback from
	 * the network data transfer code, we need to be able to
	 * work with the data as it comes in.  There is no chance
	 * for doing a seek other than forwards.
	 *
	 * The process we use is to first load the section
	 * headers.  Once they are loaded (shdr != 0) we then
	 * look for where the symbol table and symbol table
	 * strings are and setup some state that we found
	 * them and fall into processing the first one (which
	 * is the symbol table) and after that has been loaded,
	 * we try the symbol strings.  Note that the order is
	 * actually required as the memory image depends on
	 * the symbol strings being loaded starting at the
	 * end of the symbol table.  The kernel assumes this
	 * layout of the image.
	 *
	 * At any point, if we get to the end of the load file
	 * or the section requested is earlier in the file than
	 * the current file pointer, we just end up falling
	 * out of this and booting the kernel without this
	 * information.
	 */

	/* Make sure that the next address is long aligned... */
	/* Assumes size of long is a power of 2... */
	estate.curaddr = (estate.curaddr + sizeof(long) - 1) & ~(sizeof(long) - 1);
	
	/* If we have not yet gotten the shdr loaded, try that */
	if (shdr == 0)
	{
		estate.toread = estate.e.elf32.e_shnum * estate.e.elf32.e_shentsize;
		estate.skip = estate.e.elf32.e_shoff - (estate.loc + offset);
		if (estate.toread)
		{
#if ELF_DEBUG
			printf("shdr *, size %lX, curaddr %lX\n", 
				estate.toread, estate.curaddr);
#endif
			
			/* Start reading at the curaddr and make that the shdr */
			shdr = (Elf32_Shdr *)phys_to_virt(estate.curaddr);
			
			/* Start to read... */
			return 1;
		}
	}
	else
	{
		/* We have the shdr loaded, check if we have found
		 * the indexs where the symbols are supposed to be */
		if ((symtabindex == -1) && (symstrindex == -1))
		{
			int i;
			/* Make sure that the address is page aligned... */
			/* Symbols need to start in their own page(s)... */
			estate.curaddr = (estate.curaddr + 4095) & ~4095;
			
			/* Need to make new indexes... */
			for (i=0; i < estate.e.elf32.e_shnum; i++)
			{
				if (shdr[i].sh_type == SHT_SYMTAB)
				{
					int j;
					for (j=0; j < estate.e.elf32.e_phnum; j++)
					{
						/* Check only for loaded sections */
						if ((estate.p.phdr32[j].p_type | 0x80) == (PT_LOAD | 0x80))
						{
							/* Only the extra symbols */
							if ((shdr[i].sh_offset >= estate.p.phdr32[j].p_offset) &&
								((shdr[i].sh_offset + shdr[i].sh_size) <=
									(estate.p.phdr32[j].p_offset + estate.p.phdr32[j].p_filesz)))
							{
								shdr[i].sh_offset=0;
								shdr[i].sh_size=0;
								break;
							}
						}
					}
					if ((shdr[i].sh_offset != 0) && (shdr[i].sh_size != 0))
					{
						symtabindex = i;
						symstrindex = shdr[i].sh_link;
					}
				}
			}
		}
		
		/* Check if we have a symbol table index and have not loaded it */
		if ((symtab_load == 0) && (symtabindex >= 0))
		{
			/* No symbol table yet?  Load it first... */
			
			/* This happens to work out in a strange way.
			 * If we are past the point in the file already,
			 * we will skip a *large* number of bytes which
			 * ends up bringing us to the end of the file and
			 * an old (default) boot.  Less code and lets
			 * the state machine work in a cleaner way but this
			 * is a nasty side-effect trick... */
			estate.skip = shdr[symtabindex].sh_offset - (estate.loc + offset);
			
			/* And we need to read this many bytes... */
			estate.toread = shdr[symtabindex].sh_size;
			
			if (estate.toread)
			{
#if ELF_DEBUG
				printf("db sym, size %lX, curaddr %lX\n", 
					estate.toread, estate.curaddr);
#endif
				/* Save where we are loading this... */
				symtab_load = estate.curaddr;
				
				*((long *)phys_to_virt(estate.curaddr)) = estate.toread;
				estate.curaddr += sizeof(long);
				
				/* Start to read... */
				return 1;
			}
		}
		else if ((symstr_load == 0) && (symstrindex >= 0))
		{
			/* We have already loaded the symbol table, so
			 * now on to the symbol strings... */
			
			
			/* Same nasty trick as above... */
			estate.skip = shdr[symstrindex].sh_offset - (estate.loc + offset);
			
			/* And we need to read this many bytes... */
			estate.toread = shdr[symstrindex].sh_size;
			
			if (estate.toread)
			{
#if ELF_DEBUG
				printf("db str, size %lX, curaddr %lX\n", 
					estate.toread, estate.curaddr);
#endif
				/* Save where we are loading this... */
				symstr_load = estate.curaddr;
				
				*((long *)phys_to_virt(estate.curaddr)) = estate.toread;
				estate.curaddr += sizeof(long);
				
				/* Start to read... */
				return 1;
			}
		}
	}
	/* all done */
	return 0;
}

static void elf_freebsd_boot(unsigned long entry) 
{
	if (image_type != Elf_FreeBSD)
		return;

	memset(&bsdinfo, 0, sizeof(bsdinfo));
	bsdinfo.bi_basemem = meminfo.basememsize;
	bsdinfo.bi_extmem = meminfo.memsize;
	bsdinfo.bi_memsizes_valid = 1;
	bsdinfo.bi_version = BOOTINFO_VERSION;
	bsdinfo.bi_kernelname = virt_to_phys(KERNEL_BUF);
	bsdinfo.bi_nfs_diskless = NULL;
	bsdinfo.bi_size = sizeof(bsdinfo);
#define RB_BOOTINFO     0x80000000      /* have `struct bootinfo *' arg */  
	if(freebsd_kernel_env[0] != '\0'){
		freebsd_howto |= RB_BOOTINFO;
		bsdinfo.bi_envp = (unsigned long)freebsd_kernel_env;
	}
	
	/* Check if we have symbols loaded, and if so,
	 * made the meta_data needed to pass those to
	 * the kernel. */
	if ((symtab_load !=0) && (symstr_load != 0))
	{
		unsigned long *t;
		
		bsdinfo.bi_symtab = symtab_load;
		
		/* End of symbols (long aligned...) */
		/* Assumes size of long is a power of 2... */
		bsdinfo.bi_esymtab = (symstr_load +
			sizeof(long) +
			*((long *)phys_to_virt(symstr_load)) +
			sizeof(long) - 1) & ~(sizeof(long) - 1);
		
		/* Where we will build the meta data... */
		t = phys_to_virt(bsdinfo.bi_esymtab);
		
#if ELF_DEBUG
		printf("Metadata at %lX\n",t);
#endif
		
		/* Set up the pointer to the memory... */
		bsdinfo.bi_modulep = virt_to_phys(t);
		
		/* The metadata structure is an array of 32-bit
		 * words where we store some information about the
		 * system.  This is critical, as FreeBSD now looks
		 * only for the metadata for the extended symbol
		 * information rather than in the bootinfo.
		 */
		/* First, do the kernel name and the kernel type */
		/* Note that this assumed x86 byte order... */
		
		/* 'kernel\0\0' */
		*t++=MODINFO_NAME; *t++= 7; *t++=0x6E72656B; *t++=0x00006C65;
		
		/* 'elf kernel\0\0' */
		*t++=MODINFO_TYPE; *t++=11; *t++=0x20666C65; *t++=0x6E72656B; *t++ = 0x00006C65;
		
		/* Now the symbol start/end - note that they are
		 * here in local/physical address - the Kernel
		 * boot process will relocate the addresses. */
		*t++=MODINFOMD_SSYM | MODINFO_METADATA; *t++=sizeof(*t); *t++=bsdinfo.bi_symtab;
		*t++=MODINFOMD_ESYM | MODINFO_METADATA; *t++=sizeof(*t); *t++=bsdinfo.bi_esymtab;
		
		*t++=MODINFO_END; *t++=0; /* end of metadata */
		
		/* Since we have symbols we need to make
		 * sure that the kernel knows its own end
		 * of memory...  It is not _end but after
		 * the symbols and the metadata... */
		bsdinfo.bi_kernend = virt_to_phys(t);
		
		/* Signal locore.s that we have a valid bootinfo
		 * structure that was completely filled in. */
		freebsd_howto |= 0x80000000;
	}
	
	xstart32(entry, freebsd_howto, NODEV, 0, 0, 0, 
		virt_to_phys(&bsdinfo), 0, 0, 0);
	longjmp(restart_etherboot, -2);
}
#endif

#ifdef AOUT_IMAGE
static void aout_freebsd_probe(void)
{
	image_type = Aout;
	if (((astate.head.a_midmag >> 16) & 0xffff) == 0) {
		/* Some other a.out variants have a different
		 * value, and use other alignments (e.g. 1K),
		 * not the 4K used by FreeBSD.  */
		image_type = Aout_FreeBSD;
		printf("/FreeBSD");
		off = -(astate.head.a_entry & 0xff000000);
		astate.head.a_entry += off;
	}
}

static void aout_freebsd_boot(void)
{
	if (image_type == Aout_FreeBSD) {
		memset(&bsdinfo, 0, sizeof(bsdinfo));
		bsdinfo.bi_basemem = meminfo.basememsize;
		bsdinfo.bi_extmem = meminfo.memsize;
		bsdinfo.bi_memsizes_valid = 1;
		bsdinfo.bi_version = BOOTINFO_VERSION;
		bsdinfo.bi_kernelname = virt_to_phys(KERNEL_BUF);
		bsdinfo.bi_nfs_diskless = NULL;
		bsdinfo.bi_size = sizeof(bsdinfo);
		xstart32(astate.head.a_entry, freebsd_howto, NODEV, 0, 0, 0, 
			virt_to_phys(&bsdinfo), 0, 0, 0);
		longjmp(restart_etherboot, -2);
	}
}
#endif