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