blob: cfc7afdd75d0c1e46f6b96530af9e83a44cd9cae [file] [log] [blame]
Nico Huber8f7122c2023-02-11 18:28:33 +01001/*
2 * This file is part of the flashprog project.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (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
15#include <stdio.h>
16#include <stdlib.h>
17#include <stdbool.h>
18#include <string.h>
19#include <getopt.h>
20
21#include "libflashprog.h"
22#include "cli.h"
23
24static void usage(const char *const name, const char *const msg)
25{
26 if (msg)
27 fprintf(stderr, "\nError: %s\n", msg);
28
29 fprintf(stderr, "\nUsage:"
30 "\t%s [status] <options>\n"
31 "\t%s list <options>\n"
32 "\t%s disable <options> [--temporary]\n"
33 "\t%s enable <options> [--temporary]\n"
34 "\t%s range <options> [--temporary] <start>,<len>\n"
35 "\t%s region <options> [--temporary] <region-name>\n",
36 name, name, name, name, name, name);
37 fprintf(stderr, "\n"
38 "A range is specified by two integers, the offset from the start of the flash\n"
39 "and the length in bytes. A region is specified by name from the layout, see\n"
40 "layout options below.\n");
41 print_generic_options(/* layout_options =>*/true);
42 exit(1);
43}
44
45static int parse_wp_range(size_t *const start, size_t *const len, const char *const arg)
46{
47 size_t processed;
48
49 if (sscanf(arg, "%zi,%zi%zn", start, len, &processed) != 2)
50 return -1;
51
52 if (*start > SIZE_MAX / 2 || *len > SIZE_MAX / 2)
53 return -1;
54
55 if (processed != strlen(arg))
56 return -1;
57
58 return 0;
59}
60
61static void print_wp_range(const char *const prefix,
62 struct flashprog_flashctx *const flash,
63 size_t start, size_t len)
64{
65 /* Start address and length */
66 printf("%sstart=0x%08zx length=0x%08zx ", prefix, start, len);
67
68 /* Easily readable description like 'none' or 'lower 1/8' */
69 size_t chip_len = flashprog_flash_getsize(flash);
70
71 if (len == 0) {
72 printf("(none)\n");
73 } else if (len == chip_len) {
74 printf("(all)\n");
75 } else {
76 const char *location = "";
77 if (start == 0)
78 location = "lower ";
79 if (start == chip_len - len)
80 location = "upper ";
81
82 /* Remove common factors of 2 to simplify */
83 /* the (range_len/chip_len) fraction. */
84 while ((chip_len % 2) == 0 && (len % 2) == 0) {
85 chip_len /= 2;
86 len /= 2;
87 }
88
89 printf("(%s%zu/%zu)\n", location, len, chip_len);
90 }
91}
92
93static const char *get_wp_error_str(int err)
94{
95 switch (err) {
96 case FLASHPROG_WP_ERR_CHIP_UNSUPPORTED:
97 return "WP operations are not implemented for this chip";
98 case FLASHPROG_WP_ERR_READ_FAILED:
99 return "failed to read the current WP configuration";
100 case FLASHPROG_WP_ERR_WRITE_FAILED:
101 return "failed to write the new WP configuration";
102 case FLASHPROG_WP_ERR_VERIFY_FAILED:
103 return "unexpected WP configuration read back from chip";
104 case FLASHPROG_WP_ERR_MODE_UNSUPPORTED:
105 return "the requested protection mode is not supported";
106 case FLASHPROG_WP_ERR_RANGE_UNSUPPORTED:
107 return "the requested protection range is not supported";
108 case FLASHPROG_WP_ERR_RANGE_LIST_UNAVAILABLE:
109 return "could not determine what protection ranges are available";
110 case FLASHPROG_WP_ERR_UNSUPPORTED_STATE:
111 return "can't operate on current WP configuration of the chip";
112 }
113 return "unknown WP error";
114}
115
116static int wp_print_status(struct flashprog_flashctx *const flash)
117{
118 size_t start, len;
119 enum flashprog_wp_mode mode;
120 struct flashprog_wp_cfg *cfg = NULL;
121 enum flashprog_wp_result ret;
122
123 ret = flashprog_wp_cfg_new(&cfg);
124 if (ret == FLASHPROG_WP_OK)
125 ret = flashprog_wp_read_cfg(cfg, flash);
126
127 if (ret != FLASHPROG_WP_OK) {
128 fprintf(stderr, "Failed to get WP status: %s\n", get_wp_error_str(ret));
129 flashprog_wp_cfg_release(cfg);
130 return 1;
131 }
132
133 flashprog_wp_get_range(&start, &len, cfg);
134 mode = flashprog_wp_get_mode(cfg);
135 flashprog_wp_cfg_release(cfg);
136
137 print_wp_range("Protection range: ", flash, start, len);
138
139 const char *mode_desc;
140 switch (mode) {
141 case FLASHPROG_WP_MODE_DISABLED: mode_desc = "disabled"; break;
142 case FLASHPROG_WP_MODE_HARDWARE: mode_desc = "hardware"; break;
143 case FLASHPROG_WP_MODE_POWER_CYCLE: mode_desc = "power_cycle"; break;
144 case FLASHPROG_WP_MODE_PERMANENT: mode_desc = "permanent"; break;
145 default: mode_desc = "unknown"; break;
146 }
147 printf("Protection mode: %s\n", mode_desc);
148
149 return 0;
150}
151
152static int wp_print_ranges(struct flashprog_flashctx *const flash)
153{
154 struct flashprog_wp_ranges *list;
155 size_t i;
156
157 const enum flashprog_wp_result ret = flashprog_wp_get_available_ranges(&list, flash);
158 if (ret != FLASHPROG_WP_OK) {
159 fprintf(stderr, "Failed to get list of protection ranges: %s\n", get_wp_error_str(ret));
160 return 1;
161 }
162
163 printf("Available protection ranges:\n");
164 const size_t count = flashprog_wp_ranges_get_count(list);
165 for (i = 0; i < count; i++) {
166 size_t start, len;
167
168 flashprog_wp_ranges_get_range(&start, &len, list, i);
169 print_wp_range("\t", flash, start, len);
170 }
171 flashprog_wp_ranges_release(list);
172
173 return 0;
174}
175
176static int wp_apply(struct flashprog_flashctx *const flash,
177 const bool enable_wp, const bool disable_wp,
178 const bool set_wp_range, const size_t wp_start,
179 const size_t wp_len)
180{
181 struct flashprog_wp_cfg *cfg;
182 enum flashprog_wp_result ret;
183
184 ret = flashprog_wp_cfg_new(&cfg);
185 if (ret == FLASHPROG_WP_OK)
186 ret = flashprog_wp_read_cfg(cfg, flash);
187
188 if (ret != FLASHPROG_WP_OK) {
189 fprintf(stderr, "Failed to get WP status: %s\n", get_wp_error_str(ret));
190 flashprog_wp_cfg_release(cfg);
191 return 1;
192 }
193
194 /* Store current WP mode for printing help text if changing the cfg fails. */
195 const enum flashprog_wp_mode old_mode = flashprog_wp_get_mode(cfg);
196
197 if (set_wp_range)
198 flashprog_wp_set_range(cfg, wp_start, wp_len);
199
200 if (disable_wp)
201 flashprog_wp_set_mode(cfg, FLASHPROG_WP_MODE_DISABLED);
202
203 if (enable_wp)
204 flashprog_wp_set_mode(cfg, FLASHPROG_WP_MODE_HARDWARE);
205
206 ret = flashprog_wp_write_cfg(flash, cfg);
207
208 flashprog_wp_cfg_release(cfg);
209
210 if (ret != FLASHPROG_WP_OK) {
211 fprintf(stderr, "Failed to apply new WP settings: %s\n", get_wp_error_str(ret));
212
213 if (ret != FLASHPROG_WP_ERR_VERIFY_FAILED)
214 return 1;
215
216 /* Warn user if active WP is likely to have caused failure */
217 switch (old_mode) {
218 case FLASHPROG_WP_MODE_HARDWARE:
219 fprintf(stderr, "Note: hardware status register protection is enabled. "
220 "The chip's WP# pin must be set to an inactive voltage "
221 "level to be able to change the WP settings.\n");
222 break;
223 case FLASHPROG_WP_MODE_POWER_CYCLE:
224 fprintf(stderr, "Note: power-cycle status register protection is enabled. "
225 "A power-off, power-on cycle is usually required to change "
226 "the chip's WP settings.\n");
227 break;
228 case FLASHPROG_WP_MODE_PERMANENT:
229 fprintf(stderr, "Note: permanent status register protection is enabled. "
230 "The chip's WP settings cannot be modified.\n");
231 break;
232 default:
233 break;
234 }
235 return 1;
236 }
237
238 if (disable_wp)
239 printf("Disabled hardware protection\n");
240
241 if (enable_wp)
242 printf("Enabled hardware protection\n");
243
244 if (set_wp_range)
245 print_wp_range("Configured protection range: ", flash, wp_start, wp_len);
246
247 return 0;
248}
249
250int flashprog_wp_main(int argc, char *argv[])
251{
252 static const char optstring[] = "+p:c:Vo:hl:";
253 static const struct option long_options[] = {
254 {"programmer", 1, NULL, 'p'},
255 {"chip", 1, NULL, 'c'},
256 {"verbose", 0, NULL, 'V'},
257 {"output", 1, NULL, 'o'},
258 {"help", 0, NULL, 'h'},
259 {"layout", 1, NULL, 'l'},
260 {"ifd", 0, NULL, OPTION_IFD},
261 {"fmap", 0, NULL, OPTION_FMAP},
262 {"fmap-file", 1, NULL, OPTION_FMAP_FILE},
263 {"temporary", 0, NULL, OPTION_CONFIG_VOLATILE},
264 {"status", 0, NULL, OPTION_WP_STATUS},
265 {"list", 0, NULL, OPTION_WP_LIST},
266 {"range", 0, NULL, OPTION_WP_SET_RANGE},
267 {"region", 0, NULL, OPTION_WP_SET_REGION},
268 {"enable", 0, NULL, OPTION_WP_ENABLE},
269 {"disable", 0, NULL, OPTION_WP_DISABLE},
270 {NULL, 0, NULL, 0},
271 };
272 static const struct opt_command cmd_options[] = {
273 {"status", OPTION_WP_STATUS},
274 {"list", OPTION_WP_LIST},
275 {"range", OPTION_WP_SET_RANGE},
276 {"region", OPTION_WP_SET_REGION},
277 {"enable", OPTION_WP_ENABLE},
278 {"disable", OPTION_WP_DISABLE},
279 {NULL, 0},
280 };
281
282 unsigned int ops = 0;
283 int ret = 1, opt;
284 struct log_args log_args = { FLASHPROG_MSG_INFO, FLASHPROG_MSG_DEBUG2, NULL };
285 struct flash_args flash_args = { 0 };
286 struct layout_args layout_args = { 0 };
287 bool volat1le = false;
288 bool enable_wp = false, disable_wp = false, print_wp_status = false;
289 bool set_wp_range = false, set_wp_region = false, print_wp_ranges = false;
290 size_t wp_start = 0, wp_len = 0;
291 char *wp_region = NULL;
292
293 if (cli_init()) /* TODO: Can be moved below argument parsing once usage() uses `stderr` directly. */
294 goto free_ret;
295
296 if (argc < 2)
297 usage(argv[0], NULL);
298
299 while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1 ||
300 (opt = getopt_command(argc, argv, cmd_options)) != -1) {
301 switch (opt) {
302 case 'V':
303 case 'o':
304 ret = cli_parse_log_args(&log_args, opt, optarg);
305 if (ret == 1)
306 usage(argv[0], NULL);
307 else if (ret)
308 goto free_ret;
309 break;
310 case 'p':
311 case 'c':
312 ret = cli_parse_flash_args(&flash_args, opt, optarg);
313 if (ret == 1)
314 usage(argv[0], NULL);
315 else if (ret)
316 goto free_ret;
317 break;
318 case OPTION_LAYOUT:
319 case OPTION_IFD:
320 case OPTION_FMAP:
321 case OPTION_FMAP_FILE:
322 ret = cli_parse_layout_args(&layout_args, opt, optarg);
323 if (ret == 1)
324 usage(argv[0], NULL);
325 else if (ret)
326 goto free_ret;
327 break;
328 case OPTION_CONFIG_VOLATILE:
329 volat1le = true;
330 break;
331 case OPTION_WP_STATUS:
332 print_wp_status = true;
333 ++ops;
334 break;
335 case OPTION_WP_LIST:
336 print_wp_ranges = true;
337 ++ops;
338 break;
339 case OPTION_WP_SET_RANGE:
340 set_wp_range = true;
341 ++ops;
342 break;
343 case OPTION_WP_SET_REGION:
344 set_wp_region = true;
345 ++ops;
346 break;
347 case OPTION_WP_ENABLE:
348 enable_wp = true;
349 ++ops;
350 break;
351 case OPTION_WP_DISABLE:
352 disable_wp = true;
353 ++ops;
354 break;
355 case '?':
356 case 'h':
357 usage(argv[0], NULL);
358 break;
359 }
360 }
361
362 if (!ops) {
363 print_wp_status = true;
364 ++ops;
365 }
366 if (ops > 1)
367 usage(argv[0], "Only one operation may be specified.");
368
369 if (!enable_wp && !disable_wp && !set_wp_range && !set_wp_region && volat1le)
370 usage(argv[0], "`--temporary' may only be specified for write operations.");
371
372 ret = 1;
373 if (set_wp_range) {
374 if (optind != argc - 1)
375 usage(argv[0], "`range' requires exactly one argument.");
376 if (parse_wp_range(&wp_start, &wp_len, argv[optind++]) < 0)
377 usage(argv[0], "Incorrect wp-range arguments provided.");
378 } else if (set_wp_region) {
379 if (optind != argc - 1)
380 usage(argv[0], "`region' requires exactly one argument.");
381 wp_region = strdup(argv[optind++]);
382 if (!wp_region) {
383 fprintf(stderr, "Out of memory!\n");
384 goto free_ret;
385 }
386 } else if (optind < argc) {
387 usage(argv[0], "Extra parameter found.");
388 }
389
390 if (!flash_args.prog_name)
391 usage(argv[0], "No programmer specified.");
392
393 struct flashprog_programmer *prog;
394 struct flashprog_flashctx *flash;
395 struct flashprog_layout *layout = NULL;
396
397 if (log_args.logfile && open_logfile(log_args.logfile))
398 goto free_ret;
399 verbose_screen = log_args.screen_level;
400 verbose_logfile = log_args.logfile_level;
401 start_logging();
402
403 if (flashprog_programmer_init(&prog, flash_args.prog_name, flash_args.prog_args))
404 goto free_ret;
405 ret = flashprog_flash_probe(&flash, prog, flash_args.chip);
406 if (ret == 3) {
407 fprintf(stderr, "Multiple flash chip definitions match the detected chip.\n"
408 "Please specify which chip definition to use with the -c <chipname> option.\n");
409 } else if (ret) {
410 fprintf(stderr, "No EEPROM/flash device found.\n");
411 goto shutdown_ret;
412 }
413
414 flashprog_flag_set(flash, FLASHPROG_FLAG_NON_VOLATILE_WRSR, !volat1le);
415
416 if (print_wp_status)
417 ret = wp_print_status(flash);
418
419 if (print_wp_ranges)
420 ret = wp_print_ranges(flash);
421
422 if (set_wp_region) {
423 ret = 1;
424 if (cli_process_layout_args(&layout, flash, &layout_args)) {
425 fprintf(stderr, "Failed to read layout.\n");
426 goto release_ret;
427 }
428 if (!layout) {
429 fprintf(stderr, "Error: `--region' operation requires a layout.\n");
430 goto release_ret;
431 }
432
433 if (flashprog_layout_get_region_range(layout, wp_region, &wp_start, &wp_len)) {
434 fprintf(stderr, "Cannot find region '%s'.\n", wp_region);
435 goto release_ret;
436 }
437 set_wp_range = true;
438 }
439
440 if (set_wp_range || enable_wp || disable_wp)
441 ret = wp_apply(flash, enable_wp, disable_wp, set_wp_range, wp_start, wp_len);
442
443release_ret:
444 flashprog_layout_release(layout);
445 flashprog_flash_release(flash);
446shutdown_ret:
447 flashprog_programmer_shutdown(prog);
448free_ret:
449 free(wp_region);
450 free(layout_args.fmapfile);
451 free(layout_args.layoutfile);
452 free(flash_args.chip);
453 free(flash_args.prog_args);
454 free(flash_args.prog_name);
455 free(log_args.logfile);
456 close_logfile();
457 return ret;
458}