fdt_loader_cmd.c revision 294417
1/*-
2 * Copyright (c) 2009-2010 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Semihalf under sponsorship from
6 * the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD: stable/10/sys/boot/fdt/fdt_loader_cmd.c 294417 2016-01-20 13:23:02Z royger $");
32
33#include <stand.h>
34#include <fdt.h>
35#include <libfdt.h>
36#include <sys/param.h>
37#include <sys/linker.h>
38#include <machine/elf.h>
39
40#include "bootstrap.h"
41#include "fdt_platform.h"
42
43#ifdef DEBUG
44#define debugf(fmt, args...) do { printf("%s(): ", __func__);	\
45    printf(fmt,##args); } while (0)
46#else
47#define debugf(fmt, args...)
48#endif
49
50#define FDT_CWD_LEN	256
51#define FDT_MAX_DEPTH	6
52
53#define FDT_PROP_SEP	" = "
54
55#define COPYOUT(s,d,l)	archsw.arch_copyout(s, d, l)
56#define COPYIN(s,d,l)	archsw.arch_copyin(s, d, l)
57
58#define FDT_STATIC_DTB_SYMBOL	"fdt_static_dtb"
59
60#define	CMD_REQUIRES_BLOB	0x01
61
62/* Location of FDT yet to be loaded. */
63/* This may be in read-only memory, so can't be manipulated directly. */
64static struct fdt_header *fdt_to_load = NULL;
65/* Location of FDT on heap. */
66/* This is the copy we actually manipulate. */
67static struct fdt_header *fdtp = NULL;
68/* Size of FDT blob */
69static size_t fdtp_size = 0;
70/* Location of FDT in kernel or module. */
71/* This won't be set if FDT is loaded from disk or memory. */
72/* If it is set, we'll update it when fdt_copy() gets called. */
73static vm_offset_t fdtp_va = 0;
74
75static int fdt_load_dtb(vm_offset_t va);
76
77static int fdt_cmd_nyi(int argc, char *argv[]);
78
79static int fdt_cmd_addr(int argc, char *argv[]);
80static int fdt_cmd_mkprop(int argc, char *argv[]);
81static int fdt_cmd_cd(int argc, char *argv[]);
82static int fdt_cmd_hdr(int argc, char *argv[]);
83static int fdt_cmd_ls(int argc, char *argv[]);
84static int fdt_cmd_prop(int argc, char *argv[]);
85static int fdt_cmd_pwd(int argc, char *argv[]);
86static int fdt_cmd_rm(int argc, char *argv[]);
87static int fdt_cmd_mknode(int argc, char *argv[]);
88static int fdt_cmd_mres(int argc, char *argv[]);
89
90typedef int cmdf_t(int, char *[]);
91
92struct cmdtab {
93	const char	*name;
94	cmdf_t		*handler;
95	int		flags;
96};
97
98static const struct cmdtab commands[] = {
99	{ "addr", &fdt_cmd_addr,	0 },
100	{ "alias", &fdt_cmd_nyi,	0 },
101	{ "cd", &fdt_cmd_cd,		CMD_REQUIRES_BLOB },
102	{ "header", &fdt_cmd_hdr,	CMD_REQUIRES_BLOB },
103	{ "ls", &fdt_cmd_ls,		CMD_REQUIRES_BLOB },
104	{ "mknode", &fdt_cmd_mknode,	CMD_REQUIRES_BLOB },
105	{ "mkprop", &fdt_cmd_mkprop,	CMD_REQUIRES_BLOB },
106	{ "mres", &fdt_cmd_mres,	CMD_REQUIRES_BLOB },
107	{ "prop", &fdt_cmd_prop,	CMD_REQUIRES_BLOB },
108	{ "pwd", &fdt_cmd_pwd,		CMD_REQUIRES_BLOB },
109	{ "rm", &fdt_cmd_rm,		CMD_REQUIRES_BLOB },
110	{ NULL, NULL }
111};
112
113static char cwd[FDT_CWD_LEN] = "/";
114
115static vm_offset_t
116fdt_find_static_dtb()
117{
118	Elf_Ehdr *ehdr;
119	Elf_Shdr *shdr;
120	Elf_Sym sym;
121	vm_offset_t strtab, symtab, fdt_start;
122	uint64_t offs;
123	struct preloaded_file *kfp;
124	struct file_metadata *md;
125	char *strp;
126	int i, sym_count;
127
128	debugf("fdt_find_static_dtb()\n");
129
130	sym_count = symtab = strtab = 0;
131	strp = NULL;
132
133	offs = __elfN(relocation_offset);
134
135	kfp = file_findfile(NULL, NULL);
136	if (kfp == NULL)
137		return (0);
138
139	/* Locate the dynamic symbols and strtab. */
140	md = file_findmetadata(kfp, MODINFOMD_ELFHDR);
141	if (md == NULL)
142		return (0);
143	ehdr = (Elf_Ehdr *)md->md_data;
144
145	md = file_findmetadata(kfp, MODINFOMD_SHDR);
146	if (md == NULL)
147		return (0);
148	shdr = (Elf_Shdr *)md->md_data;
149
150	for (i = 0; i < ehdr->e_shnum; ++i) {
151		if (shdr[i].sh_type == SHT_DYNSYM && symtab == 0) {
152			symtab = shdr[i].sh_addr + offs;
153			sym_count = shdr[i].sh_size / sizeof(Elf_Sym);
154		} else if (shdr[i].sh_type == SHT_STRTAB && strtab == 0) {
155			strtab = shdr[i].sh_addr + offs;
156		}
157	}
158
159	/*
160	 * The most efficent way to find a symbol would be to calculate a
161	 * hash, find proper bucket and chain, and thus find a symbol.
162	 * However, that would involve code duplication (e.g. for hash
163	 * function). So we're using simpler and a bit slower way: we're
164	 * iterating through symbols, searching for the one which name is
165	 * 'equal' to 'fdt_static_dtb'. To speed up the process a little bit,
166	 * we are eliminating symbols type of which is not STT_NOTYPE, or(and)
167	 * those which binding attribute is not STB_GLOBAL.
168	 */
169	fdt_start = 0;
170	while (sym_count > 0 && fdt_start == 0) {
171		COPYOUT(symtab, &sym, sizeof(sym));
172		symtab += sizeof(sym);
173		--sym_count;
174		if (ELF_ST_BIND(sym.st_info) != STB_GLOBAL ||
175		    ELF_ST_TYPE(sym.st_info) != STT_NOTYPE)
176			continue;
177		strp = strdupout(strtab + sym.st_name);
178		if (strcmp(strp, FDT_STATIC_DTB_SYMBOL) == 0)
179			fdt_start = (vm_offset_t)sym.st_value + offs;
180		free(strp);
181	}
182	return (fdt_start);
183}
184
185static int
186fdt_load_dtb(vm_offset_t va)
187{
188	struct fdt_header header;
189	int err;
190
191	debugf("fdt_load_dtb(0x%08jx)\n", (uintmax_t)va);
192
193	COPYOUT(va, &header, sizeof(header));
194	err = fdt_check_header(&header);
195	if (err < 0) {
196		if (err == -FDT_ERR_BADVERSION)
197			sprintf(command_errbuf,
198			    "incompatible blob version: %d, should be: %d",
199			    fdt_version(fdtp), FDT_LAST_SUPPORTED_VERSION);
200
201		else
202			sprintf(command_errbuf, "error validating blob: %s",
203			    fdt_strerror(err));
204		return (1);
205	}
206
207	/*
208	 * Release previous blob
209	 */
210	if (fdtp)
211		free(fdtp);
212
213	fdtp_size = fdt_totalsize(&header);
214	fdtp = malloc(fdtp_size);
215
216	if (fdtp == NULL) {
217		command_errmsg = "can't allocate memory for device tree copy";
218		return (1);
219	}
220
221	fdtp_va = va;
222	COPYOUT(va, fdtp, fdtp_size);
223	debugf("DTB blob found at 0x%jx, size: 0x%jx\n", (uintmax_t)va, (uintmax_t)fdtp_size);
224
225	return (0);
226}
227
228int
229fdt_load_dtb_addr(struct fdt_header *header)
230{
231	int err;
232
233	debugf("fdt_load_dtb_addr(0x%p)\n", header);
234
235	fdtp_size = fdt_totalsize(header);
236	err = fdt_check_header(header);
237	if (err < 0) {
238		sprintf(command_errbuf, "error validating blob: %s",
239		    fdt_strerror(err));
240		return (err);
241	}
242	free(fdtp);
243	if ((fdtp = malloc(fdtp_size)) == NULL) {
244		command_errmsg = "can't allocate memory for device tree copy";
245		return (1);
246	}
247
248	fdtp_va = 0; // Don't write this back into module or kernel.
249	bcopy(header, fdtp, fdtp_size);
250	return (0);
251}
252
253int
254fdt_load_dtb_file(const char * filename)
255{
256	struct preloaded_file *bfp, *oldbfp;
257	int err;
258
259	debugf("fdt_load_dtb_file(%s)\n", filename);
260
261	oldbfp = file_findfile(NULL, "dtb");
262
263	/* Attempt to load and validate a new dtb from a file. */
264	if ((bfp = file_loadraw(filename, "dtb", 1)) == NULL) {
265		sprintf(command_errbuf, "failed to load file '%s'", filename);
266		return (1);
267	}
268	if ((err = fdt_load_dtb(bfp->f_addr)) != 0) {
269		file_discard(bfp);
270		return (err);
271	}
272
273	/* A new dtb was validated, discard any previous file. */
274	if (oldbfp)
275		file_discard(oldbfp);
276	return (0);
277}
278
279int
280fdt_setup_fdtp()
281{
282	struct preloaded_file *bfp;
283	vm_offset_t va;
284
285	debugf("fdt_setup_fdtp()\n");
286
287	/* If we already loaded a file, use it. */
288	if ((bfp = file_findfile(NULL, "dtb")) != NULL) {
289		if (fdt_load_dtb(bfp->f_addr) == 0) {
290			printf("Using DTB from loaded file '%s'.\n",
291			    bfp->f_name);
292			return (0);
293		}
294	}
295
296	/* If we were given the address of a valid blob in memory, use it. */
297	if (fdt_to_load != NULL) {
298		if (fdt_load_dtb_addr(fdt_to_load) == 0) {
299			printf("Using DTB from memory address 0x%08X.\n",
300			    (unsigned int)fdt_to_load);
301			return (0);
302		}
303	}
304
305	if (fdt_platform_load_dtb() == 0)
306		return (0);
307
308	/* If there is a dtb compiled into the kernel, use it. */
309	if ((va = fdt_find_static_dtb()) != 0) {
310		if (fdt_load_dtb(va) == 0) {
311			printf("Using DTB compiled into kernel.\n");
312			return (0);
313		}
314	}
315
316	command_errmsg = "No device tree blob found!\n";
317	return (1);
318}
319
320#define fdt_strtovect(str, cellbuf, lim, cellsize) _fdt_strtovect((str), \
321    (cellbuf), (lim), (cellsize), 0);
322
323/* Force using base 16 */
324#define fdt_strtovectx(str, cellbuf, lim, cellsize) _fdt_strtovect((str), \
325    (cellbuf), (lim), (cellsize), 16);
326
327static int
328_fdt_strtovect(const char *str, void *cellbuf, int lim, unsigned char cellsize,
329    uint8_t base)
330{
331	const char *buf = str;
332	const char *end = str + strlen(str) - 2;
333	uint32_t *u32buf = NULL;
334	uint8_t *u8buf = NULL;
335	int cnt = 0;
336
337	if (cellsize == sizeof(uint32_t))
338		u32buf = (uint32_t *)cellbuf;
339	else
340		u8buf = (uint8_t *)cellbuf;
341
342	if (lim == 0)
343		return (0);
344
345	while (buf < end) {
346
347		/* Skip white whitespace(s)/separators */
348		while (!isxdigit(*buf) && buf < end)
349			buf++;
350
351		if (u32buf != NULL)
352			u32buf[cnt] =
353			    cpu_to_fdt32((uint32_t)strtol(buf, NULL, base));
354
355		else
356			u8buf[cnt] = (uint8_t)strtol(buf, NULL, base);
357
358		if (cnt + 1 <= lim - 1)
359			cnt++;
360		else
361			break;
362		buf++;
363		/* Find another number */
364		while ((isxdigit(*buf) || *buf == 'x') && buf < end)
365			buf++;
366	}
367	return (cnt);
368}
369
370void
371fdt_fixup_ethernet(const char *str, char *ethstr, int len)
372{
373	uint8_t tmp_addr[6];
374
375	/* Convert macaddr string into a vector of uints */
376	fdt_strtovectx(str, &tmp_addr, 6, sizeof(uint8_t));
377	/* Set actual property to a value from vect */
378	fdt_setprop(fdtp, fdt_path_offset(fdtp, ethstr),
379	    "local-mac-address", &tmp_addr, 6 * sizeof(uint8_t));
380}
381
382void
383fdt_fixup_cpubusfreqs(unsigned long cpufreq, unsigned long busfreq)
384{
385	int lo, o = 0, o2, maxo = 0, depth;
386	const uint32_t zero = 0;
387
388	/* We want to modify every subnode of /cpus */
389	o = fdt_path_offset(fdtp, "/cpus");
390	if (o < 0)
391		return;
392
393	/* maxo should contain offset of node next to /cpus */
394	depth = 0;
395	maxo = o;
396	while (depth != -1)
397		maxo = fdt_next_node(fdtp, maxo, &depth);
398
399	/* Find CPU frequency properties */
400	o = fdt_node_offset_by_prop_value(fdtp, o, "clock-frequency",
401	    &zero, sizeof(uint32_t));
402
403	o2 = fdt_node_offset_by_prop_value(fdtp, o, "bus-frequency", &zero,
404	    sizeof(uint32_t));
405
406	lo = MIN(o, o2);
407
408	while (o != -FDT_ERR_NOTFOUND && o2 != -FDT_ERR_NOTFOUND) {
409
410		o = fdt_node_offset_by_prop_value(fdtp, lo,
411		    "clock-frequency", &zero, sizeof(uint32_t));
412
413		o2 = fdt_node_offset_by_prop_value(fdtp, lo, "bus-frequency",
414		    &zero, sizeof(uint32_t));
415
416		/* We're only interested in /cpus subnode(s) */
417		if (lo > maxo)
418			break;
419
420		fdt_setprop_inplace_cell(fdtp, lo, "clock-frequency",
421		    (uint32_t)cpufreq);
422
423		fdt_setprop_inplace_cell(fdtp, lo, "bus-frequency",
424		    (uint32_t)busfreq);
425
426		lo = MIN(o, o2);
427	}
428}
429
430static int
431fdt_reg_valid(uint32_t *reg, int len, int addr_cells, int size_cells)
432{
433	int cells_in_tuple, i, tuples, tuple_size;
434	uint32_t cur_start, cur_size;
435
436	cells_in_tuple = (addr_cells + size_cells);
437	tuple_size = cells_in_tuple * sizeof(uint32_t);
438	tuples = len / tuple_size;
439	if (tuples == 0)
440		return (EINVAL);
441
442	for (i = 0; i < tuples; i++) {
443		if (addr_cells == 2)
444			cur_start = fdt64_to_cpu(reg[i * cells_in_tuple]);
445		else
446			cur_start = fdt32_to_cpu(reg[i * cells_in_tuple]);
447
448		if (size_cells == 2)
449			cur_size = fdt64_to_cpu(reg[i * cells_in_tuple + 2]);
450		else
451			cur_size = fdt32_to_cpu(reg[i * cells_in_tuple + 1]);
452
453		if (cur_size == 0)
454			return (EINVAL);
455
456		debugf(" reg#%d (start: 0x%0x size: 0x%0x) valid!\n",
457		    i, cur_start, cur_size);
458	}
459	return (0);
460}
461
462void
463fdt_fixup_memory(struct fdt_mem_region *region, size_t num)
464{
465	struct fdt_mem_region *curmr;
466	uint32_t addr_cells, size_cells;
467	uint32_t *addr_cellsp, *reg,  *size_cellsp;
468	int err, i, len, memory, root;
469	size_t realmrno;
470	uint8_t *buf, *sb;
471	uint64_t rstart, rsize;
472	int reserved;
473
474	root = fdt_path_offset(fdtp, "/");
475	if (root < 0) {
476		sprintf(command_errbuf, "Could not find root node !");
477		return;
478	}
479
480	memory = fdt_path_offset(fdtp, "/memory");
481	if (memory <= 0) {
482		/* Create proper '/memory' node. */
483		memory = fdt_add_subnode(fdtp, root, "memory");
484		if (memory <= 0) {
485			sprintf(command_errbuf, "Could not fixup '/memory' "
486			    "node, error code : %d!\n", memory);
487			return;
488		}
489
490		err = fdt_setprop(fdtp, memory, "device_type", "memory",
491		    sizeof("memory"));
492
493		if (err < 0)
494			return;
495	}
496
497	addr_cellsp = (uint32_t *)fdt_getprop(fdtp, root, "#address-cells",
498	    NULL);
499	size_cellsp = (uint32_t *)fdt_getprop(fdtp, root, "#size-cells", NULL);
500
501	if (addr_cellsp == NULL || size_cellsp == NULL) {
502		sprintf(command_errbuf, "Could not fixup '/memory' node : "
503		    "%s %s property not found in root node!\n",
504		    (!addr_cellsp) ? "#address-cells" : "",
505		    (!size_cellsp) ? "#size-cells" : "");
506		return;
507	}
508
509	addr_cells = fdt32_to_cpu(*addr_cellsp);
510	size_cells = fdt32_to_cpu(*size_cellsp);
511
512	/*
513	 * Convert memreserve data to memreserve property
514	 * Check if property already exists
515	 */
516	reserved = fdt_num_mem_rsv(fdtp);
517	if (reserved &&
518	    (fdt_getprop(fdtp, root, "memreserve", NULL) == NULL)) {
519		len = (addr_cells + size_cells) * reserved * sizeof(uint32_t);
520		sb = buf = (uint8_t *)malloc(len);
521		if (!buf)
522			return;
523
524		bzero(buf, len);
525
526		for (i = 0; i < reserved; i++) {
527			if (fdt_get_mem_rsv(fdtp, i, &rstart, &rsize))
528				break;
529			if (rsize) {
530				/* Ensure endianess, and put cells into a buffer */
531				if (addr_cells == 2)
532					*(uint64_t *)buf =
533					    cpu_to_fdt64(rstart);
534				else
535					*(uint32_t *)buf =
536					    cpu_to_fdt32(rstart);
537
538				buf += sizeof(uint32_t) * addr_cells;
539				if (size_cells == 2)
540					*(uint64_t *)buf =
541					    cpu_to_fdt64(rsize);
542				else
543					*(uint32_t *)buf =
544					    cpu_to_fdt32(rsize);
545
546				buf += sizeof(uint32_t) * size_cells;
547			}
548		}
549
550		/* Set property */
551		if ((err = fdt_setprop(fdtp, root, "memreserve", sb, len)) < 0)
552			printf("Could not fixup 'memreserve' property.\n");
553
554		free(sb);
555	}
556
557	/* Count valid memory regions entries in sysinfo. */
558	realmrno = num;
559	for (i = 0; i < num; i++)
560		if (region[i].start == 0 && region[i].size == 0)
561			realmrno--;
562
563	if (realmrno == 0) {
564		sprintf(command_errbuf, "Could not fixup '/memory' node : "
565		    "sysinfo doesn't contain valid memory regions info!\n");
566		return;
567	}
568
569	len = (addr_cells + size_cells) * realmrno * sizeof(uint32_t);
570	sb = buf = (uint8_t *)malloc(len);
571	if (!buf)
572		return;
573
574	bzero(buf, len);
575
576	for (i = 0; i < num; i++) {
577		curmr = &region[i];
578		if (curmr->size != 0) {
579			/* Ensure endianess, and put cells into a buffer */
580			if (addr_cells == 2)
581				*(uint64_t *)buf =
582				    cpu_to_fdt64(curmr->start);
583			else
584				*(uint32_t *)buf =
585				    cpu_to_fdt32(curmr->start);
586
587			buf += sizeof(uint32_t) * addr_cells;
588			if (size_cells == 2)
589				*(uint64_t *)buf =
590				    cpu_to_fdt64(curmr->size);
591			else
592				*(uint32_t *)buf =
593				    cpu_to_fdt32(curmr->size);
594
595			buf += sizeof(uint32_t) * size_cells;
596		}
597	}
598
599	/* Set property */
600	if ((err = fdt_setprop(fdtp, memory, "reg", sb, len)) < 0)
601		sprintf(command_errbuf, "Could not fixup '/memory' node.\n");
602
603	free(sb);
604}
605
606void
607fdt_fixup_stdout(const char *str)
608{
609	char *ptr;
610	int serialno;
611	int len, no, sero;
612	const struct fdt_property *prop;
613	char *tmp[10];
614
615	ptr = (char *)str + strlen(str) - 1;
616	while (ptr > str && isdigit(*(str - 1)))
617		str--;
618
619	if (ptr == str)
620		return;
621
622	serialno = (int)strtol(ptr, NULL, 0);
623	no = fdt_path_offset(fdtp, "/chosen");
624	if (no < 0)
625		return;
626
627	prop = fdt_get_property(fdtp, no, "stdout", &len);
628
629	/* If /chosen/stdout does not extist, create it */
630	if (prop == NULL || (prop != NULL && len == 0)) {
631
632		bzero(tmp, 10 * sizeof(char));
633		strcpy((char *)&tmp, "serial");
634		if (strlen(ptr) > 3)
635			/* Serial number too long */
636			return;
637
638		strncpy((char *)tmp + 6, ptr, 3);
639		sero = fdt_path_offset(fdtp, (const char *)tmp);
640		if (sero < 0)
641			/*
642			 * If serial device we're trying to assign
643			 * stdout to doesn't exist in DT -- return.
644			 */
645			return;
646
647		fdt_setprop(fdtp, no, "stdout", &tmp,
648		    strlen((char *)&tmp) + 1);
649		fdt_setprop(fdtp, no, "stdin", &tmp,
650		    strlen((char *)&tmp) + 1);
651	}
652}
653
654/*
655 * Locate the blob, fix it up and return its location.
656 */
657static int
658fdt_fixup(void)
659{
660	int chosen, len;
661
662	len = 0;
663
664	debugf("fdt_fixup()\n");
665
666	if (fdtp == NULL && fdt_setup_fdtp() != 0)
667		return (0);
668
669	/* Create /chosen node (if not exists) */
670	if ((chosen = fdt_subnode_offset(fdtp, 0, "chosen")) ==
671	    -FDT_ERR_NOTFOUND)
672		chosen = fdt_add_subnode(fdtp, 0, "chosen");
673
674	/* Value assigned to fixup-applied does not matter. */
675	if (fdt_getprop(fdtp, chosen, "fixup-applied", NULL))
676		return (1);
677
678	fdt_platform_fixups();
679
680	fdt_setprop(fdtp, chosen, "fixup-applied", NULL, 0);
681	return (1);
682}
683
684/*
685 * Copy DTB blob to specified location and return size
686 */
687int
688fdt_copy(vm_offset_t va)
689{
690	int err;
691	debugf("fdt_copy va 0x%08x\n", va);
692	if (fdtp == NULL) {
693		err = fdt_setup_fdtp();
694		if (err) {
695			printf("No valid device tree blob found!\n");
696			return (0);
697		}
698	}
699
700	if (fdt_fixup() == 0)
701		return (0);
702
703	if (fdtp_va != 0) {
704		/* Overwrite the FDT with the fixed version. */
705		/* XXX Is this really appropriate? */
706		COPYIN(fdtp, fdtp_va, fdtp_size);
707	}
708	COPYIN(fdtp, va, fdtp_size);
709	return (fdtp_size);
710}
711
712
713
714int
715command_fdt_internal(int argc, char *argv[])
716{
717	cmdf_t *cmdh;
718	int flags;
719	char *cmd;
720	int i, err;
721
722	if (argc < 2) {
723		command_errmsg = "usage is 'fdt <command> [<args>]";
724		return (CMD_ERROR);
725	}
726
727	/*
728	 * Validate fdt <command>.
729	 */
730	cmd = strdup(argv[1]);
731	i = 0;
732	cmdh = NULL;
733	while (!(commands[i].name == NULL)) {
734		if (strcmp(cmd, commands[i].name) == 0) {
735			/* found it */
736			cmdh = commands[i].handler;
737			flags = commands[i].flags;
738			break;
739		}
740		i++;
741	}
742	if (cmdh == NULL) {
743		command_errmsg = "unknown command";
744		return (CMD_ERROR);
745	}
746
747	if (flags & CMD_REQUIRES_BLOB) {
748		/*
749		 * Check if uboot env vars were parsed already. If not, do it now.
750		 */
751		if (fdt_fixup() == 0)
752			return (CMD_ERROR);
753	}
754
755	/*
756	 * Call command handler.
757	 */
758	err = (*cmdh)(argc, argv);
759
760	return (err);
761}
762
763static int
764fdt_cmd_addr(int argc, char *argv[])
765{
766	struct preloaded_file *fp;
767	struct fdt_header *hdr;
768	const char *addr;
769	char *cp;
770
771	fdt_to_load = NULL;
772
773	if (argc > 2)
774		addr = argv[2];
775	else {
776		sprintf(command_errbuf, "no address specified");
777		return (CMD_ERROR);
778	}
779
780	hdr = (struct fdt_header *)strtoul(addr, &cp, 16);
781	if (cp == addr) {
782		sprintf(command_errbuf, "Invalid address: %s", addr);
783		return (CMD_ERROR);
784	}
785
786	while ((fp = file_findfile(NULL, "dtb")) != NULL) {
787		file_discard(fp);
788	}
789
790	fdt_to_load = hdr;
791	return (CMD_OK);
792}
793
794static int
795fdt_cmd_cd(int argc, char *argv[])
796{
797	char *path;
798	char tmp[FDT_CWD_LEN];
799	int len, o;
800
801	path = (argc > 2) ? argv[2] : "/";
802
803	if (path[0] == '/') {
804		len = strlen(path);
805		if (len >= FDT_CWD_LEN)
806			goto fail;
807	} else {
808		/* Handle path specification relative to cwd */
809		len = strlen(cwd) + strlen(path) + 1;
810		if (len >= FDT_CWD_LEN)
811			goto fail;
812
813		strcpy(tmp, cwd);
814		strcat(tmp, "/");
815		strcat(tmp, path);
816		path = tmp;
817	}
818
819	o = fdt_path_offset(fdtp, path);
820	if (o < 0) {
821		sprintf(command_errbuf, "could not find node: '%s'", path);
822		return (CMD_ERROR);
823	}
824
825	strcpy(cwd, path);
826	return (CMD_OK);
827
828fail:
829	sprintf(command_errbuf, "path too long: %d, max allowed: %d",
830	    len, FDT_CWD_LEN - 1);
831	return (CMD_ERROR);
832}
833
834static int
835fdt_cmd_hdr(int argc __unused, char *argv[] __unused)
836{
837	char line[80];
838	int ver;
839
840	if (fdtp == NULL) {
841		command_errmsg = "no device tree blob pointer?!";
842		return (CMD_ERROR);
843	}
844
845	ver = fdt_version(fdtp);
846	pager_open();
847	sprintf(line, "\nFlattened device tree header (%p):\n", fdtp);
848	pager_output(line);
849	sprintf(line, " magic                   = 0x%08x\n", fdt_magic(fdtp));
850	pager_output(line);
851	sprintf(line, " size                    = %d\n", fdt_totalsize(fdtp));
852	pager_output(line);
853	sprintf(line, " off_dt_struct           = 0x%08x\n",
854	    fdt_off_dt_struct(fdtp));
855	pager_output(line);
856	sprintf(line, " off_dt_strings          = 0x%08x\n",
857	    fdt_off_dt_strings(fdtp));
858	pager_output(line);
859	sprintf(line, " off_mem_rsvmap          = 0x%08x\n",
860	    fdt_off_mem_rsvmap(fdtp));
861	pager_output(line);
862	sprintf(line, " version                 = %d\n", ver);
863	pager_output(line);
864	sprintf(line, " last compatible version = %d\n",
865	    fdt_last_comp_version(fdtp));
866	pager_output(line);
867	if (ver >= 2) {
868		sprintf(line, " boot_cpuid              = %d\n",
869		    fdt_boot_cpuid_phys(fdtp));
870		pager_output(line);
871	}
872	if (ver >= 3) {
873		sprintf(line, " size_dt_strings         = %d\n",
874		    fdt_size_dt_strings(fdtp));
875		pager_output(line);
876	}
877	if (ver >= 17) {
878		sprintf(line, " size_dt_struct          = %d\n",
879		    fdt_size_dt_struct(fdtp));
880		pager_output(line);
881	}
882	pager_close();
883
884	return (CMD_OK);
885}
886
887static int
888fdt_cmd_ls(int argc, char *argv[])
889{
890	const char *prevname[FDT_MAX_DEPTH] = { NULL };
891	const char *name;
892	char *path;
893	int i, o, depth, len;
894
895	path = (argc > 2) ? argv[2] : NULL;
896	if (path == NULL)
897		path = cwd;
898
899	o = fdt_path_offset(fdtp, path);
900	if (o < 0) {
901		sprintf(command_errbuf, "could not find node: '%s'", path);
902		return (CMD_ERROR);
903	}
904
905	for (depth = 0;
906	    (o >= 0) && (depth >= 0);
907	    o = fdt_next_node(fdtp, o, &depth)) {
908
909		name = fdt_get_name(fdtp, o, &len);
910
911		if (depth > FDT_MAX_DEPTH) {
912			printf("max depth exceeded: %d\n", depth);
913			continue;
914		}
915
916		prevname[depth] = name;
917
918		/* Skip root (i = 1) when printing devices */
919		for (i = 1; i <= depth; i++) {
920			if (prevname[i] == NULL)
921				break;
922
923			if (strcmp(cwd, "/") == 0)
924				printf("/");
925			printf("%s", prevname[i]);
926		}
927		printf("\n");
928	}
929
930	return (CMD_OK);
931}
932
933static __inline int
934isprint(int c)
935{
936
937	return (c >= ' ' && c <= 0x7e);
938}
939
940static int
941fdt_isprint(const void *data, int len, int *count)
942{
943	const char *d;
944	char ch;
945	int yesno, i;
946
947	if (len == 0)
948		return (0);
949
950	d = (const char *)data;
951	if (d[len - 1] != '\0')
952		return (0);
953
954	*count = 0;
955	yesno = 1;
956	for (i = 0; i < len; i++) {
957		ch = *(d + i);
958		if (isprint(ch) || (ch == '\0' && i > 0)) {
959			/* Count strings */
960			if (ch == '\0')
961				(*count)++;
962			continue;
963		}
964
965		yesno = 0;
966		break;
967	}
968
969	return (yesno);
970}
971
972static int
973fdt_data_str(const void *data, int len, int count, char **buf)
974{
975	char *b, *tmp;
976	const char *d;
977	int buf_len, i, l;
978
979	/*
980	 * Calculate the length for the string and allocate memory.
981	 *
982	 * Note that 'len' already includes at least one terminator.
983	 */
984	buf_len = len;
985	if (count > 1) {
986		/*
987		 * Each token had already a terminator buried in 'len', but we
988		 * only need one eventually, don't count space for these.
989		 */
990		buf_len -= count - 1;
991
992		/* Each consecutive token requires a ", " separator. */
993		buf_len += count * 2;
994	}
995
996	/* Add some space for surrounding double quotes. */
997	buf_len += count * 2;
998
999	/* Note that string being put in 'tmp' may be as big as 'buf_len'. */
1000	b = (char *)malloc(buf_len);
1001	tmp = (char *)malloc(buf_len);
1002	if (b == NULL)
1003		goto error;
1004
1005	if (tmp == NULL) {
1006		free(b);
1007		goto error;
1008	}
1009
1010	b[0] = '\0';
1011
1012	/*
1013	 * Now that we have space, format the string.
1014	 */
1015	i = 0;
1016	do {
1017		d = (const char *)data + i;
1018		l = strlen(d) + 1;
1019
1020		sprintf(tmp, "\"%s\"%s", d,
1021		    (i + l) < len ?  ", " : "");
1022		strcat(b, tmp);
1023
1024		i += l;
1025
1026	} while (i < len);
1027	*buf = b;
1028
1029	free(tmp);
1030
1031	return (0);
1032error:
1033	return (1);
1034}
1035
1036static int
1037fdt_data_cell(const void *data, int len, char **buf)
1038{
1039	char *b, *tmp;
1040	const uint32_t *c;
1041	int count, i, l;
1042
1043	/* Number of cells */
1044	count = len / 4;
1045
1046	/*
1047	 * Calculate the length for the string and allocate memory.
1048	 */
1049
1050	/* Each byte translates to 2 output characters */
1051	l = len * 2;
1052	if (count > 1) {
1053		/* Each consecutive cell requires a " " separator. */
1054		l += (count - 1) * 1;
1055	}
1056	/* Each cell will have a "0x" prefix */
1057	l += count * 2;
1058	/* Space for surrounding <> and terminator */
1059	l += 3;
1060
1061	b = (char *)malloc(l);
1062	tmp = (char *)malloc(l);
1063	if (b == NULL)
1064		goto error;
1065
1066	if (tmp == NULL) {
1067		free(b);
1068		goto error;
1069	}
1070
1071	b[0] = '\0';
1072	strcat(b, "<");
1073
1074	for (i = 0; i < len; i += 4) {
1075		c = (const uint32_t *)((const uint8_t *)data + i);
1076		sprintf(tmp, "0x%08x%s", fdt32_to_cpu(*c),
1077		    i < (len - 4) ? " " : "");
1078		strcat(b, tmp);
1079	}
1080	strcat(b, ">");
1081	*buf = b;
1082
1083	free(tmp);
1084
1085	return (0);
1086error:
1087	return (1);
1088}
1089
1090static int
1091fdt_data_bytes(const void *data, int len, char **buf)
1092{
1093	char *b, *tmp;
1094	const char *d;
1095	int i, l;
1096
1097	/*
1098	 * Calculate the length for the string and allocate memory.
1099	 */
1100
1101	/* Each byte translates to 2 output characters */
1102	l = len * 2;
1103	if (len > 1)
1104		/* Each consecutive byte requires a " " separator. */
1105		l += (len - 1) * 1;
1106	/* Each byte will have a "0x" prefix */
1107	l += len * 2;
1108	/* Space for surrounding [] and terminator. */
1109	l += 3;
1110
1111	b = (char *)malloc(l);
1112	tmp = (char *)malloc(l);
1113	if (b == NULL)
1114		goto error;
1115
1116	if (tmp == NULL) {
1117		free(b);
1118		goto error;
1119	}
1120
1121	b[0] = '\0';
1122	strcat(b, "[");
1123
1124	for (i = 0, d = data; i < len; i++) {
1125		sprintf(tmp, "0x%02x%s", d[i], i < len - 1 ? " " : "");
1126		strcat(b, tmp);
1127	}
1128	strcat(b, "]");
1129	*buf = b;
1130
1131	free(tmp);
1132
1133	return (0);
1134error:
1135	return (1);
1136}
1137
1138static int
1139fdt_data_fmt(const void *data, int len, char **buf)
1140{
1141	int count;
1142
1143	if (len == 0) {
1144		*buf = NULL;
1145		return (1);
1146	}
1147
1148	if (fdt_isprint(data, len, &count))
1149		return (fdt_data_str(data, len, count, buf));
1150
1151	else if ((len % 4) == 0)
1152		return (fdt_data_cell(data, len, buf));
1153
1154	else
1155		return (fdt_data_bytes(data, len, buf));
1156}
1157
1158static int
1159fdt_prop(int offset)
1160{
1161	char *line, *buf;
1162	const struct fdt_property *prop;
1163	const char *name;
1164	const void *data;
1165	int len, rv;
1166
1167	line = NULL;
1168	prop = fdt_offset_ptr(fdtp, offset, sizeof(*prop));
1169	if (prop == NULL)
1170		return (1);
1171
1172	name = fdt_string(fdtp, fdt32_to_cpu(prop->nameoff));
1173	len = fdt32_to_cpu(prop->len);
1174
1175	rv = 0;
1176	buf = NULL;
1177	if (len == 0) {
1178		/* Property without value */
1179		line = (char *)malloc(strlen(name) + 2);
1180		if (line == NULL) {
1181			rv = 2;
1182			goto out2;
1183		}
1184		sprintf(line, "%s\n", name);
1185		goto out1;
1186	}
1187
1188	/*
1189	 * Process property with value
1190	 */
1191	data = prop->data;
1192
1193	if (fdt_data_fmt(data, len, &buf) != 0) {
1194		rv = 3;
1195		goto out2;
1196	}
1197
1198	line = (char *)malloc(strlen(name) + strlen(FDT_PROP_SEP) +
1199	    strlen(buf) + 2);
1200	if (line == NULL) {
1201		sprintf(command_errbuf, "could not allocate space for string");
1202		rv = 4;
1203		goto out2;
1204	}
1205
1206	sprintf(line, "%s" FDT_PROP_SEP "%s\n", name, buf);
1207
1208out1:
1209	pager_open();
1210	pager_output(line);
1211	pager_close();
1212
1213out2:
1214	if (buf)
1215		free(buf);
1216
1217	if (line)
1218		free(line);
1219
1220	return (rv);
1221}
1222
1223static int
1224fdt_modprop(int nodeoff, char *propname, void *value, char mode)
1225{
1226	uint32_t cells[100];
1227	const char *buf;
1228	int len, rv;
1229	const struct fdt_property *p;
1230
1231	p = fdt_get_property(fdtp, nodeoff, propname, NULL);
1232
1233	if (p != NULL) {
1234		if (mode == 1) {
1235			 /* Adding inexistant value in mode 1 is forbidden */
1236			sprintf(command_errbuf, "property already exists!");
1237			return (CMD_ERROR);
1238		}
1239	} else if (mode == 0) {
1240		sprintf(command_errbuf, "property does not exist!");
1241		return (CMD_ERROR);
1242	}
1243	len = strlen(value);
1244	rv = 0;
1245	buf = value;
1246
1247	switch (*buf) {
1248	case '&':
1249		/* phandles */
1250		break;
1251	case '<':
1252		/* Data cells */
1253		len = fdt_strtovect(buf, (void *)&cells, 100,
1254		    sizeof(uint32_t));
1255
1256		rv = fdt_setprop(fdtp, nodeoff, propname, &cells,
1257		    len * sizeof(uint32_t));
1258		break;
1259	case '[':
1260		/* Data bytes */
1261		len = fdt_strtovect(buf, (void *)&cells, 100,
1262		    sizeof(uint8_t));
1263
1264		rv = fdt_setprop(fdtp, nodeoff, propname, &cells,
1265		    len * sizeof(uint8_t));
1266		break;
1267	case '"':
1268	default:
1269		/* Default -- string */
1270		rv = fdt_setprop_string(fdtp, nodeoff, propname, value);
1271		break;
1272	}
1273
1274	if (rv != 0) {
1275		if (rv == -FDT_ERR_NOSPACE)
1276			sprintf(command_errbuf,
1277			    "Device tree blob is too small!\n");
1278		else
1279			sprintf(command_errbuf,
1280			    "Could not add/modify property!\n");
1281	}
1282	return (rv);
1283}
1284
1285/* Merge strings from argv into a single string */
1286static int
1287fdt_merge_strings(int argc, char *argv[], int start, char **buffer)
1288{
1289	char *buf;
1290	int i, idx, sz;
1291
1292	*buffer = NULL;
1293	sz = 0;
1294
1295	for (i = start; i < argc; i++)
1296		sz += strlen(argv[i]);
1297
1298	/* Additional bytes for whitespaces between args */
1299	sz += argc - start;
1300
1301	buf = (char *)malloc(sizeof(char) * sz);
1302	if (buf == NULL) {
1303		sprintf(command_errbuf, "could not allocate space "
1304		    "for string");
1305		return (1);
1306	}
1307	bzero(buf, sizeof(char) * sz);
1308
1309	idx = 0;
1310	for (i = start, idx = 0; i < argc; i++) {
1311		strcpy(buf + idx, argv[i]);
1312		idx += strlen(argv[i]);
1313		buf[idx] = ' ';
1314		idx++;
1315	}
1316	buf[sz - 1] = '\0';
1317	*buffer = buf;
1318	return (0);
1319}
1320
1321/* Extract offset and name of node/property from a given path */
1322static int
1323fdt_extract_nameloc(char **pathp, char **namep, int *nodeoff)
1324{
1325	int o;
1326	char *path = *pathp, *name = NULL, *subpath = NULL;
1327
1328	subpath = strrchr(path, '/');
1329	if (subpath == NULL) {
1330		o = fdt_path_offset(fdtp, cwd);
1331		name = path;
1332		path = (char *)&cwd;
1333	} else {
1334		*subpath = '\0';
1335		if (strlen(path) == 0)
1336			path = cwd;
1337
1338		name = subpath + 1;
1339		o = fdt_path_offset(fdtp, path);
1340	}
1341
1342	if (strlen(name) == 0) {
1343		sprintf(command_errbuf, "name not specified");
1344		return (1);
1345	}
1346	if (o < 0) {
1347		sprintf(command_errbuf, "could not find node: '%s'", path);
1348		return (1);
1349	}
1350	*namep = name;
1351	*nodeoff = o;
1352	*pathp = path;
1353	return (0);
1354}
1355
1356static int
1357fdt_cmd_prop(int argc, char *argv[])
1358{
1359	char *path, *propname, *value;
1360	int o, next, depth, rv;
1361	uint32_t tag;
1362
1363	path = (argc > 2) ? argv[2] : NULL;
1364
1365	value = NULL;
1366
1367	if (argc > 3) {
1368		/* Merge property value strings into one */
1369		if (fdt_merge_strings(argc, argv, 3, &value) != 0)
1370			return (CMD_ERROR);
1371	} else
1372		value = NULL;
1373
1374	if (path == NULL)
1375		path = cwd;
1376
1377	rv = CMD_OK;
1378
1379	if (value) {
1380		/* If value is specified -- try to modify prop. */
1381		if (fdt_extract_nameloc(&path, &propname, &o) != 0)
1382			return (CMD_ERROR);
1383
1384		rv = fdt_modprop(o, propname, value, 0);
1385		if (rv)
1386			return (CMD_ERROR);
1387		return (CMD_OK);
1388
1389	}
1390	/* User wants to display properties */
1391	o = fdt_path_offset(fdtp, path);
1392
1393	if (o < 0) {
1394		sprintf(command_errbuf, "could not find node: '%s'", path);
1395		rv = CMD_ERROR;
1396		goto out;
1397	}
1398
1399	depth = 0;
1400	while (depth >= 0) {
1401		tag = fdt_next_tag(fdtp, o, &next);
1402		switch (tag) {
1403		case FDT_NOP:
1404			break;
1405		case FDT_PROP:
1406			if (depth > 1)
1407				/* Don't process properties of nested nodes */
1408				break;
1409
1410			if (fdt_prop(o) != 0) {
1411				sprintf(command_errbuf, "could not process "
1412				    "property");
1413				rv = CMD_ERROR;
1414				goto out;
1415			}
1416			break;
1417		case FDT_BEGIN_NODE:
1418			depth++;
1419			if (depth > FDT_MAX_DEPTH) {
1420				printf("warning: nesting too deep: %d\n",
1421				    depth);
1422				goto out;
1423			}
1424			break;
1425		case FDT_END_NODE:
1426			depth--;
1427			if (depth == 0)
1428				/*
1429				 * This is the end of our starting node, force
1430				 * the loop finish.
1431				 */
1432				depth--;
1433			break;
1434		}
1435		o = next;
1436	}
1437out:
1438	return (rv);
1439}
1440
1441static int
1442fdt_cmd_mkprop(int argc, char *argv[])
1443{
1444	int o;
1445	char *path, *propname, *value;
1446
1447	path = (argc > 2) ? argv[2] : NULL;
1448
1449	value = NULL;
1450
1451	if (argc > 3) {
1452		/* Merge property value strings into one */
1453		if (fdt_merge_strings(argc, argv, 3, &value) != 0)
1454			return (CMD_ERROR);
1455	} else
1456		value = NULL;
1457
1458	if (fdt_extract_nameloc(&path, &propname, &o) != 0)
1459		return (CMD_ERROR);
1460
1461	if (fdt_modprop(o, propname, value, 1))
1462		return (CMD_ERROR);
1463
1464	return (CMD_OK);
1465}
1466
1467static int
1468fdt_cmd_rm(int argc, char *argv[])
1469{
1470	int o, rv;
1471	char *path = NULL, *propname;
1472
1473	if (argc > 2)
1474		path = argv[2];
1475	else {
1476		sprintf(command_errbuf, "no node/property name specified");
1477		return (CMD_ERROR);
1478	}
1479
1480	o = fdt_path_offset(fdtp, path);
1481	if (o < 0) {
1482		/* If node not found -- try to find & delete property */
1483		if (fdt_extract_nameloc(&path, &propname, &o) != 0)
1484			return (CMD_ERROR);
1485
1486		if ((rv = fdt_delprop(fdtp, o, propname)) != 0) {
1487			sprintf(command_errbuf, "could not delete"
1488			    "%s\n", (rv == -FDT_ERR_NOTFOUND) ?
1489			    "(property/node does not exist)" : "");
1490			return (CMD_ERROR);
1491
1492		} else
1493			return (CMD_OK);
1494	}
1495	/* If node exists -- remove node */
1496	rv = fdt_del_node(fdtp, o);
1497	if (rv) {
1498		sprintf(command_errbuf, "could not delete node");
1499		return (CMD_ERROR);
1500	}
1501	return (CMD_OK);
1502}
1503
1504static int
1505fdt_cmd_mknode(int argc, char *argv[])
1506{
1507	int o, rv;
1508	char *path = NULL, *nodename = NULL;
1509
1510	if (argc > 2)
1511		path = argv[2];
1512	else {
1513		sprintf(command_errbuf, "no node name specified");
1514		return (CMD_ERROR);
1515	}
1516
1517	if (fdt_extract_nameloc(&path, &nodename, &o) != 0)
1518		return (CMD_ERROR);
1519
1520	rv = fdt_add_subnode(fdtp, o, nodename);
1521
1522	if (rv < 0) {
1523		if (rv == -FDT_ERR_NOSPACE)
1524			sprintf(command_errbuf,
1525			    "Device tree blob is too small!\n");
1526		else
1527			sprintf(command_errbuf,
1528			    "Could not add node!\n");
1529		return (CMD_ERROR);
1530	}
1531	return (CMD_OK);
1532}
1533
1534static int
1535fdt_cmd_pwd(int argc, char *argv[])
1536{
1537	char line[FDT_CWD_LEN];
1538
1539	pager_open();
1540	sprintf(line, "%s\n", cwd);
1541	pager_output(line);
1542	pager_close();
1543	return (CMD_OK);
1544}
1545
1546static int
1547fdt_cmd_mres(int argc, char *argv[])
1548{
1549	uint64_t start, size;
1550	int i, total;
1551	char line[80];
1552
1553	pager_open();
1554	total = fdt_num_mem_rsv(fdtp);
1555	if (total > 0) {
1556		pager_output("Reserved memory regions:\n");
1557		for (i = 0; i < total; i++) {
1558			fdt_get_mem_rsv(fdtp, i, &start, &size);
1559			sprintf(line, "reg#%d: (start: 0x%jx, size: 0x%jx)\n",
1560			    i, start, size);
1561			pager_output(line);
1562		}
1563	} else
1564		pager_output("No reserved memory regions\n");
1565	pager_close();
1566
1567	return (CMD_OK);
1568}
1569
1570static int
1571fdt_cmd_nyi(int argc, char *argv[])
1572{
1573
1574	printf("command not yet implemented\n");
1575	return (CMD_ERROR);
1576}
1577