1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
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(s: name); |
43 | fprintf(stderr, format: "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, format: "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(s: *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(ptr: value, size: value_size); |
86 | if (!value) { |
87 | fprintf(stderr, format: "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(dest: ptr, src: *arg, n: len); |
96 | if (disp->verbose) |
97 | fprintf(stderr, format: "\tstring: '%s'\n" , ptr); |
98 | } else { |
99 | int *iptr = (int *)ptr; |
100 | sscanf(s: *arg, format: 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, format: "\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, format: "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(name: node_name, namelen: -1, err: node); |
129 | return -1; |
130 | } |
131 | |
132 | err = fdt_setprop(blob, node, property, buf, len); |
133 | if (err) { |
134 | report_error(name: property, namelen: -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(s: path, c: '/'); |
163 | if (!sep) |
164 | sep = path + strlen(s: 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(name: path, namelen: sep - path, err: 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(s: node_name, c: '/'); |
198 | if (!p) { |
199 | report_error(name: node_name, namelen: -1, err: -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(name: node_name, namelen: -1, err: node); |
208 | return -1; |
209 | } |
210 | } |
211 | |
212 | node = fdt_add_subnode(blob, node, p + 1); |
213 | if (node < 0) { |
214 | report_error(name: p + 1, namelen: -1, err: 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, in_path: *arg)) |
240 | return -1; |
241 | if (encode_value(disp, arg: arg + 2, arg_count: arg_count - 2, valuep: &value, value_len: &len) || |
242 | store_key_value(blob, node_name: *arg, property: arg[1], buf: 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, in_path: *arg); |
249 | else |
250 | ret = create_node(blob, node_name: *arg); |
251 | } |
252 | break; |
253 | } |
254 | if (ret >= 0) |
255 | ret = utilfdt_write(filename, blob); |
256 | |
257 | free(ptr: 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, format: "Error: %s\n\n" , msg); |
281 | |
282 | fprintf(stderr, format: "%s" , usage_msg); |
283 | exit(status: 2); |
284 | } |
285 | |
286 | int main(int argc, char *argv[]) |
287 | { |
288 | struct display_info disp; |
289 | char *filename = NULL; |
290 | |
291 | memset(s: &disp, c: '\0', n: sizeof(disp)); |
292 | disp.size = -1; |
293 | disp.oper = OPER_WRITE_PROP; |
294 | for (;;) { |
295 | int c = getopt(argc: argc, argv: argv, shortopts: "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(fmt: optarg, type: &disp.type, |
320 | size: &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: &disp, filename, arg: argv, arg_count: argc)) |
346 | return 1; |
347 | return 0; |
348 | } |
349 | |