1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2018 iceman
4 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
5 // at your option, any later version. See the LICENSE.txt file for the text of
7 //-----------------------------------------------------------------------------
8 // Proxmark3 RDV40 Flash memory commands
9 //-----------------------------------------------------------------------------
10 #include "cmdflashmem.h"
12 #include "cmdparser.h" // command_t
13 #include "cliparser.h"
14 #include "pmflash.h" // rdv40validation_t
15 #include "fileutils.h" // saveFile
16 #include "comms.h" // getfromdevice
17 #include "cmdflashmemspiffs.h" // spiffs commands
20 #include "pk.h" // PEM key load functions
23 #define FLASH_MINFAST 24000000 //33000000
24 #define FLASH_BAUD MCK/2
25 #define FLASH_FASTBAUD MCK
26 #define FLASH_MINBAUD FLASH_FASTBAUD
28 static int CmdHelp(const char *Cmd
);
30 //-------------------------------------------------------------------------------------
32 #define RRG_RSA_KEY_LEN 128
34 // public key Exponent E
35 #define RRG_RSA_E "010001"
37 // public key modulus N
38 #define RRG_RSA_N "E28D809BF323171D11D1ACA4C32A5B7E0A8974FD171E75AD120D60E9B76968FF" \
39 "4B0A6364AE50583F9555B8EE1A725F279E949246DF0EFCE4C02B9F3ACDCC623F" \
40 "9337F21C0C066FFB703D8BFCB5067F309E056772096642C2B1A8F50305D5EC33" \
41 "DB7FB5A3C8AC42EB635AE3C148C910750ABAA280CE82DC2F180F49F30A1393B5"
43 //-------------------------------------------------------------------------------------
45 int rdv4_get_signature(rdv40_validation_t
*out
) {
51 SendCommandNG(CMD_FLASHMEM_INFO
, NULL
, 0);
52 PacketResponseNG resp
;
53 if (WaitForResponseTimeout(CMD_ACK
, &resp
, 2500) == false) {
54 PrintAndLogEx(WARNING
, "timeout while waiting for reply");
58 uint8_t isok
= resp
.oldarg
[0] & 0xFF;
60 PrintAndLogEx(FAILED
, "fail reading from flashmemory");
64 //rdv40_validation_t mem;
65 memcpy(out
, (rdv40_validation_t
*)resp
.data
.asBytes
, sizeof(rdv40_validation_t
));
70 int rdv4_validate(rdv40_validation_t
*mem
) {
71 // Flash ID hash (sha1)
72 uint8_t sha_hash
[20] = {0};
73 mbedtls_sha1(mem
->flashid
, sizeof(mem
->flashid
), sha_hash
);
76 mbedtls_rsa_context rsa
;
77 mbedtls_rsa_init(&rsa
, MBEDTLS_RSA_PKCS_V15
, 0);
78 rsa
.len
= RRG_RSA_KEY_LEN
;
79 mbedtls_mpi_read_string(&rsa
.N
, 16, RRG_RSA_N
);
80 mbedtls_mpi_read_string(&rsa
.E
, 16, RRG_RSA_E
);
82 // Verify (public key)
83 int is_verified
= mbedtls_rsa_pkcs1_verify(&rsa
, NULL
, NULL
, MBEDTLS_RSA_PUBLIC
, MBEDTLS_MD_SHA1
, 20, sha_hash
, mem
->signature
);
84 mbedtls_rsa_free(&rsa
);
86 if (is_verified
== 0) {
92 static int rdv4_sign_write(uint8_t *signature
, uint8_t slen
) {
93 flashmem_old_write_t payload
= {
94 .startidx
= FLASH_MEM_SIGNATURE_OFFSET
,
95 .len
= FLASH_MEM_SIGNATURE_LEN
,
97 memcpy(payload
.data
, signature
, slen
);
100 PacketResponseNG resp
;
101 SendCommandNG(CMD_FLASHMEM_WRITE
, (uint8_t *)&payload
, sizeof(payload
));
103 if (WaitForResponseTimeout(CMD_FLASHMEM_WRITE
, &resp
, 2000) == false) {
104 PrintAndLogEx(WARNING
, "timeout while waiting for reply");
107 if (resp
.status
!= PM3_SUCCESS
) {
108 PrintAndLogEx(FAILED
, "Writing signature ( "_RED_("fail") ")");
112 PrintAndLogEx(SUCCESS
, "Writing signature at offset %u ( "_GREEN_("ok") " )", FLASH_MEM_SIGNATURE_OFFSET
);
116 static int CmdFlashmemSpiBaud(const char *Cmd
) {
118 CLIParserContext
*ctx
;
119 CLIParserInit(&ctx
, "mem baudrate",
120 "Set the baudrate for the SPI flash memory communications.\n"
121 "Reading Flash ID will virtually always fail under 48MHz setting.\n"
122 "Unless you know what you are doing, please stay at 24MHz.\n"
123 "If >= 24MHz, FASTREADS instead of READS instruction will be used.",
124 "mem baudrate --mhz 48"
129 arg_int1(NULL
, "mhz", "<24|48>", "SPI baudrate in MHz"),
132 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
133 int br
= arg_get_int_def(ctx
, 1, -1);
137 PrintAndLogEx(ERR
, "failed to get baudrate");
141 uint32_t baudrate
= br
* 1000000;
142 if (baudrate
!= FLASH_BAUD
&& baudrate
!= FLASH_MINBAUD
) {
143 PrintAndLogEx(ERR
, "wrong baudrate. Only 24 or 48 is allowed");
146 SendCommandNG(CMD_FLASHMEM_SET_SPIBAUDRATE
, (uint8_t *)&baudrate
, sizeof(uint32_t));
150 static int CmdFlashMemLoad(const char *Cmd
) {
152 CLIParserContext
*ctx
;
153 CLIParserInit(&ctx
, "mem load",
154 "Loads binary file into flash memory on device\n"
155 "Warning: mem area to be written must have been wiped first\n"
156 "( this is already taken care when loading dictionaries )",
157 "mem load -f myfile -> upload file myfile values at default offset 0\n"
158 "mem load -f myfile -o 1024 -> upload file myfile values at offset 1024\n"
159 "mem load -f mfc_default_keys -m -> upload MFC keys\n"
160 "mem load -f t55xx_default_pwds -t -> upload T55XX passwords\n"
161 "mem load -f iclass_default_keys -i -> upload iCLASS keys\n"
166 arg_int0("o", "offset", "<dec>", "offset in memory"),
167 arg_lit0("m", "mifare,mfc", "upload 6 bytes keys (mifare key dictionary)"),
168 arg_lit0("i", "iclass", "upload 8 bytes keys (iClass key dictionary)"),
169 arg_lit0("t", "t55xx", "upload 4 bytes keys (password dictionary)"),
170 arg_strx0("f", "file", "<filename>", "file name"),
173 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
175 int offset
= arg_get_int_def(ctx
, 1, 0);
176 bool is_mfc
= arg_get_lit(ctx
, 2);
177 bool is_iclass
= arg_get_lit(ctx
, 3);
178 bool is_t55xx
= arg_get_lit(ctx
, 4);
180 char filename
[FILE_PATH_SIZE
] = {0};
181 CLIParamStrToBuf(arg_get_str(ctx
, 5), (uint8_t *)filename
, FILE_PATH_SIZE
, &fnlen
);
184 Dictionary_t d
= DICTIONARY_NONE
;
186 d
= DICTIONARY_MIFARE
;
187 PrintAndLogEx(INFO
, "treating file as MIFARE Classic keys");
188 } else if (is_iclass
) {
189 d
= DICTIONARY_ICLASS
;
190 PrintAndLogEx(INFO
, "treating file as iCLASS keys");
191 } else if (is_t55xx
) {
192 d
= DICTIONARY_T55XX
;
193 PrintAndLogEx(INFO
, "treating file as T55xx passwords");
197 uint32_t keycount
= 0;
199 uint8_t *data
= calloc(FLASH_MEM_MAX_SIZE
, sizeof(uint8_t));
202 case DICTIONARY_MIFARE
:
203 offset
= DEFAULT_MF_KEYS_OFFSET
;
204 res
= loadFileDICTIONARY(filename
, data
+ 2, &datalen
, 6, &keycount
);
205 if (res
|| !keycount
) {
209 // limited space on flash mem
210 if (keycount
> 0xFFFF)
213 data
[0] = (keycount
>> 0) & 0xFF;
214 data
[1] = (keycount
>> 8) & 0xFF;
217 case DICTIONARY_T55XX
:
218 offset
= DEFAULT_T55XX_KEYS_OFFSET
;
219 res
= loadFileDICTIONARY(filename
, data
+ 2, &datalen
, 4, &keycount
);
220 if (res
|| !keycount
) {
224 // limited space on flash mem
225 if (keycount
> 0xFFFF)
228 data
[0] = (keycount
>> 0) & 0xFF;
229 data
[1] = (keycount
>> 8) & 0xFF;
232 case DICTIONARY_ICLASS
:
233 offset
= DEFAULT_ICLASS_KEYS_OFFSET
;
234 res
= loadFileDICTIONARY(filename
, data
+ 2, &datalen
, 8, &keycount
);
235 if (res
|| !keycount
) {
239 // limited space on flash mem
240 if (keycount
> 0xFFFF)
243 data
[0] = (keycount
>> 0) & 0xFF;
244 data
[1] = (keycount
>> 8) & 0xFF;
247 case DICTIONARY_NONE
:
248 res
= loadFile_safe(filename
, ".bin", (void **)&data
, &datalen
);
249 if (res
!= PM3_SUCCESS
) {
254 if (datalen
> FLASH_MEM_MAX_SIZE
) {
255 PrintAndLogEx(ERR
, "error, filesize is larger than available memory");
261 // not needed when we transite to loadxxxx_safe methods.(iceman)
262 uint8_t *newdata
= realloc(data
, datalen
);
263 if (newdata
== NULL
) {
271 uint32_t bytes_sent
= 0;
272 uint32_t bytes_remaining
= datalen
;
276 conn
.block_after_ACK
= true;
278 while (bytes_remaining
> 0) {
279 uint32_t bytes_in_packet
= MIN(FLASH_MEM_BLOCK_SIZE
, bytes_remaining
);
281 clearCommandBuffer();
283 flashmem_old_write_t payload
= {
284 .startidx
= offset
+ bytes_sent
,
285 .len
= bytes_in_packet
,
287 memcpy(payload
.data
, data
+ bytes_sent
, bytes_in_packet
);
288 SendCommandNG(CMD_FLASHMEM_WRITE
, (uint8_t *)&payload
, sizeof(payload
));
290 bytes_remaining
-= bytes_in_packet
;
291 bytes_sent
+= bytes_in_packet
;
293 PacketResponseNG resp
;
294 if (WaitForResponseTimeout(CMD_FLASHMEM_WRITE
, &resp
, 2000) == false) {
295 PrintAndLogEx(WARNING
, "timeout while waiting for reply.");
296 conn
.block_after_ACK
= false;
301 if (resp
.status
!= PM3_SUCCESS
) {
302 conn
.block_after_ACK
= false;
303 PrintAndLogEx(FAILED
, "Flash write fail [offset %u]", bytes_sent
);
309 conn
.block_after_ACK
= false;
311 PrintAndLogEx(SUCCESS
, "Wrote "_GREEN_("%zu")" bytes to offset "_GREEN_("%u"), datalen
, offset
);
315 static int CmdFlashMemDump(const char *Cmd
) {
317 CLIParserContext
*ctx
;
318 CLIParserInit(&ctx
, "mem dump",
319 "Dumps flash memory on device into a file or view in console",
320 "mem dump -f myfile -> download all flashmem to file\n"
321 "mem dump --view -o 262015 --len 128 -> display 128 bytes from offset 262015 (RSA sig)\n"
322 "mem dump --view -f myfile -o 241664 --len 58 -> display 58 bytes from offset 241664 and save to file"
327 arg_int0("o", "offset", "<dec>", "offset in memory"),
328 arg_int0("l", "len", "<dec>", "length"),
329 arg_lit0("v", "view", "view dump"),
330 arg_strx0("f", "file", "<filename>", "file name"),
331 arg_int0("c", "cols", "<dec>", "column breaks (def 32)"),
334 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
336 int offset
= arg_get_int_def(ctx
, 1, 0);
337 int len
= arg_get_int_def(ctx
, 2, FLASH_MEM_MAX_SIZE
);
338 bool view
= arg_get_lit(ctx
, 3);
340 char filename
[FILE_PATH_SIZE
] = {0};
341 CLIParamStrToBuf(arg_get_str(ctx
, 4), (uint8_t *)filename
, FILE_PATH_SIZE
, &fnlen
);
342 int breaks
= arg_get_int_def(ctx
, 5, 32);
345 uint8_t *dump
= calloc(len
, sizeof(uint8_t));
347 PrintAndLogEx(ERR
, "error, cannot allocate memory ");
351 PrintAndLogEx(INFO
, "downloading "_YELLOW_("%u")" bytes from flash memory", len
);
352 if (!GetFromDevice(FLASH_MEM
, dump
, len
, offset
, NULL
, 0, NULL
, -1, true)) {
353 PrintAndLogEx(FAILED
, "ERROR; downloading from flash memory");
359 PrintAndLogEx(INFO
, "---- " _CYAN_("data") " ---------------");
360 print_hex_break(dump
, len
, breaks
);
363 if (filename
[0] != '\0') {
364 saveFile(filename
, ".bin", dump
, len
);
365 saveFileEML(filename
, dump
, len
, 16);
372 static int CmdFlashMemWipe(const char *Cmd
) {
374 CLIParserContext
*ctx
;
375 CLIParserInit(&ctx
, "mem wipe",
376 "Wipe flash memory on device, which fills it with 0xFF\n"
377 _WHITE_("[ ") _RED_("!!! OBS") _WHITE_(" ] use with caution"),
378 "mem wipe -p 0 -> wipes first page"
379 // "mem wipe -i -> inital total wipe"
384 arg_int0("p", NULL
, "<dec>", "0,1,2 page memory"),
385 // arg_lit0("i", NULL, "inital total wipe"),
388 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
390 bool initalwipe
= false;
391 int page
= arg_get_int_def(ctx
, 1, -1);
392 // initalwipe = arg_get_lit(ctx, 2);
395 if (page
< 0 || page
> 2) {
396 PrintAndLogEx(WARNING
, "page must be 0, 1 or 2");
400 clearCommandBuffer();
401 SendCommandMIX(CMD_FLASHMEM_WIPE
, page
, initalwipe
, 0, NULL
, 0);
402 PacketResponseNG resp
;
403 if (!WaitForResponseTimeout(CMD_ACK
, &resp
, 8000)) {
404 PrintAndLogEx(WARNING
, "timeout while waiting for reply.");
408 const char *msg
= "Flash WIPE ";
409 uint8_t isok
= resp
.oldarg
[0] & 0xFF;
411 PrintAndLogEx(SUCCESS
, "%s ( " _GREEN_("ok")" )", msg
);
413 PrintAndLogEx(FAILED
, "%s ( " _RED_("failed") " )", msg
);
420 static int CmdFlashMemInfo(const char *Cmd
) {
422 CLIParserContext
*ctx
;
423 CLIParserInit(&ctx
, "mem info",
424 "Collect signature and verify it from flash memory",
430 arg_lit0("s", "sign", "create a signature"),
431 arg_str0("d", NULL
, "<hex>", "flash memory id, 8 hex bytes"),
432 arg_str0("p", "pem", "<fn>", "key in PEM format"),
433 arg_lit0("v", "verbose", "verbose output"),
434 // arg_lit0("w", "write", "write signature to flash memory"),
437 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
439 bool shall_sign
= arg_get_lit(ctx
, 1);
443 int res
= CLIParamHexToBuf(arg_get_str(ctx
, 2), id
, sizeof(id
), &dlen
);
446 char pem_fn
[FILE_PATH_SIZE
] = {0};
447 CLIParamStrToBuf(arg_get_str(ctx
, 3), (uint8_t *)pem_fn
, FILE_PATH_SIZE
, &pemlen
);
449 bool verbose
= arg_get_lit(ctx
, 4);
450 bool shall_write
= false;
451 // shall_write = arg_get_lit(ctx, 5);
454 if (res
|| (dlen
> 0 && dlen
< sizeof(id
))) {
455 PrintAndLogEx(FAILED
, "Error parsing flash memory id, expect 8, got %d", dlen
);
459 // set up PK key context now.
460 mbedtls_pk_context pkctx
;
461 mbedtls_pk_init(&pkctx
);
462 bool got_private
= false;
467 if (searchFile(&path
, RESOURCES_SUBDIR
, pem_fn
, ".pem", true) != PM3_SUCCESS
) {
468 if (searchFile(&path
, RESOURCES_SUBDIR
, pem_fn
, "", false) != PM3_SUCCESS
) {
473 PrintAndLogEx(INFO
, "loading file `" _YELLOW_("%s") "`" NOLF
, path
);
476 res
= mbedtls_pk_parse_keyfile(&pkctx
, path
, NULL
);
478 //res = mbedtls_pk_parse_public_keyfile(&pkctx, path);
480 PrintAndLogEx(NORMAL
, " ( " _GREEN_("ok") " )");
482 PrintAndLogEx(NORMAL
, " ( " _RED_("fail") " )");
483 mbedtls_pk_free(&pkctx
);
487 mbedtls_rsa_context
*rsa
= (mbedtls_rsa_context
*)pkctx
.pk_ctx
;
489 PrintAndLogEx(FAILED
, "failed to allocate rsa context memory");
495 // it not loaded, we need to setup the context manually
496 if (mbedtls_pk_setup(&pkctx
, mbedtls_pk_info_from_type((mbedtls_pk_type_t
) MBEDTLS_PK_RSA
)) != 0) {
497 PrintAndLogEx(FAILED
, "failed, mbedtls_pk_setup returned ");
502 // validate devicesignature data
503 rdv40_validation_t mem
;
504 res
= rdv4_get_signature(&mem
);
505 if (res
!= PM3_SUCCESS
) {
509 res
= rdv4_validate(&mem
);
511 // Flash ID hash (sha1)
512 uint8_t sha_hash
[20] = {0};
513 mbedtls_sha1(mem
.flashid
, sizeof(mem
.flashid
), sha_hash
);
516 PrintAndLogEx(NORMAL
, "");
517 PrintAndLogEx(INFO
, "--- " _CYAN_("Flash memory Information") " ---------");
518 PrintAndLogEx(INFO
, "ID................... %s", sprint_hex_inrow(mem
.flashid
, sizeof(mem
.flashid
)));
519 PrintAndLogEx(INFO
, "SHA1................. %s", sprint_hex_inrow(sha_hash
, sizeof(sha_hash
)));
520 PrintAndLogEx(NORMAL
, "");
521 PrintAndLogEx(INFO
, "--- " _CYAN_("RDV4 RSA signature") " ---------------");
522 for (int i
= 0; i
< (sizeof(mem
.signature
) / 32); i
++) {
523 PrintAndLogEx(INFO
, " %s", sprint_hex_inrow(mem
.signature
+ (i
* 32), 32));
526 (res
== PM3_SUCCESS
) ? SUCCESS
: FAILED
,
527 "Signature............ ( %s )",
528 (res
== PM3_SUCCESS
) ? _GREEN_("ok") : _RED_("fail")
530 PrintAndLogEx(NORMAL
, "");
532 mbedtls_rsa_context
*rsa
= NULL
;
535 rsa
= mbedtls_pk_rsa(pkctx
);
536 rsa
->padding
= MBEDTLS_RSA_PKCS_V15
;
538 rsa
->len
= RRG_RSA_KEY_LEN
;
541 rsa
= (mbedtls_rsa_context
*)calloc(1, sizeof(mbedtls_rsa_context
));
542 mbedtls_rsa_init(rsa
, MBEDTLS_RSA_PKCS_V15
, 0);
543 rsa
->len
= RRG_RSA_KEY_LEN
;
546 mbedtls_mpi_read_string(&rsa
->N
, 16, RRG_RSA_N
);
547 mbedtls_mpi_read_string(&rsa
->E
, 16, RRG_RSA_E
);
550 PrintAndLogEx(INFO
, "--- " _CYAN_("RDV4 RSA Public key") " --------------");
554 size_t exlen
= 0, pklen
= 0;
555 mbedtls_mpi_write_string(&rsa
->E
, 16, str_exp
, sizeof(str_exp
), &exlen
);
556 mbedtls_mpi_write_string(&rsa
->N
, 16, str_pk
, sizeof(str_pk
), &pklen
);
558 PrintAndLogEx(INFO
, "Len.................. %"PRIu64
, rsa
->len
);
559 PrintAndLogEx(INFO
, "Exponent............. %s", str_exp
);
560 PrintAndLogEx(INFO
, "Public key modulus N");
561 PrintAndLogEx(INFO
, " %.64s", str_pk
);
562 PrintAndLogEx(INFO
, " %.64s", str_pk
+ 64);
563 PrintAndLogEx(INFO
, " %.64s", str_pk
+ 128);
564 PrintAndLogEx(INFO
, " %.64s", str_pk
+ 192);
565 PrintAndLogEx(NORMAL
, "");
568 bool is_keyok
= (mbedtls_rsa_check_pubkey(rsa
) == 0);
570 (is_keyok
) ? SUCCESS
: FAILED
,
571 "RRG/Iceman RSA public key check.... ( %s )",
572 (is_keyok
) ? _GREEN_("ok") : _RED_("fail")
575 is_keyok
= (mbedtls_rsa_check_privkey(rsa
) == 0);
578 (is_keyok
) ? SUCCESS
: FAILED
,
579 "RRG/Iceman RSA private key check... ( %s )",
580 (is_keyok
) ? _GREEN_("ok") : _YELLOW_("N/A")
585 uint8_t from_device
[RRG_RSA_KEY_LEN
];
586 memcpy(from_device
, mem
.signature
, RRG_RSA_KEY_LEN
);
589 uint8_t sign
[RRG_RSA_KEY_LEN
];
590 memset(sign
, 0, RRG_RSA_KEY_LEN
);
592 // Signing (private key)
597 PrintAndLogEx(NORMAL
, "");
598 PrintAndLogEx(INFO
, "--- " _CYAN_("Enter signing") " --------------------");
601 mbedtls_sha1(id
, sizeof(id
), sha_hash
);
603 PrintAndLogEx(INFO
, "Signing....... %s", sprint_hex_inrow(sha_hash
, sizeof(sha_hash
)));
605 int is_signed
= mbedtls_rsa_pkcs1_sign(rsa
, NULL
, NULL
, MBEDTLS_RSA_PRIVATE
, MBEDTLS_MD_SHA1
, 20, sha_hash
, sign
);
607 (is_signed
== 0) ? SUCCESS
: FAILED
,
608 "RSA signing... ( %s )",
609 (is_signed
== 0) ? _GREEN_("ok") : _RED_("fail")
613 rdv4_sign_write(sign
, RRG_RSA_KEY_LEN
);
615 PrintAndLogEx(INFO
, "New signature");
616 for (int i
= 0; i
< (sizeof(sign
) / 32); i
++) {
617 PrintAndLogEx(INFO
, " %s", sprint_hex_inrow(sign
+ (i
* 32), 32));
620 PrintAndLogEx(FAILED
, "no private key available to sign");
624 // Verify (public key)
625 bool is_verified
= (mbedtls_rsa_pkcs1_verify(rsa
, NULL
, NULL
, MBEDTLS_RSA_PUBLIC
, MBEDTLS_MD_SHA1
, 20, sha_hash
, from_device
) == 0);
627 mbedtls_pk_free(&pkctx
);
629 PrintAndLogEx(NORMAL
, "");
631 (is_verified
) ? SUCCESS
: FAILED
,
632 "Genuine Proxmark3 RDV4 signature detected... %s",
633 (is_verified
) ? ":heavy_check_mark:" : ":x:"
635 PrintAndLogEx(NORMAL
, "");
639 static command_t CommandTable
[] = {
640 {"spiffs", CmdFlashMemSpiFFS
, IfPm3Flash
, "{ SPI File system }"},
641 {"help", CmdHelp
, AlwaysAvailable
, "This help"},
642 {"baudrate", CmdFlashmemSpiBaud
, IfPm3Flash
, "Set Flash memory Spi baudrate"},
643 {"dump", CmdFlashMemDump
, IfPm3Flash
, "Dump data from flash memory"},
644 {"info", CmdFlashMemInfo
, IfPm3Flash
, "Flash memory information"},
645 {"load", CmdFlashMemLoad
, IfPm3Flash
, "Load data to flash memory"},
646 {"wipe", CmdFlashMemWipe
, IfPm3Flash
, "Wipe data from flash memory"},
647 {NULL
, NULL
, NULL
, NULL
}
650 static int CmdHelp(const char *Cmd
) {
651 (void)Cmd
; // Cmd is not used so far
652 CmdsHelp(CommandTable
);
656 int CmdFlashMem(const char *Cmd
) {
657 clearCommandBuffer();
658 return CmdsParse(CommandTable
, Cmd
);