1/*-
2 * Copyright (c) 2000 Benno Rice <benno@jeamland.net>
3 * Copyright (c) 2000 Stephane Potvin <sepotvin@videotron.ca>
4 * Copyright (c) 2007-2008 Semihalf, Rafal Jaworowski <raj@semihalf.com>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31#include <sys/param.h>
32
33#include <stand.h>
34
35#include "api_public.h"
36#include "bootstrap.h"
37#include "glue.h"
38#include "libuboot.h"
39
40#ifndef nitems
41#define	nitems(x)	(sizeof((x)) / sizeof((x)[0]))
42#endif
43
44#ifndef HEAP_SIZE
45#define	HEAP_SIZE	(2 * 1024 * 1024)
46#endif
47
48struct uboot_devdesc currdev;
49struct arch_switch archsw;		/* MI/MD interface boundary */
50int devs_no;
51
52uintptr_t uboot_heap_start;
53uintptr_t uboot_heap_end;
54
55struct device_type {
56	const char *name;
57	int type;
58} device_types[] = {
59	{ "disk", DEV_TYP_STOR },
60	{ "ide",  DEV_TYP_STOR | DT_STOR_IDE },
61	{ "mmc",  DEV_TYP_STOR | DT_STOR_MMC },
62	{ "sata", DEV_TYP_STOR | DT_STOR_SATA },
63	{ "scsi", DEV_TYP_STOR | DT_STOR_SCSI },
64	{ "usb",  DEV_TYP_STOR | DT_STOR_USB },
65	{ "net",  DEV_TYP_NET }
66};
67
68extern char end[];
69
70extern unsigned char _etext[];
71extern unsigned char _edata[];
72extern unsigned char __bss_start[];
73extern unsigned char __sbss_start[];
74extern unsigned char __sbss_end[];
75extern unsigned char _end[];
76
77#ifdef LOADER_FDT_SUPPORT
78extern int command_fdt_internal(int argc, char *argv[]);
79#endif
80
81static void
82dump_sig(struct api_signature *sig)
83{
84#ifdef DEBUG
85	printf("signature:\n");
86	printf("  version\t= %d\n", sig->version);
87	printf("  checksum\t= 0x%08x\n", sig->checksum);
88	printf("  sc entry\t= 0x%08x\n", sig->syscall);
89#endif
90}
91
92static void
93dump_addr_info(void)
94{
95#ifdef DEBUG
96	printf("\naddresses info:\n");
97	printf(" _etext (sdata) = 0x%08x\n", (uint32_t)_etext);
98	printf(" _edata         = 0x%08x\n", (uint32_t)_edata);
99	printf(" __sbss_start   = 0x%08x\n", (uint32_t)__sbss_start);
100	printf(" __sbss_end     = 0x%08x\n", (uint32_t)__sbss_end);
101	printf(" __sbss_start   = 0x%08x\n", (uint32_t)__bss_start);
102	printf(" _end           = 0x%08x\n", (uint32_t)_end);
103	printf(" syscall entry  = 0x%08x\n", (uint32_t)syscall_ptr);
104#endif
105}
106
107static uint64_t
108memsize(struct sys_info *si, int flags)
109{
110	uint64_t size;
111	int i;
112
113	size = 0;
114	for (i = 0; i < si->mr_no; i++)
115		if (si->mr[i].flags == flags && si->mr[i].size)
116			size += (si->mr[i].size);
117
118	return (size);
119}
120
121static void
122meminfo(void)
123{
124	uint64_t size;
125	struct sys_info *si;
126	int t[3] = { MR_ATTR_DRAM, MR_ATTR_FLASH, MR_ATTR_SRAM };
127	int i;
128
129	if ((si = ub_get_sys_info()) == NULL)
130		panic("could not retrieve system info");
131
132	for (i = 0; i < 3; i++) {
133		size = memsize(si, t[i]);
134		if (size > 0)
135			printf("%s: %juMB\n", ub_mem_type(t[i]),
136			    (uintmax_t)(size / 1024 / 1024));
137	}
138}
139
140static const char *
141get_device_type(const char *devstr, int *devtype)
142{
143	int i;
144	int namelen;
145	struct device_type *dt;
146
147	if (devstr) {
148		for (i = 0; i < nitems(device_types); i++) {
149			dt = &device_types[i];
150			namelen = strlen(dt->name);
151			if (strncmp(dt->name, devstr, namelen) == 0) {
152				*devtype = dt->type;
153				return (devstr + namelen);
154			}
155		}
156		printf("Unknown device type '%s'\n", devstr);
157	}
158
159	*devtype = DEV_TYP_NONE;
160	return (NULL);
161}
162
163static const char *
164device_typename(int type)
165{
166	int i;
167
168	for (i = 0; i < nitems(device_types); i++)
169		if (device_types[i].type == type)
170			return (device_types[i].name);
171
172	return ("<unknown>");
173}
174
175/*
176 * Parse a device string into type, unit, slice and partition numbers. A
177 * returned value of -1 for type indicates a search should be done for the
178 * first loadable device, otherwise a returned value of -1 for unit
179 * indicates a search should be done for the first loadable device of the
180 * given type.
181 *
182 * The returned values for slice and partition are interpreted by
183 * disk_open().
184 *
185 * The device string can be a standard loader(8) disk specifier:
186 *
187 * disk<unit>s<slice>              disk0s1
188 * disk<unit>s<slice><partition>   disk1s2a
189 * disk<unit>p<partition>          disk0p4
190 *
191 * or one of the following formats:
192 *
193 * Valid device strings:                     For device types:
194 *
195 * <type_name>                               DEV_TYP_STOR, DEV_TYP_NET
196 * <type_name><unit>                         DEV_TYP_STOR, DEV_TYP_NET
197 * <type_name><unit>:                        DEV_TYP_STOR, DEV_TYP_NET
198 * <type_name><unit>:<slice>                 DEV_TYP_STOR
199 * <type_name><unit>:<slice>.                DEV_TYP_STOR
200 * <type_name><unit>:<slice>.<partition>     DEV_TYP_STOR
201 *
202 * For valid type names, see the device_types array, above.
203 *
204 * Slice numbers are 1-based.  0 is a wildcard.
205 */
206static void
207get_load_device(int *type, int *unit, int *slice, int *partition)
208{
209	struct disk_devdesc dev;
210	char *devstr;
211	const char *p;
212	char *endp;
213
214	*type = DEV_TYP_NONE;
215	*unit = -1;
216	*slice = D_SLICEWILD;
217	*partition = D_PARTWILD;
218
219	devstr = ub_env_get("loaderdev");
220	if (devstr == NULL) {
221		printf("U-Boot env: loaderdev not set, will probe all devices.\n");
222		return;
223	}
224	printf("U-Boot env: loaderdev='%s'\n", devstr);
225
226	p = get_device_type(devstr, type);
227
228	/*
229	 * If type is DEV_TYP_STOR we have a disk-like device.  If the remainder
230	 * of the string contains spaces, dots, or a colon in any location other
231	 * than the last char, it's legacy format.  Otherwise it might be
232	 * standard loader(8) format (e.g., disk0s2a or mmc1p12), so try to
233	 * parse the remainder of the string as such, and if it works, return
234	 * those results. Otherwise we'll fall through to the code that parses
235	 * the legacy format.
236	 */
237	if (*type & DEV_TYP_STOR) {
238		size_t len = strlen(p);
239		if (strcspn(p, " .") == len && strcspn(p, ":") >= len - 1 &&
240		    disk_parsedev(&dev, p, NULL) == 0) {
241			*unit = dev.dd.d_unit;
242			*slice = dev.d_slice;
243			*partition = dev.d_partition;
244			return;
245		}
246	}
247
248	/* Ignore optional spaces after the device name. */
249	while (*p == ' ')
250		p++;
251
252	/* Unknown device name, or a known name without unit number.  */
253	if ((*type == DEV_TYP_NONE) || (*p == '\0')) {
254		return;
255	}
256
257	/* Malformed unit number. */
258	if (!isdigit(*p)) {
259		*type = DEV_TYP_NONE;
260		return;
261	}
262
263	/* Guaranteed to extract a number from the string, as *p is a digit. */
264	*unit = strtol(p, &endp, 10);
265	p = endp;
266
267	/* Known device name with unit number and nothing else. */
268	if (*p == '\0') {
269		return;
270	}
271
272	/* Device string is malformed beyond unit number. */
273	if (*p != ':') {
274		*type = DEV_TYP_NONE;
275		*unit = -1;
276		return;
277	}
278
279	p++;
280
281	/* No slice and partition specification. */
282	if ('\0' == *p )
283		return;
284
285	/* Only DEV_TYP_STOR devices can have a slice specification. */
286	if (!(*type & DEV_TYP_STOR)) {
287		*type = DEV_TYP_NONE;
288		*unit = -1;
289		return;
290	}
291
292	*slice = strtoul(p, &endp, 10);
293
294	/* Malformed slice number. */
295	if (p == endp) {
296		*type = DEV_TYP_NONE;
297		*unit = -1;
298		*slice = D_SLICEWILD;
299		return;
300	}
301
302	p = endp;
303
304	/* No partition specification. */
305	if (*p == '\0')
306		return;
307
308	/* Device string is malformed beyond slice number. */
309	if (*p != '.') {
310		*type = DEV_TYP_NONE;
311		*unit = -1;
312		*slice = D_SLICEWILD;
313		return;
314	}
315
316	p++;
317
318	/* No partition specification. */
319	if (*p == '\0')
320		return;
321
322	*partition = strtol(p, &endp, 10);
323	p = endp;
324
325	/*  Full, valid device string. */
326	if (*endp == '\0')
327		return;
328
329	/* Junk beyond partition number. */
330	*type = DEV_TYP_NONE;
331	*unit = -1;
332	*slice = D_SLICEWILD;
333	*partition = D_PARTWILD;
334}
335
336static void
337print_disk_probe_info()
338{
339	char slice[32];
340	char partition[32];
341
342	if (currdev.d_disk.d_slice == D_SLICENONE)
343		strlcpy(slice, "<none>", sizeof(slice));
344	else if (currdev.d_disk.d_slice == D_SLICEWILD)
345		strlcpy(slice, "<auto>", sizeof(slice));
346	else
347		snprintf(slice, sizeof(slice), "%d", currdev.d_disk.d_slice);
348
349	if (currdev.d_disk.d_partition == D_PARTNONE)
350		strlcpy(partition, "<none>", sizeof(partition));
351	else if (currdev.d_disk.d_partition == D_PARTWILD)
352		strlcpy(partition, "<auto>", sizeof(partition));
353	else
354		snprintf(partition, sizeof(partition), "%d",
355		    currdev.d_disk.d_partition);
356
357	printf("  Checking unit=%d slice=%s partition=%s...",
358	    currdev.dd.d_unit, slice, partition);
359
360}
361
362static int
363probe_disks(int devidx, int load_type, int load_unit, int load_slice,
364    int load_partition)
365{
366	int open_result, unit;
367	struct open_file f;
368
369	currdev.d_disk.d_slice = load_slice;
370	currdev.d_disk.d_partition = load_partition;
371
372	f.f_devdata = &currdev;
373	open_result = -1;
374
375	if (load_type == -1) {
376		printf("  Probing all disk devices...\n");
377		/* Try each disk in succession until one works.  */
378		for (currdev.dd.d_unit = 0; currdev.dd.d_unit < UB_MAX_DEV;
379		     currdev.dd.d_unit++) {
380			print_disk_probe_info();
381			open_result = devsw[devidx]->dv_open(&f, &currdev);
382			if (open_result == 0) {
383				printf(" good.\n");
384				return (0);
385			}
386			printf("\n");
387		}
388		return (-1);
389	}
390
391	if (load_unit == -1) {
392		printf("  Probing all %s devices...\n", device_typename(load_type));
393		/* Try each disk of given type in succession until one works. */
394		for (unit = 0; unit < UB_MAX_DEV; unit++) {
395			currdev.dd.d_unit = uboot_diskgetunit(load_type, unit);
396			if (currdev.dd.d_unit == -1)
397				break;
398			print_disk_probe_info();
399			open_result = devsw[devidx]->dv_open(&f, &currdev);
400			if (open_result == 0) {
401				printf(" good.\n");
402				return (0);
403			}
404			printf("\n");
405		}
406		return (-1);
407	}
408
409	if ((currdev.dd.d_unit = uboot_diskgetunit(load_type, load_unit)) != -1) {
410		print_disk_probe_info();
411		open_result = devsw[devidx]->dv_open(&f,&currdev);
412		if (open_result == 0) {
413			printf(" good.\n");
414			return (0);
415		}
416		printf("\n");
417	}
418
419	printf("  Requested disk type/unit/slice/partition not found\n");
420	return (-1);
421}
422
423int
424main(int argc, char **argv)
425{
426	struct api_signature *sig = NULL;
427	int load_type, load_unit, load_slice, load_partition;
428	int i;
429	const char *ldev;
430
431	/*
432	 * We first check if a command line argument was passed to us containing
433	 * API's signature address. If it wasn't then we try to search for the
434	 * API signature via the usual hinted address.
435	 * If we can't find the magic signature and related info, exit with a
436	 * unique error code that U-Boot reports as "## Application terminated,
437	 * rc = 0xnnbadab1". Hopefully 'badab1' looks enough like "bad api" to
438	 * provide a clue. It's better than 0xffffffff anyway.
439	 */
440	if (!api_parse_cmdline_sig(argc, argv, &sig) && !api_search_sig(&sig))
441		return (0x01badab1);
442
443	syscall_ptr = sig->syscall;
444	if (syscall_ptr == NULL)
445		return (0x02badab1);
446
447	if (sig->version > API_SIG_VERSION)
448		return (0x03badab1);
449
450        /* Clear BSS sections */
451	bzero(__sbss_start, __sbss_end - __sbss_start);
452	bzero(__bss_start, _end - __bss_start);
453
454	/*
455	 * Initialise the heap as early as possible.  Once this is done,
456	 * alloc() is usable.  We are using the stack u-boot set up near the top
457	 * of physical ram; hopefully there is sufficient space between the end
458	 * of our bss and the bottom of the u-boot stack to avoid overlap.
459	 */
460	uboot_heap_start = round_page((uintptr_t)end);
461	uboot_heap_end   = uboot_heap_start + HEAP_SIZE;
462	setheap((void *)uboot_heap_start, (void *)uboot_heap_end);
463
464	/*
465	 * Set up console.
466	 */
467	cons_probe();
468	printf("Compatible U-Boot API signature found @%p\n", sig);
469
470	printf("\n%s", bootprog_info);
471	printf("\n");
472
473	dump_sig(sig);
474	dump_addr_info();
475
476	meminfo();
477
478	/*
479	 * Enumerate U-Boot devices
480	 */
481	if ((devs_no = ub_dev_enum()) == 0) {
482		printf("no U-Boot devices found");
483		goto do_interact;
484	}
485	printf("Number of U-Boot devices: %d\n", devs_no);
486
487	get_load_device(&load_type, &load_unit, &load_slice, &load_partition);
488
489	/*
490	 * March through the device switch probing for things.
491	 */
492	for (i = 0; devsw[i] != NULL; i++) {
493
494		if (devsw[i]->dv_init == NULL)
495			continue;
496		if ((devsw[i]->dv_init)() != 0)
497			continue;
498
499		printf("Found U-Boot device: %s\n", devsw[i]->dv_name);
500
501		currdev.dd.d_dev = devsw[i];
502		currdev.dd.d_unit = 0;
503
504		if ((load_type == DEV_TYP_NONE || (load_type & DEV_TYP_STOR)) &&
505		    strcmp(devsw[i]->dv_name, "disk") == 0) {
506			if (probe_disks(i, load_type, load_unit, load_slice,
507			    load_partition) == 0)
508				break;
509		}
510
511		if ((load_type == DEV_TYP_NONE || (load_type & DEV_TYP_NET)) &&
512		    strcmp(devsw[i]->dv_name, "net") == 0)
513			break;
514	}
515
516	/*
517	 * If we couldn't find a boot device, return an error to u-boot.
518	 * U-boot may be running a boot script that can try something different
519	 * so returning an error is better than forcing a reboot.
520	 */
521	if (devsw[i] == NULL) {
522		printf("No boot device found!\n");
523		return (0xbadef1ce);
524	}
525
526	ldev = uboot_fmtdev(&currdev);
527	env_setenv("currdev", EV_VOLATILE, ldev, uboot_setcurrdev, env_nounset);
528	env_setenv("loaddev", EV_VOLATILE, ldev, env_noset, env_nounset);
529	printf("Booting from %s\n", ldev);
530
531do_interact:
532	setenv("LINES", "24", 1);		/* optional */
533	setenv("prompt", "loader>", 1);
534#ifdef __powerpc__
535	setenv("usefdt", "1", 1);
536#endif
537
538	archsw.arch_loadaddr = uboot_loadaddr;
539	archsw.arch_getdev = uboot_getdev;
540	archsw.arch_copyin = uboot_copyin;
541	archsw.arch_copyout = uboot_copyout;
542	archsw.arch_readin = uboot_readin;
543	archsw.arch_autoload = uboot_autoload;
544
545	interact();				/* doesn't return */
546
547	return (0);
548}
549
550
551COMMAND_SET(heap, "heap", "show heap usage", command_heap);
552static int
553command_heap(int argc, char *argv[])
554{
555
556	printf("heap base at %p, top at %p, used %td\n", end, sbrk(0),
557	    sbrk(0) - end);
558
559	return (CMD_OK);
560}
561
562COMMAND_SET(reboot, "reboot", "reboot the system", command_reboot);
563static int
564command_reboot(int argc, char *argv[])
565{
566
567	printf("Resetting...\n");
568	ub_reset();
569
570	printf("Reset failed!\n");
571	while (1);
572	__unreachable();
573}
574
575COMMAND_SET(devinfo, "devinfo", "show U-Boot devices", command_devinfo);
576static int
577command_devinfo(int argc, char *argv[])
578{
579	int i;
580
581	if ((devs_no = ub_dev_enum()) == 0) {
582		command_errmsg = "no U-Boot devices found!?";
583		return (CMD_ERROR);
584	}
585
586	printf("U-Boot devices:\n");
587	for (i = 0; i < devs_no; i++) {
588		ub_dump_di(i);
589		printf("\n");
590	}
591	return (CMD_OK);
592}
593
594COMMAND_SET(sysinfo, "sysinfo", "show U-Boot system info", command_sysinfo);
595static int
596command_sysinfo(int argc, char *argv[])
597{
598	struct sys_info *si;
599
600	if ((si = ub_get_sys_info()) == NULL) {
601		command_errmsg = "could not retrieve U-Boot sys info!?";
602		return (CMD_ERROR);
603	}
604
605	printf("U-Boot system info:\n");
606	ub_dump_si(si);
607	return (CMD_OK);
608}
609
610enum ubenv_action {
611	UBENV_UNKNOWN,
612	UBENV_SHOW,
613	UBENV_IMPORT
614};
615
616static void
617handle_uboot_env_var(enum ubenv_action action, const char * var)
618{
619	char ldvar[128];
620	const char *val;
621	char *wrk;
622	int len;
623
624	/*
625	 * On an import with the variable name formatted as ldname=ubname,
626	 * import the uboot variable ubname into the loader variable ldname,
627	 * otherwise the historical behavior is to import to uboot.ubname.
628	 */
629	if (action == UBENV_IMPORT) {
630		len = strcspn(var, "=");
631		if (len == 0) {
632			printf("name cannot start with '=': '%s'\n", var);
633			return;
634		}
635		if (var[len] == 0) {
636			strcpy(ldvar, "uboot.");
637			strncat(ldvar, var, sizeof(ldvar) - 7);
638		} else {
639			len = MIN(len, sizeof(ldvar) - 1);
640			strncpy(ldvar, var, len);
641			ldvar[len] = 0;
642			var = &var[len + 1];
643		}
644	}
645
646	/*
647	 * If the user prepended "uboot." (which is how they usually see these
648	 * names) strip it off as a convenience.
649	 */
650	if (strncmp(var, "uboot.", 6) == 0) {
651		var = &var[6];
652	}
653
654	/* If there is no variable name left, punt. */
655	if (var[0] == 0) {
656		printf("empty variable name\n");
657		return;
658	}
659
660	val = ub_env_get(var);
661	if (action == UBENV_SHOW) {
662		if (val == NULL)
663			printf("uboot.%s is not set\n", var);
664		else
665			printf("uboot.%s=%s\n", var, val);
666	} else if (action == UBENV_IMPORT) {
667		if (val != NULL) {
668			setenv(ldvar, val, 1);
669		}
670	}
671}
672
673static int
674command_ubenv(int argc, char *argv[])
675{
676	enum ubenv_action action;
677	const char *var;
678	int i;
679
680	action = UBENV_UNKNOWN;
681	if (argc > 1) {
682		if (strcasecmp(argv[1], "import") == 0)
683			action = UBENV_IMPORT;
684		else if (strcasecmp(argv[1], "show") == 0)
685			action = UBENV_SHOW;
686	}
687	if (action == UBENV_UNKNOWN) {
688		command_errmsg = "usage: 'ubenv <import|show> [var ...]";
689		return (CMD_ERROR);
690	}
691
692	if (argc > 2) {
693		for (i = 2; i < argc; i++)
694			handle_uboot_env_var(action, argv[i]);
695	} else {
696		var = NULL;
697		for (;;) {
698			if ((var = ub_env_enum(var)) == NULL)
699				break;
700			handle_uboot_env_var(action, var);
701		}
702	}
703
704	return (CMD_OK);
705}
706COMMAND_SET(ubenv, "ubenv", "show or import U-Boot env vars", command_ubenv);
707
708#ifdef LOADER_FDT_SUPPORT
709/*
710 * Since proper fdt command handling function is defined in fdt_loader_cmd.c,
711 * and declaring it as extern is in contradiction with COMMAND_SET() macro
712 * (which uses static pointer), we're defining wrapper function, which
713 * calls the proper fdt handling routine.
714 */
715static int
716command_fdt(int argc, char *argv[])
717{
718
719	return (command_fdt_internal(argc, argv));
720}
721
722COMMAND_SET(fdt, "fdt", "flattened device tree handling", command_fdt);
723#endif
724