Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
cd296721 SW |
2 | /* |
3 | * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | |
cd296721 SW |
4 | */ |
5 | ||
6 | #include <assert.h> | |
7 | #include <ctype.h> | |
8 | #include <getopt.h> | |
9 | #include <stdio.h> | |
10 | #include <stdlib.h> | |
11 | #include <string.h> | |
12 | ||
13 | #include <libfdt.h> | |
14 | ||
15 | #include "util.h" | |
16 | ||
17 | /* These are the operations we support */ | |
18 | enum oper_type { | |
19 | OPER_WRITE_PROP, /* Write a property in a node */ | |
20 | OPER_CREATE_NODE, /* Create a new node */ | |
21 | }; | |
22 | ||
23 | struct display_info { | |
24 | enum oper_type oper; /* operation to perform */ | |
25 | int type; /* data type (s/i/u/x or 0 for default) */ | |
26 | int size; /* data size (1/2/4) */ | |
27 | int verbose; /* verbose output */ | |
28 | int auto_path; /* automatically create all path components */ | |
29 | }; | |
30 | ||
31 | ||
32 | /** | |
33 | * Report an error with a particular node. | |
34 | * | |
35 | * @param name Node name to report error on | |
36 | * @param namelen Length of node name, or -1 to use entire string | |
37 | * @param err Error number to report (-FDT_ERR_...) | |
38 | */ | |
39 | static void report_error(const char *name, int namelen, int err) | |
40 | { | |
41 | if (namelen == -1) | |
42 | namelen = strlen(name); | |
43 | fprintf(stderr, "Error at '%1.*s': %s\n", namelen, name, | |
44 | fdt_strerror(err)); | |
45 | } | |
46 | ||
47 | /** | |
48 | * Encode a series of arguments in a property value. | |
49 | * | |
50 | * @param disp Display information / options | |
51 | * @param arg List of arguments from command line | |
52 | * @param arg_count Number of arguments (may be 0) | |
53 | * @param valuep Returns buffer containing value | |
54 | * @param *value_len Returns length of value encoded | |
55 | */ | |
56 | static int encode_value(struct display_info *disp, char **arg, int arg_count, | |
57 | char **valuep, int *value_len) | |
58 | { | |
59 | char *value = NULL; /* holding area for value */ | |
60 | int value_size = 0; /* size of holding area */ | |
61 | char *ptr; /* pointer to current value position */ | |
62 | int len; /* length of this cell/string/byte */ | |
63 | int ival; | |
64 | int upto; /* the number of bytes we have written to buf */ | |
65 | char fmt[3]; | |
66 | ||
67 | upto = 0; | |
68 | ||
69 | if (disp->verbose) | |
70 | fprintf(stderr, "Decoding value:\n"); | |
71 | ||
72 | fmt[0] = '%'; | |
73 | fmt[1] = disp->type ? disp->type : 'd'; | |
74 | fmt[2] = '\0'; | |
75 | for (; arg_count > 0; arg++, arg_count--, upto += len) { | |
76 | /* assume integer unless told otherwise */ | |
77 | if (disp->type == 's') | |
78 | len = strlen(*arg) + 1; | |
79 | else | |
80 | len = disp->size == -1 ? 4 : disp->size; | |
81 | ||
82 | /* enlarge our value buffer by a suitable margin if needed */ | |
83 | if (upto + len > value_size) { | |
84 | value_size = (upto + len) + 500; | |
85 | value = realloc(value, value_size); | |
86 | if (!value) { | |
87 | fprintf(stderr, "Out of mmory: cannot alloc " | |
88 | "%d bytes\n", value_size); | |
89 | return -1; | |
90 | } | |
91 | } | |
92 | ||
93 | ptr = value + upto; | |
94 | if (disp->type == 's') { | |
95 | memcpy(ptr, *arg, len); | |
96 | if (disp->verbose) | |
97 | fprintf(stderr, "\tstring: '%s'\n", ptr); | |
98 | } else { | |
99 | int *iptr = (int *)ptr; | |
100 | sscanf(*arg, fmt, &ival); | |
101 | if (len == 4) | |
102 | *iptr = cpu_to_fdt32(ival); | |
103 | else | |
104 | *ptr = (uint8_t)ival; | |
105 | if (disp->verbose) { | |
106 | fprintf(stderr, "\t%s: %d\n", | |
107 | disp->size == 1 ? "byte" : | |
108 | disp->size == 2 ? "short" : "int", | |
109 | ival); | |
110 | } | |
111 | } | |
112 | } | |
113 | *value_len = upto; | |
114 | *valuep = value; | |
115 | if (disp->verbose) | |
116 | fprintf(stderr, "Value size %d\n", upto); | |
117 | return 0; | |
118 | } | |
119 | ||
120 | static int store_key_value(void *blob, const char *node_name, | |
121 | const char *property, const char *buf, int len) | |
122 | { | |
123 | int node; | |
124 | int err; | |
125 | ||
126 | node = fdt_path_offset(blob, node_name); | |
127 | if (node < 0) { | |
128 | report_error(node_name, -1, node); | |
129 | return -1; | |
130 | } | |
131 | ||
132 | err = fdt_setprop(blob, node, property, buf, len); | |
133 | if (err) { | |
134 | report_error(property, -1, err); | |
135 | return -1; | |
136 | } | |
137 | return 0; | |
138 | } | |
139 | ||
140 | /** | |
141 | * Create paths as needed for all components of a path | |
142 | * | |
143 | * Any components of the path that do not exist are created. Errors are | |
144 | * reported. | |
145 | * | |
146 | * @param blob FDT blob to write into | |
147 | * @param in_path Path to process | |
148 | * @return 0 if ok, -1 on error | |
149 | */ | |
150 | static int create_paths(void *blob, const char *in_path) | |
151 | { | |
152 | const char *path = in_path; | |
153 | const char *sep; | |
154 | int node, offset = 0; | |
155 | ||
156 | /* skip leading '/' */ | |
157 | while (*path == '/') | |
158 | path++; | |
159 | ||
160 | for (sep = path; *sep; path = sep + 1, offset = node) { | |
161 | /* equivalent to strchrnul(), but it requires _GNU_SOURCE */ | |
162 | sep = strchr(path, '/'); | |
163 | if (!sep) | |
164 | sep = path + strlen(path); | |
165 | ||
166 | node = fdt_subnode_offset_namelen(blob, offset, path, | |
167 | sep - path); | |
168 | if (node == -FDT_ERR_NOTFOUND) { | |
169 | node = fdt_add_subnode_namelen(blob, offset, path, | |
170 | sep - path); | |
171 | } | |
172 | if (node < 0) { | |
173 | report_error(path, sep - path, node); | |
174 | return -1; | |
175 | } | |
176 | } | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | /** | |
182 | * Create a new node in the fdt. | |
183 | * | |
184 | * This will overwrite the node_name string. Any error is reported. | |
185 | * | |
186 | * TODO: Perhaps create fdt_path_offset_namelen() so we don't need to do this. | |
187 | * | |
188 | * @param blob FDT blob to write into | |
189 | * @param node_name Name of node to create | |
190 | * @return new node offset if found, or -1 on failure | |
191 | */ | |
192 | static int create_node(void *blob, const char *node_name) | |
193 | { | |
194 | int node = 0; | |
195 | char *p; | |
196 | ||
197 | p = strrchr(node_name, '/'); | |
198 | if (!p) { | |
199 | report_error(node_name, -1, -FDT_ERR_BADPATH); | |
200 | return -1; | |
201 | } | |
202 | *p = '\0'; | |
203 | ||
204 | if (p > node_name) { | |
205 | node = fdt_path_offset(blob, node_name); | |
206 | if (node < 0) { | |
207 | report_error(node_name, -1, node); | |
208 | return -1; | |
209 | } | |
210 | } | |
211 | ||
212 | node = fdt_add_subnode(blob, node, p + 1); | |
213 | if (node < 0) { | |
214 | report_error(p + 1, -1, node); | |
215 | return -1; | |
216 | } | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
221 | static int do_fdtput(struct display_info *disp, const char *filename, | |
222 | char **arg, int arg_count) | |
223 | { | |
224 | char *value; | |
225 | char *blob; | |
226 | int len, ret = 0; | |
227 | ||
228 | blob = utilfdt_read(filename); | |
229 | if (!blob) | |
230 | return -1; | |
231 | ||
232 | switch (disp->oper) { | |
233 | case OPER_WRITE_PROP: | |
234 | /* | |
235 | * Convert the arguments into a single binary value, then | |
236 | * store them into the property. | |
237 | */ | |
238 | assert(arg_count >= 2); | |
239 | if (disp->auto_path && create_paths(blob, *arg)) | |
240 | return -1; | |
241 | if (encode_value(disp, arg + 2, arg_count - 2, &value, &len) || | |
242 | store_key_value(blob, *arg, arg[1], value, len)) | |
243 | ret = -1; | |
244 | break; | |
245 | case OPER_CREATE_NODE: | |
246 | for (; ret >= 0 && arg_count--; arg++) { | |
247 | if (disp->auto_path) | |
248 | ret = create_paths(blob, *arg); | |
249 | else | |
250 | ret = create_node(blob, *arg); | |
251 | } | |
252 | break; | |
253 | } | |
254 | if (ret >= 0) | |
255 | ret = utilfdt_write(filename, blob); | |
256 | ||
257 | free(blob); | |
258 | return ret; | |
259 | } | |
260 | ||
261 | static const char *usage_msg = | |
262 | "fdtput - write a property value to a device tree\n" | |
263 | "\n" | |
264 | "The command line arguments are joined together into a single value.\n" | |
265 | "\n" | |
266 | "Usage:\n" | |
267 | " fdtput <options> <dt file> <node> <property> [<value>...]\n" | |
268 | " fdtput -c <options> <dt file> [<node>...]\n" | |
269 | "Options:\n" | |
270 | "\t-c\t\tCreate nodes if they don't already exist\n" | |
271 | "\t-p\t\tAutomatically create nodes as needed for the node path\n" | |
272 | "\t-t <type>\tType of data\n" | |
273 | "\t-v\t\tVerbose: display each value decoded from command line\n" | |
274 | "\t-h\t\tPrint this help\n\n" | |
275 | USAGE_TYPE_MSG; | |
276 | ||
277 | static void usage(const char *msg) | |
278 | { | |
279 | if (msg) | |
280 | fprintf(stderr, "Error: %s\n\n", msg); | |
281 | ||
282 | fprintf(stderr, "%s", usage_msg); | |
283 | exit(2); | |
284 | } | |
285 | ||
286 | int main(int argc, char *argv[]) | |
287 | { | |
288 | struct display_info disp; | |
289 | char *filename = NULL; | |
290 | ||
291 | memset(&disp, '\0', sizeof(disp)); | |
292 | disp.size = -1; | |
293 | disp.oper = OPER_WRITE_PROP; | |
294 | for (;;) { | |
295 | int c = getopt(argc, argv, "chpt:v"); | |
296 | if (c == -1) | |
297 | break; | |
298 | ||
299 | /* | |
300 | * TODO: add options to: | |
301 | * - delete property | |
302 | * - delete node (optionally recursively) | |
303 | * - rename node | |
304 | * - pack fdt before writing | |
305 | * - set amount of free space when writing | |
306 | * - expand fdt if value doesn't fit | |
307 | */ | |
308 | switch (c) { | |
309 | case 'c': | |
310 | disp.oper = OPER_CREATE_NODE; | |
311 | break; | |
312 | case 'h': | |
313 | case '?': | |
314 | usage(NULL); | |
315 | case 'p': | |
316 | disp.auto_path = 1; | |
317 | break; | |
318 | case 't': | |
319 | if (utilfdt_decode_type(optarg, &disp.type, | |
320 | &disp.size)) | |
321 | usage("Invalid type string"); | |
322 | break; | |
323 | ||
324 | case 'v': | |
325 | disp.verbose = 1; | |
326 | break; | |
327 | } | |
328 | } | |
329 | ||
330 | if (optind < argc) | |
331 | filename = argv[optind++]; | |
332 | if (!filename) | |
333 | usage("Missing filename"); | |
334 | ||
335 | argv += optind; | |
336 | argc -= optind; | |
337 | ||
338 | if (disp.oper == OPER_WRITE_PROP) { | |
339 | if (argc < 1) | |
340 | usage("Missing node"); | |
341 | if (argc < 2) | |
342 | usage("Missing property"); | |
343 | } | |
344 | ||
345 | if (do_fdtput(&disp, filename, argv, argc)) | |
346 | return 1; | |
347 | return 0; | |
348 | } |