1/*
2 * Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of
7 * the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
17 * MA 02111-1307 USA
18 */
19
20#include <assert.h>
21#include <ctype.h>
22#include <getopt.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include <libfdt.h>
28
29#include "util.h"
30
31/* These are the operations we support */
32enum oper_type {
33	OPER_WRITE_PROP,		/* Write a property in a node */
34	OPER_CREATE_NODE,		/* Create a new node */
35	OPER_REMOVE_NODE,		/* Delete a node */
36	OPER_DELETE_PROP,		/* Delete a property in a node */
37};
38
39struct display_info {
40	enum oper_type oper;	/* operation to perform */
41	int type;		/* data type (s/i/u/x or 0 for default) */
42	int size;		/* data size (1/2/4) */
43	int verbose;		/* verbose output */
44	int auto_path;		/* automatically create all path components */
45};
46
47
48/**
49 * Report an error with a particular node.
50 *
51 * @param name		Node name to report error on
52 * @param namelen	Length of node name, or -1 to use entire string
53 * @param err		Error number to report (-FDT_ERR_...)
54 */
55static void report_error(const char *name, int namelen, int err)
56{
57	if (namelen == -1)
58		namelen = strlen(name);
59	fprintf(stderr, "Error at '%1.*s': %s\n", namelen, name,
60		fdt_strerror(err));
61}
62
63/**
64 * Encode a series of arguments in a property value.
65 *
66 * @param disp		Display information / options
67 * @param arg		List of arguments from command line
68 * @param arg_count	Number of arguments (may be 0)
69 * @param valuep	Returns buffer containing value
70 * @param *value_len	Returns length of value encoded
71 */
72static int encode_value(struct display_info *disp, char **arg, int arg_count,
73			char **valuep, int *value_len)
74{
75	char *value = NULL;	/* holding area for value */
76	int value_size = 0;	/* size of holding area */
77	char *ptr;		/* pointer to current value position */
78	int len;		/* length of this cell/string/byte */
79	int ival;
80	int upto;	/* the number of bytes we have written to buf */
81	char fmt[3];
82
83	upto = 0;
84
85	if (disp->verbose)
86		fprintf(stderr, "Decoding value:\n");
87
88	fmt[0] = '%';
89	fmt[1] = disp->type ? disp->type : 'd';
90	fmt[2] = '\0';
91	for (; arg_count > 0; arg++, arg_count--, upto += len) {
92		/* assume integer unless told otherwise */
93		if (disp->type == 's')
94			len = strlen(*arg) + 1;
95		else
96			len = disp->size == -1 ? 4 : disp->size;
97
98		/* enlarge our value buffer by a suitable margin if needed */
99		if (upto + len > value_size) {
100			value_size = (upto + len) + 500;
101			value = xrealloc(value, value_size);
102		}
103
104		ptr = value + upto;
105		if (disp->type == 's') {
106			memcpy(ptr, *arg, len);
107			if (disp->verbose)
108				fprintf(stderr, "\tstring: '%s'\n", ptr);
109		} else {
110			int *iptr = (int *)ptr;
111			sscanf(*arg, fmt, &ival);
112			if (len == 4)
113				*iptr = cpu_to_fdt32(ival);
114			else
115				*ptr = (uint8_t)ival;
116			if (disp->verbose) {
117				fprintf(stderr, "\t%s: %d\n",
118					disp->size == 1 ? "byte" :
119					disp->size == 2 ? "short" : "int",
120					ival);
121			}
122		}
123	}
124	*value_len = upto;
125	*valuep = value;
126	if (disp->verbose)
127		fprintf(stderr, "Value size %d\n", upto);
128	return 0;
129}
130
131#define ALIGN(x)		(((x) + (FDT_TAGSIZE) - 1) & ~((FDT_TAGSIZE) - 1))
132
133static char *_realloc_fdt(char *fdt, int delta)
134{
135	int new_sz = fdt_totalsize(fdt) + delta;
136	fdt = xrealloc(fdt, new_sz);
137	fdt_open_into(fdt, fdt, new_sz);
138	return fdt;
139}
140
141static char *realloc_node(char *fdt, const char *name)
142{
143	int delta;
144	/* FDT_BEGIN_NODE, node name in off_struct and FDT_END_NODE */
145	delta = sizeof(struct fdt_node_header) + ALIGN(strlen(name) + 1)
146			+ FDT_TAGSIZE;
147	return _realloc_fdt(fdt, delta);
148}
149
150static char *realloc_property(char *fdt, int nodeoffset,
151		const char *name, int newlen)
152{
153	int delta = 0;
154	int oldlen = 0;
155
156	if (!fdt_get_property(fdt, nodeoffset, name, &oldlen))
157		/* strings + property header */
158		delta = sizeof(struct fdt_property) + strlen(name) + 1;
159
160	if (newlen > oldlen)
161		/* actual value in off_struct */
162		delta += ALIGN(newlen) - ALIGN(oldlen);
163
164	return _realloc_fdt(fdt, delta);
165}
166
167static int store_key_value(char **blob, const char *node_name,
168		const char *property, const char *buf, int len)
169{
170	int node;
171	int err;
172
173	node = fdt_path_offset(*blob, node_name);
174	if (node < 0) {
175		report_error(node_name, -1, node);
176		return -1;
177	}
178
179	err = fdt_setprop(*blob, node, property, buf, len);
180	if (err == -FDT_ERR_NOSPACE) {
181		*blob = realloc_property(*blob, node, property, len);
182		err = fdt_setprop(*blob, node, property, buf, len);
183	}
184	if (err) {
185		report_error(property, -1, err);
186		return -1;
187	}
188	return 0;
189}
190
191/**
192 * Create paths as needed for all components of a path
193 *
194 * Any components of the path that do not exist are created. Errors are
195 * reported.
196 *
197 * @param blob		FDT blob to write into
198 * @param in_path	Path to process
199 * @return 0 if ok, -1 on error
200 */
201static int create_paths(char **blob, const char *in_path)
202{
203	const char *path = in_path;
204	const char *sep;
205	int node, offset = 0;
206
207	/* skip leading '/' */
208	while (*path == '/')
209		path++;
210
211	for (sep = path; *sep; path = sep + 1, offset = node) {
212		/* equivalent to strchrnul(), but it requires _GNU_SOURCE */
213		sep = strchr(path, '/');
214		if (!sep)
215			sep = path + strlen(path);
216
217		node = fdt_subnode_offset_namelen(*blob, offset, path,
218				sep - path);
219		if (node == -FDT_ERR_NOTFOUND) {
220			*blob = realloc_node(*blob, path);
221			node = fdt_add_subnode_namelen(*blob, offset, path,
222						       sep - path);
223		}
224		if (node < 0) {
225			report_error(path, sep - path, node);
226			return -1;
227		}
228	}
229
230	return 0;
231}
232
233/**
234 * Create a new node in the fdt.
235 *
236 * This will overwrite the node_name string. Any error is reported.
237 *
238 * TODO: Perhaps create fdt_path_offset_namelen() so we don't need to do this.
239 *
240 * @param blob		FDT blob to write into
241 * @param node_name	Name of node to create
242 * @return new node offset if found, or -1 on failure
243 */
244static int create_node(char **blob, const char *node_name)
245{
246	int node = 0;
247	char *p;
248
249	p = strrchr(node_name, '/');
250	if (!p) {
251		report_error(node_name, -1, -FDT_ERR_BADPATH);
252		return -1;
253	}
254	*p = '\0';
255
256	*blob = realloc_node(*blob, p + 1);
257
258	if (p > node_name) {
259		node = fdt_path_offset(*blob, node_name);
260		if (node < 0) {
261			report_error(node_name, -1, node);
262			return -1;
263		}
264	}
265
266	node = fdt_add_subnode(*blob, node, p + 1);
267	if (node < 0) {
268		report_error(p + 1, -1, node);
269		return -1;
270	}
271
272	return 0;
273}
274
275/**
276 * Delete a property of a node in the fdt.
277 *
278 * @param blob		FDT blob to write into
279 * @param node_name	Path to node containing the property to delete
280 * @param prop_name	Name of property to delete
281 * @return 0 on success, or -1 on failure
282 */
283static int delete_prop(char *blob, const char *node_name, const char *prop_name)
284{
285	int node = 0;
286
287	node = fdt_path_offset(blob, node_name);
288	if (node < 0) {
289		report_error(node_name, -1, node);
290		return -1;
291	}
292
293	node = fdt_delprop(blob, node, prop_name);
294	if (node < 0) {
295		report_error(node_name, -1, node);
296		return -1;
297	}
298
299	return 0;
300}
301
302/**
303 * Delete a node in the fdt.
304 *
305 * @param blob		FDT blob to write into
306 * @param node_name	Name of node to delete
307 * @return 0 on success, or -1 on failure
308 */
309static int delete_node(char *blob, const char *node_name)
310{
311	int node = 0;
312
313	node = fdt_path_offset(blob, node_name);
314	if (node < 0) {
315		report_error(node_name, -1, node);
316		return -1;
317	}
318
319	node = fdt_del_node(blob, node);
320	if (node < 0) {
321		report_error(node_name, -1, node);
322		return -1;
323	}
324
325	return 0;
326}
327
328static int do_fdtput(struct display_info *disp, const char *filename,
329		    char **arg, int arg_count)
330{
331	char *value = NULL;
332	char *blob;
333	char *node;
334	int len, ret = 0;
335
336	blob = utilfdt_read(filename);
337	if (!blob)
338		return -1;
339
340	switch (disp->oper) {
341	case OPER_WRITE_PROP:
342		/*
343		 * Convert the arguments into a single binary value, then
344		 * store them into the property.
345		 */
346		assert(arg_count >= 2);
347		if (disp->auto_path && create_paths(&blob, *arg))
348			return -1;
349		if (encode_value(disp, arg + 2, arg_count - 2, &value, &len) ||
350			store_key_value(&blob, *arg, arg[1], value, len))
351			ret = -1;
352		break;
353	case OPER_CREATE_NODE:
354		for (; ret >= 0 && arg_count--; arg++) {
355			if (disp->auto_path)
356				ret = create_paths(&blob, *arg);
357			else
358				ret = create_node(&blob, *arg);
359		}
360		break;
361	case OPER_REMOVE_NODE:
362		for (; ret >= 0 && arg_count--; arg++)
363			ret = delete_node(blob, *arg);
364		break;
365	case OPER_DELETE_PROP:
366		node = *arg;
367		for (arg++; ret >= 0 && arg_count-- > 1; arg++)
368			ret = delete_prop(blob, node, *arg);
369		break;
370	}
371	if (ret >= 0) {
372		fdt_pack(blob);
373		ret = utilfdt_write(filename, blob);
374	}
375
376	free(blob);
377
378	if (value) {
379		free(value);
380	}
381
382	return ret;
383}
384
385/* Usage related data. */
386static const char usage_synopsis[] =
387	"write a property value to a device tree\n"
388	"	fdtput <options> <dt file> <node> <property> [<value>...]\n"
389	"	fdtput -c <options> <dt file> [<node>...]\n"
390	"	fdtput -r <options> <dt file> [<node>...]\n"
391	"	fdtput -d <options> <dt file> <node> [<property>...]\n"
392	"\n"
393	"The command line arguments are joined together into a single value.\n"
394	USAGE_TYPE_MSG;
395static const char usage_short_opts[] = "crdpt:v" USAGE_COMMON_SHORT_OPTS;
396static struct option const usage_long_opts[] = {
397	{"create",           no_argument, NULL, 'c'},
398	{"remove",	     no_argument, NULL, 'r'},
399	{"delete",	     no_argument, NULL, 'd'},
400	{"auto-path",        no_argument, NULL, 'p'},
401	{"type",              a_argument, NULL, 't'},
402	{"verbose",          no_argument, NULL, 'v'},
403	USAGE_COMMON_LONG_OPTS,
404};
405static const char * const usage_opts_help[] = {
406	"Create nodes if they don't already exist",
407	"Delete nodes (and any subnodes) if they already exist",
408	"Delete properties if they already exist",
409	"Automatically create nodes as needed for the node path",
410	"Type of data",
411	"Display each value decoded from command line",
412	USAGE_COMMON_OPTS_HELP
413};
414
415int main(int argc, char *argv[])
416{
417	int opt;
418	struct display_info disp;
419	char *filename = NULL;
420
421	memset(&disp, '\0', sizeof(disp));
422	disp.size = -1;
423	disp.oper = OPER_WRITE_PROP;
424	while ((opt = util_getopt_long()) != EOF) {
425		/*
426		 * TODO: add options to:
427		 * - rename node
428		 * - pack fdt before writing
429		 * - set amount of free space when writing
430		 */
431		switch (opt) {
432		case_USAGE_COMMON_FLAGS
433
434		case 'c':
435			disp.oper = OPER_CREATE_NODE;
436			break;
437		case 'r':
438			disp.oper = OPER_REMOVE_NODE;
439			break;
440		case 'd':
441			disp.oper = OPER_DELETE_PROP;
442			break;
443		case 'p':
444			disp.auto_path = 1;
445			break;
446		case 't':
447			if (utilfdt_decode_type(optarg, &disp.type,
448					&disp.size))
449				usage("Invalid type string");
450			break;
451
452		case 'v':
453			disp.verbose = 1;
454			break;
455		}
456	}
457
458	if (optind < argc)
459		filename = argv[optind++];
460	if (!filename)
461		usage("missing filename");
462
463	argv += optind;
464	argc -= optind;
465
466	if (disp.oper == OPER_WRITE_PROP) {
467		if (argc < 1)
468			usage("missing node");
469		if (argc < 2)
470			usage("missing property");
471	}
472
473	if (disp.oper == OPER_DELETE_PROP)
474		if (argc < 1)
475			usage("missing node");
476
477	if (do_fdtput(&disp, filename, argv, argc))
478		return 1;
479	return 0;
480}
481