metadata.c revision 176489
1/*-
2 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3 * Copyright (C) 2007-2008 Semihalf, Piotr Kruszynski <ppk@semihalf.com>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD: head/sys/boot/powerpc/uboot/metadata.c 176489 2008-02-23 19:43:29Z marcel $");
30
31#include <stand.h>
32#include <sys/param.h>
33#include <sys/reboot.h>
34#include <sys/linker.h>
35
36#include <machine/elf.h>
37#include <machine/metadata.h>
38#include <machine/bootinfo.h>
39
40#include "api_public.h"
41#include "bootstrap.h"
42#include "glue.h"
43
44/*
45 * Return a 'boothowto' value corresponding to the kernel arguments in
46 * (kargs) and any relevant environment variables.
47 */
48static struct
49{
50	const char	*ev;
51	int		mask;
52} howto_names[] = {
53	{"boot_askname",	RB_ASKNAME},
54	{"boot_cdrom",		RB_CDROM},
55	{"boot_ddb",		RB_KDB},
56	{"boot_dfltroot",	RB_DFLTROOT},
57	{"boot_gdb",		RB_GDB},
58	{"boot_multicons",	RB_MULTIPLE},
59	{"boot_mute",		RB_MUTE},
60	{"boot_pause",		RB_PAUSE},
61	{"boot_serial",		RB_SERIAL},
62	{"boot_single",		RB_SINGLE},
63	{"boot_verbose",	RB_VERBOSE},
64	{NULL,			0}
65};
66
67static int
68md_getboothowto(char *kargs)
69{
70	char	*cp;
71	int	howto;
72	int	active;
73	int	i;
74
75	/* Parse kargs */
76	howto = 0;
77	if (kargs != NULL) {
78		cp = kargs;
79		active = 0;
80		while (*cp != 0) {
81			if (!active && (*cp == '-'))
82				active = 1;
83			else if (active)
84				switch (*cp) {
85				case 'a':
86					howto |= RB_ASKNAME;
87					break;
88				case 'C':
89					howto |= RB_CDROM;
90					break;
91				case 'd':
92					howto |= RB_KDB;
93					break;
94				case 'D':
95					howto |= RB_MULTIPLE;
96					break;
97				case 'm':
98					howto |= RB_MUTE;
99					break;
100				case 'g':
101					howto |= RB_GDB;
102					break;
103				case 'h':
104					howto |= RB_SERIAL;
105					break;
106				case 'p':
107					howto |= RB_PAUSE;
108					break;
109				case 'r':
110					howto |= RB_DFLTROOT;
111					break;
112				case 's':
113					howto |= RB_SINGLE;
114					break;
115				case 'v':
116					howto |= RB_VERBOSE;
117					break;
118				default:
119					active = 0;
120					break;
121				}
122				cp++;
123		}
124	}
125
126	/* get equivalents from the environment */
127	for (i = 0; howto_names[i].ev != NULL; i++) {
128		if (getenv(howto_names[i].ev) != NULL)
129			howto |= howto_names[i].mask;
130	}
131	if (!strcmp(getenv("console"), "comconsole"))
132		howto |= RB_SERIAL;
133	if (!strcmp(getenv("console"), "nullconsole"))
134		howto |= RB_MUTE;
135
136	return(howto);
137}
138
139/*
140 * Copy the environment into the load area starting at (addr).
141 * Each variable is formatted as <name>=<value>, with a single nul
142 * separating each variable, and a double nul terminating the environment.
143 */
144static vm_offset_t
145md_copyenv(vm_offset_t addr)
146{
147	struct env_var	*ep;
148
149	/* traverse the environment */
150	for (ep = environ; ep != NULL; ep = ep->ev_next) {
151		archsw.arch_copyin(ep->ev_name, addr, strlen(ep->ev_name));
152		addr += strlen(ep->ev_name);
153		archsw.arch_copyin("=", addr, 1);
154		addr++;
155		if (ep->ev_value != NULL) {
156			archsw.arch_copyin(ep->ev_value, addr,
157			    strlen(ep->ev_value));
158			addr += strlen(ep->ev_value);
159		}
160		archsw.arch_copyin("", addr, 1);
161		addr++;
162	}
163	archsw.arch_copyin("", addr, 1);
164	addr++;
165	return(addr);
166}
167
168/*
169 * Copy module-related data into the load area, where it can be
170 * used as a directory for loaded modules.
171 *
172 * Module data is presented in a self-describing format.  Each datum
173 * is preceded by a 32-bit identifier and a 32-bit size field.
174 *
175 * Currently, the following data are saved:
176 *
177 * MOD_NAME	(variable)		module name (string)
178 * MOD_TYPE	(variable)		module type (string)
179 * MOD_ARGS	(variable)		module parameters (string)
180 * MOD_ADDR	sizeof(vm_offset_t)	module load address
181 * MOD_SIZE	sizeof(size_t)		module size
182 * MOD_METADATA	(variable)		type-specific metadata
183 */
184#define	COPY32(v, a, c) {			\
185    u_int32_t	x = (v);			\
186    if (c)					\
187	archsw.arch_copyin(&x, a, sizeof(x));	\
188    a += sizeof(x);				\
189}
190
191#define	MOD_STR(t, a, s, c) {			\
192    COPY32(t, a, c);				\
193    COPY32(strlen(s) + 1, a, c)			\
194    if (c)					\
195	archsw.arch_copyin(s, a, strlen(s) + 1);\
196    a += roundup(strlen(s) + 1, sizeof(u_long));\
197}
198
199#define	MOD_NAME(a, s, c)	MOD_STR(MODINFO_NAME, a, s, c)
200#define	MOD_TYPE(a, s, c)	MOD_STR(MODINFO_TYPE, a, s, c)
201#define	MOD_ARGS(a, s, c)	MOD_STR(MODINFO_ARGS, a, s, c)
202
203#define	MOD_VAR(t, a, s, c) {			\
204    COPY32(t, a, c);				\
205    COPY32(sizeof(s), a, c);			\
206    if (c)					\
207	archsw.arch_copyin(&s, a, sizeof(s));	\
208    a += roundup(sizeof(s), sizeof(u_long));	\
209}
210
211#define	MOD_ADDR(a, s, c)	MOD_VAR(MODINFO_ADDR, a, s, c)
212#define	MOD_SIZE(a, s, c)	MOD_VAR(MODINFO_SIZE, a, s, c)
213
214#define	MOD_METADATA(a, mm, c) {		\
215    COPY32(MODINFO_METADATA | mm->md_type, a, c);\
216    COPY32(mm->md_size, a, c);			\
217    if (c)					\
218	archsw.arch_copyin(mm->md_data, a, mm->md_size);\
219    a += roundup(mm->md_size, sizeof(u_long));	\
220}
221
222#define	MOD_END(a, c) {				\
223    COPY32(MODINFO_END, a, c);			\
224    COPY32(0, a, c);				\
225}
226
227static vm_offset_t
228md_copymodules(vm_offset_t addr)
229{
230	struct preloaded_file	*fp;
231	struct file_metadata	*md;
232	int			c;
233
234	c = addr != 0;
235	/* start with the first module on the list, should be the kernel */
236	for (fp = file_findfile(NULL, NULL); fp != NULL; fp = fp->f_next) {
237
238		MOD_NAME(addr, fp->f_name, c);	/* this field must be first */
239		MOD_TYPE(addr, fp->f_type, c);
240		if (fp->f_args)
241			MOD_ARGS(addr, fp->f_args, c);
242		MOD_ADDR(addr, fp->f_addr, c);
243		MOD_SIZE(addr, fp->f_size, c);
244		for (md = fp->f_metadata; md != NULL; md = md->md_next) {
245			if (!(md->md_type & MODINFOMD_NOCOPY))
246				MOD_METADATA(addr, md, c);
247		}
248	}
249	MOD_END(addr, c);
250	return(addr);
251}
252
253/*
254 * Prepare the bootinfo structure. Put a ptr to the allocated struct in addr,
255 * return size.
256 */
257static int
258md_bootinfo(struct bootinfo **addr)
259{
260#define	TMP_MAX_ETH	8
261#define	TMP_MAX_MR	8
262	struct bootinfo		*bi;
263	struct bi_mem_region	tmp_mr[TMP_MAX_MR];
264	struct bi_eth_addr	tmp_eth[TMP_MAX_ETH];
265	struct sys_info		*si;
266	char			*str, *end;
267	const char		*env;
268	void			*ptr;
269	u_int8_t		tmp_addr[6];
270	int			i, mr_no, eth_no, size;
271
272	if ((si = ub_get_sys_info()) == NULL)
273		panic("can't retrieve U-Boot sysinfo");
274
275	/*
276	 * Handle mem regions (we only care about DRAM)
277	 */
278	for (i = 0, mr_no = 0; i < si->mr_no; i++) {
279		if (si->mr[i].flags == MR_ATTR_DRAM) {
280			if (mr_no >= TMP_MAX_MR) {
281				printf("too many memory regions: %d\n", mr_no);
282				break;
283			}
284			tmp_mr[mr_no].mem_base = si->mr[i].start;
285			tmp_mr[mr_no].mem_size = si->mr[i].size;
286			mr_no++;
287			continue;
288		}
289	}
290	if (mr_no == 0)
291		panic("can't retrieve RAM info");
292
293	size = (mr_no * sizeof(struct bi_mem_region) - sizeof(bi->bi_data));
294
295	/*
296	 * Handle Ethernet addresses: parse u-boot env for eth%daddr
297	 */
298	env = NULL;
299	eth_no = 0;
300	while ((env = ub_env_enum(env)) != NULL) {
301		if (strncmp(env, "eth", 3) == 0 &&
302		    strncmp(env + (strlen(env) - 4), "addr", 4) == 0) {
303
304			str = ub_env_get(env);
305			for (i = 0; i < 6; i++) {
306				tmp_addr[i] = str ? strtol(str, &end, 16) : 0;
307				if (str)
308					str = (*end) ? end + 1 : end;
309
310				tmp_eth[eth_no].mac_addr[i] = tmp_addr[i];
311			}
312			eth_no++;
313		}
314	}
315
316	size += (eth_no * sizeof(struct bi_eth_addr)) + sizeof(struct bootinfo);
317
318	/*
319	 * Once its whole size is calculated, allocate space for the bootinfo
320	 * and copy over the contents from temp containers.
321	 */
322	if ((bi = malloc(size)) == NULL)
323		panic("can't allocate mem for bootinfo");
324
325	ptr = (struct bi_mem_region *)bi->bi_data;
326	bcopy(tmp_mr, ptr, mr_no * sizeof(struct bi_mem_region));
327	ptr += mr_no * sizeof(struct bi_mem_region);
328	bcopy(tmp_eth, ptr, eth_no * sizeof(struct bi_eth_addr));
329
330	bi->bi_mem_reg_no = mr_no;
331	bi->bi_eth_addr_no = eth_no;
332	bi->bi_version = BI_VERSION;
333	bi->bi_bar_base = si->bar;
334	bi->bi_cpu_clk = si->clk_cpu;
335	bi->bi_bus_clk = si->clk_bus;
336
337	*addr = bi;
338
339	return (size);
340}
341
342/*
343 * Load the information expected by a powerpc kernel.
344 *
345 * - The 'boothowto' argument is constructed
346 * - The 'bootdev' argument is constructed
347 * - The kernel environment is copied into kernel space.
348 * - Module metadata are formatted and placed in kernel space.
349 */
350int
351md_load(char *args, vm_offset_t *modulep)
352{
353	struct preloaded_file	*kfp;
354	struct preloaded_file	*xp;
355	struct file_metadata	*md;
356	struct bootinfo		*bip;
357	vm_offset_t		kernend;
358	vm_offset_t		addr;
359	vm_offset_t		envp;
360	vm_offset_t		size;
361	vm_offset_t		vaddr;
362	char			*rootdevname;
363	int			howto;
364	int			bisize;
365	int			i;
366
367	/*
368	 * These metadata addreses must be converted for kernel after
369	 * relocation.
370	 */
371	uint32_t		mdt[] = {
372	    MODINFOMD_SSYM, MODINFOMD_ESYM, MODINFOMD_KERNEND, MODINFOMD_ENVP
373	};
374
375	howto = md_getboothowto(args);
376
377	/*
378	 * Allow the environment variable 'rootdev' to override the supplied
379	 * device. This should perhaps go to MI code and/or have $rootdev
380	 * tested/set by MI code before launching the kernel.
381	 */
382	rootdevname = getenv("rootdev");
383	if (rootdevname == NULL)
384		rootdevname = getenv("currdev");
385	/* Try reading the /etc/fstab file to select the root device */
386	getrootmount(rootdevname);
387
388	/* find the last module in the chain */
389	addr = 0;
390	for (xp = file_findfile(NULL, NULL); xp != NULL; xp = xp->f_next) {
391		if (addr < (xp->f_addr + xp->f_size))
392			addr = xp->f_addr + xp->f_size;
393	}
394	/* pad to a page boundary */
395	addr = roundup(addr, PAGE_SIZE);
396
397	/* copy our environment */
398	envp = addr;
399	addr = md_copyenv(addr);
400
401	/* pad to a page boundary */
402	addr = roundup(addr, PAGE_SIZE);
403
404	/* prepare bootinfo */
405	bisize = md_bootinfo(&bip);
406
407	kernend = 0;
408	kfp = file_findfile(NULL, "elf32 kernel");
409	if (kfp == NULL)
410		kfp = file_findfile(NULL, "elf kernel");
411	if (kfp == NULL)
412		panic("can't find kernel file");
413	file_addmetadata(kfp, MODINFOMD_HOWTO, sizeof howto, &howto);
414	file_addmetadata(kfp, MODINFOMD_BOOTINFO, bisize, bip);
415	file_addmetadata(kfp, MODINFOMD_ENVP, sizeof envp, &envp);
416	file_addmetadata(kfp, MODINFOMD_KERNEND, sizeof kernend, &kernend);
417
418	*modulep = addr;
419	size = md_copymodules(0);
420	kernend = roundup(addr + size, PAGE_SIZE);
421
422	md = file_findmetadata(kfp, MODINFOMD_KERNEND);
423	bcopy(&kernend, md->md_data, sizeof kernend);
424
425	/* Convert addresses to the final VA */
426	*modulep -= __elfN(relocation_offset);
427
428	for (i = 0; i < sizeof mdt / sizeof mdt[0]; i++) {
429		md = file_findmetadata(kfp, mdt[i]);
430		if (md) {
431			bcopy(md->md_data, &vaddr, sizeof vaddr);
432			vaddr -= __elfN(relocation_offset);
433			bcopy(&vaddr, md->md_data, sizeof vaddr);
434		}
435	}
436	(void)md_copymodules(addr);
437
438	return(0);
439}
440