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