blob: 04ffeab00494d357094e6020824f2d915ff38991 [file] [log] [blame]
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -05001/*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public
4 * License v2 as published by the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 * General Public License for more details.
10 *
11 * You should have received a copy of the GNU General Public
12 * License along with this program; if not, write to the
13 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
14 * Boston, MA 021110-1307, USA.
Avi Shchislowskidc7ab962016-03-08 14:22:41 -050015 *
16 * Modified to add field firmware update support,
17 * those modifications are Copyright (c) 2016 SanDisk Corp.
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050018 */
19
Gwendal Grignou771984c2014-07-01 12:46:18 -070020#include <errno.h>
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050021#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/ioctl.h>
Gwendal Grignou771984c2014-07-01 12:46:18 -070025#include <sys/param.h>
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050026#include <sys/types.h>
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050027#include <sys/stat.h>
28#include <unistd.h>
29#include <fcntl.h>
Roman Peniaev023cc7c2014-08-12 23:25:45 +090030#include <errno.h>
31#include <stdint.h>
32#include <assert.h>
Uwe Kleine-König8f68bc32017-12-21 13:57:13 +010033#include <linux/fs.h> /* for BLKGETSIZE */
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050034
35#include "mmc.h"
36#include "mmc_cmds.h"
Gwendal Grignou0da2c512015-01-08 15:36:03 -080037#include "ffu.h"
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050038
Nick Sanders9d57aa72014-03-05 21:38:54 -080039#define EXT_CSD_SIZE 512
Gwendal Grignou0da2c512015-01-08 15:36:03 -080040#define FFU_DATA_SIZE 512
Nick Sanders9d57aa72014-03-05 21:38:54 -080041#define CID_SIZE 16
42
Julius Wernerbcc3e2e2016-04-21 16:53:02 -070043/* Sending several commands too close together seems to cause timeouts. */
44#define INTER_COMMAND_GAP_US (50 * 1000)
45
Roman Peniaev023cc7c2014-08-12 23:25:45 +090046#include "3rdparty/hmac_sha/hmac_sha2.h"
Nick Sanders9d57aa72014-03-05 21:38:54 -080047
Uwe Kleine-Königf16e4662017-12-21 11:35:08 +010048#ifndef offsetof
49#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
50#endif
51
Al Cooper1b7f5d72016-06-07 16:35:46 -040052#define WP_BLKS_PER_QUERY 32
53
54#define USER_WP_PERM_PSWD_DIS 0x80
55#define USER_WP_CD_PERM_WP_DIS 0x40
56#define USER_WP_US_PERM_WP_DIS 0x10
57#define USER_WP_US_PWR_WP_DIS 0x08
58#define USER_WP_US_PERM_WP_EN 0x04
59#define USER_WP_US_PWR_WP_EN 0x01
60#define USER_WP_CLEAR (USER_WP_US_PERM_WP_DIS | USER_WP_US_PWR_WP_DIS \
61 | USER_WP_US_PERM_WP_EN | USER_WP_US_PWR_WP_EN)
62
63#define WPTYPE_NONE 0
64#define WPTYPE_TEMP 1
65#define WPTYPE_PWRON 2
66#define WPTYPE_PERM 3
67
68
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050069int read_extcsd(int fd, __u8 *ext_csd)
70{
71 int ret = 0;
72 struct mmc_ioc_cmd idata;
73 memset(&idata, 0, sizeof(idata));
Nick Sanders9d57aa72014-03-05 21:38:54 -080074 memset(ext_csd, 0, sizeof(__u8) * EXT_CSD_SIZE);
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050075 idata.write_flag = 0;
76 idata.opcode = MMC_SEND_EXT_CSD;
77 idata.arg = 0;
78 idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
Nick Sanders9d57aa72014-03-05 21:38:54 -080079 idata.blksz = EXT_CSD_SIZE;
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050080 idata.blocks = 1;
81 mmc_ioc_cmd_set_data(idata, ext_csd);
82
83 ret = ioctl(fd, MMC_IOC_CMD, &idata);
84 if (ret)
Nick Sanders9d57aa72014-03-05 21:38:54 -080085 perror("ioctl SEND_EXT_CSD");
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -050086
87 return ret;
88}
89
90int write_extcsd_value(int fd, __u8 index, __u8 value)
91{
92 int ret = 0;
93 struct mmc_ioc_cmd idata;
94
95 memset(&idata, 0, sizeof(idata));
96 idata.write_flag = 1;
97 idata.opcode = MMC_SWITCH;
98 idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
99 (index << 16) |
100 (value << 8) |
101 EXT_CSD_CMD_SET_NORMAL;
102 idata.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
103
104 ret = ioctl(fd, MMC_IOC_CMD, &idata);
105 if (ret)
Nick Sanders9d57aa72014-03-05 21:38:54 -0800106 perror("ioctl Write EXT CSD");
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -0500107
108 return ret;
109}
110
Ben Gardiner27c357d2013-05-30 17:12:47 -0400111int send_status(int fd, __u32 *response)
112{
113 int ret = 0;
114 struct mmc_ioc_cmd idata;
115
116 memset(&idata, 0, sizeof(idata));
117 idata.opcode = MMC_SEND_STATUS;
118 idata.arg = (1 << 16);
119 idata.flags = MMC_RSP_R1 | MMC_CMD_AC;
120
121 ret = ioctl(fd, MMC_IOC_CMD, &idata);
122 if (ret)
123 perror("ioctl");
124
125 *response = idata.response[0];
126
127 return ret;
128}
129
Al Cooper1b7f5d72016-06-07 16:35:46 -0400130static __u32 get_size_in_blks(int fd)
131{
132 int res;
133 int size;
134
135 res = ioctl(fd, BLKGETSIZE, &size);
136 if (res) {
137 fprintf(stderr, "Error getting device size, errno: %d\n",
138 errno);
139 perror("");
140 return -1;
141 }
142 return size;
143}
144
145static int set_write_protect(int fd, __u32 blk_addr, int on_off)
146{
147 int ret = 0;
148 struct mmc_ioc_cmd idata;
149
150 memset(&idata, 0, sizeof(idata));
151 idata.write_flag = 1;
152 if (on_off)
153 idata.opcode = MMC_SET_WRITE_PROT;
154 else
155 idata.opcode = MMC_CLEAR_WRITE_PROT;
156 idata.arg = blk_addr;
157 idata.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
158
159 ret = ioctl(fd, MMC_IOC_CMD, &idata);
160 if (ret)
161 perror("ioctl");
162
163 return ret;
164}
165
166static int send_write_protect_type(int fd, __u32 blk_addr, __u64 *group_bits)
167{
168 int ret = 0;
169 struct mmc_ioc_cmd idata;
170 __u8 buf[8];
171 __u64 bits = 0;
172 int x;
173
174 memset(&idata, 0, sizeof(idata));
175 idata.write_flag = 0;
176 idata.opcode = MMC_SEND_WRITE_PROT_TYPE;
177 idata.blksz = 8,
178 idata.blocks = 1,
179 idata.arg = blk_addr;
180 idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
181 mmc_ioc_cmd_set_data(idata, buf);
182
183 ret = ioctl(fd, MMC_IOC_CMD, &idata);
184 if (ret)
185 perror("ioctl");
186 for (x = 0; x < sizeof(buf); x++)
187 bits |= (__u64)(buf[7 - x]) << (x * 8);
188 *group_bits = bits;
189 return ret;
190}
191
192static void print_writeprotect_boot_status(__u8 *ext_csd)
Chris Ballb9c7a172012-02-20 12:34:25 -0500193{
194 __u8 reg;
Al Cooper786418c2015-04-29 18:12:35 -0400195 __u8 ext_csd_rev = ext_csd[EXT_CSD_REV];
Chris Ballb9c7a172012-02-20 12:34:25 -0500196
197 /* A43: reserved [174:0] */
198 if (ext_csd_rev >= 5) {
199 printf("Boot write protection status registers"
200 " [BOOT_WP_STATUS]: 0x%02x\n", ext_csd[174]);
201
202 reg = ext_csd[EXT_CSD_BOOT_WP];
203 printf("Boot Area Write protection [BOOT_WP]: 0x%02x\n", reg);
204 printf(" Power ro locking: ");
205 if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS)
206 printf("not possible\n");
207 else
208 printf("possible\n");
209
210 printf(" Permanent ro locking: ");
211 if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS)
212 printf("not possible\n");
213 else
214 printf("possible\n");
215
Julius Werner7ddcb232020-03-13 14:21:48 -0700216 /* Note: We call the partitions 0 and 1 (like Linux does), but
217 * the eMMC spec calls them AREA 1 and AREA 2. */
218 reg = ext_csd[EXT_CSD_BOOT_WP_STATUS];
219 printf(" partition 0 ro lock status: ");
220 if (reg & EXT_CSD_BOOT_WP_S_AREA_1_PERM)
Chris Ballb9c7a172012-02-20 12:34:25 -0500221 printf("locked permanently\n");
Julius Werner7ddcb232020-03-13 14:21:48 -0700222 else if (reg & EXT_CSD_BOOT_WP_S_AREA_1_PWR)
223 printf("locked until next power on\n");
224 else
225 printf("not locked\n");
226 printf(" partition 1 ro lock status: ");
227 if (reg & EXT_CSD_BOOT_WP_S_AREA_2_PERM)
228 printf("locked permanently\n");
229 else if (reg & EXT_CSD_BOOT_WP_S_AREA_2_PWR)
230 printf("locked until next power on\n");
Chris Ballb9c7a172012-02-20 12:34:25 -0500231 else
232 printf("not locked\n");
233 }
234}
235
Al Cooper1b7f5d72016-06-07 16:35:46 -0400236static int get_wp_group_size_in_blks(__u8 *ext_csd, __u32 *size)
237{
238 __u8 ext_csd_rev = ext_csd[EXT_CSD_REV];
239
240 if ((ext_csd_rev < 5) || (ext_csd[EXT_CSD_ERASE_GROUP_DEF] == 0))
241 return 1;
242
243 *size = ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] *
244 ext_csd[EXT_CSD_HC_WP_GRP_SIZE] * 1024;
245 return 0;
246}
247
248
249int do_writeprotect_boot_get(int nargs, char **argv)
Chris Ballb9c7a172012-02-20 12:34:25 -0500250{
Nick Sanders9d57aa72014-03-05 21:38:54 -0800251 __u8 ext_csd[EXT_CSD_SIZE];
Chris Ballb9c7a172012-02-20 12:34:25 -0500252 int fd, ret;
253 char *device;
254
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100255 if (nargs != 2) {
256 fprintf(stderr, "Usage: mmc writeprotect boot get </path/to/mmcblkX>\n");
257 exit(1);
258 }
Chris Ballb9c7a172012-02-20 12:34:25 -0500259
260 device = argv[1];
261
262 fd = open(device, O_RDWR);
263 if (fd < 0) {
264 perror("open");
265 exit(1);
266 }
267
268 ret = read_extcsd(fd, ext_csd);
269 if (ret) {
270 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
271 exit(1);
272 }
273
Al Cooper1b7f5d72016-06-07 16:35:46 -0400274 print_writeprotect_boot_status(ext_csd);
Chris Ballb9c7a172012-02-20 12:34:25 -0500275
276 return ret;
277}
278
Al Cooper1b7f5d72016-06-07 16:35:46 -0400279int do_writeprotect_boot_set(int nargs, char **argv)
Chris Ballb9c7a172012-02-20 12:34:25 -0500280{
Nick Sanders9d57aa72014-03-05 21:38:54 -0800281 __u8 ext_csd[EXT_CSD_SIZE], value;
Chris Ballb9c7a172012-02-20 12:34:25 -0500282 int fd, ret;
283 char *device;
Julius Werner7ddcb232020-03-13 14:21:48 -0700284 char *end;
285 int argi = 1;
286 int permanent = 0;
287 int partition = -1;
Chris Ballb9c7a172012-02-20 12:34:25 -0500288
Julius Werner7ddcb232020-03-13 14:21:48 -0700289 if (!strcmp(argv[argi], "-p")){
290 permanent = 1;
291 argi++;
292 }
293
294 if (nargs < 1 + argi || nargs > 2 + argi) {
295 fprintf(stderr, "Usage: mmc writeprotect boot set [-p] </path/to/mmcblkX> [0|1]\n");
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100296 exit(1);
297 }
Chris Ballb9c7a172012-02-20 12:34:25 -0500298
Julius Werner7ddcb232020-03-13 14:21:48 -0700299 device = argv[argi++];
Chris Ballb9c7a172012-02-20 12:34:25 -0500300
301 fd = open(device, O_RDWR);
302 if (fd < 0) {
303 perror("open");
304 exit(1);
305 }
306
Julius Werner7ddcb232020-03-13 14:21:48 -0700307 if (nargs == 1 + argi) {
308 partition = strtoul(argv[argi], &end, 0);
309 if (*end != '\0' || !(partition == 0 || partition == 1)) {
310 fprintf(stderr, "Invalid partition number (must be 0 or 1): %s\n",
311 argv[argi]);
312 exit(1);
313 }
314 }
315
Chris Ballb9c7a172012-02-20 12:34:25 -0500316 ret = read_extcsd(fd, ext_csd);
317 if (ret) {
318 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
319 exit(1);
320 }
321
Julius Werner7ddcb232020-03-13 14:21:48 -0700322 value = ext_csd[EXT_CSD_BOOT_WP];
323 /*
324 * If permanent protection is already on for one partition and we're
325 * trying to enable power-reset protection for the other we need to make
326 * sure the selection bit for permanent protection still points to the
327 * former or we'll accidentally permanently protect the latter.
328 */
329 if ((value & EXT_CSD_BOOT_WP_B_PERM_WP_EN) && !permanent) {
330 if (ext_csd[EXT_CSD_BOOT_WP_STATUS] &
331 EXT_CSD_BOOT_WP_S_AREA_2_PERM) {
332 value |= EXT_CSD_BOOT_WP_B_PERM_WP_SEC_SEL;
333 if (partition != 1)
334 partition = 0;
335 } else {
336 /* PERM_WP_SEC_SEL cleared -> pointing to partition 0 */
337 if (partition != 0)
338 partition = 1;
339 }
340 }
341 if (partition != -1) {
342 value |= EXT_CSD_BOOT_WP_B_SEC_WP_SEL;
343 if (partition == 1)
344 value |= permanent ? EXT_CSD_BOOT_WP_B_PERM_WP_SEC_SEL
345 : EXT_CSD_BOOT_WP_B_PWR_WP_SEC_SEL;
346 }
347 value |= permanent ? EXT_CSD_BOOT_WP_B_PERM_WP_EN
348 : EXT_CSD_BOOT_WP_B_PWR_WP_EN;
349
Chris Ballb9c7a172012-02-20 12:34:25 -0500350 ret = write_extcsd_value(fd, EXT_CSD_BOOT_WP, value);
351 if (ret) {
352 fprintf(stderr, "Could not write 0x%02x to "
353 "EXT_CSD[%d] in %s\n",
354 value, EXT_CSD_BOOT_WP, device);
355 exit(1);
356 }
357
358 return ret;
359}
360
Al Cooper1b7f5d72016-06-07 16:35:46 -0400361static char *prot_desc[] = {
362 "No",
363 "Temporary",
364 "Power-on",
365 "Permanent"
366};
367
368static void print_wp_status(__u32 wp_sizeblks, __u32 start_group,
369 __u32 end_group, int rptype)
370{
371 printf("Write Protect Groups %d-%d (Blocks %d-%d), ",
372 start_group, end_group,
373 start_group * wp_sizeblks, ((end_group + 1) * wp_sizeblks) - 1);
374 printf("%s Write Protection\n", prot_desc[rptype]);
375}
376
377
378int do_writeprotect_user_get(int nargs, char **argv)
379{
380 __u8 ext_csd[512];
381 int fd, ret;
382 char *device;
383 int x;
384 int y = 0;
385 __u32 wp_sizeblks;
386 __u32 dev_sizeblks;
387 __u32 cnt;
388 __u64 bits;
389 __u32 wpblk;
390 __u32 last_wpblk = 0;
391 __u32 prot;
392 __u32 last_prot = -1;
393 int remain;
394
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100395 if (nargs != 2) {
396 fprintf(stderr, "Usage: mmc writeprotect user get </path/to/mmcblkX>\n");
397 exit(1);
398 }
Al Cooper1b7f5d72016-06-07 16:35:46 -0400399
400 device = argv[1];
401
402 fd = open(device, O_RDWR);
403 if (fd < 0) {
404 perror("open");
405 exit(1);
406 }
407 ret = read_extcsd(fd, ext_csd);
408 if (ret) {
409 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
410 exit(1);
411 }
412
413 ret = get_wp_group_size_in_blks(ext_csd, &wp_sizeblks);
414 if (ret)
415 exit(1);
416 printf("Write Protect Group size in blocks/bytes: %d/%d\n",
417 wp_sizeblks, wp_sizeblks * 512);
418 dev_sizeblks = get_size_in_blks(fd);
419 cnt = dev_sizeblks / wp_sizeblks;
420 for (x = 0; x < cnt; x += WP_BLKS_PER_QUERY) {
421 ret = send_write_protect_type(fd, x * wp_sizeblks, &bits);
422 if (ret)
423 break;
424 remain = cnt - x;
425 if (remain > WP_BLKS_PER_QUERY)
426 remain = WP_BLKS_PER_QUERY;
427 for (y = 0; y < remain; y++) {
428 prot = (bits >> (y * 2)) & 0x3;
429 if (prot != last_prot) {
430 /* not first time */
431 if (last_prot != -1) {
432 wpblk = x + y;
433 print_wp_status(wp_sizeblks,
434 last_wpblk,
435 wpblk - 1,
436 last_prot);
437 last_wpblk = wpblk;
438 }
439 last_prot = prot;
440 }
441 }
442 }
443 if (last_wpblk != (x + y - 1))
444 print_wp_status(wp_sizeblks, last_wpblk, cnt - 1, last_prot);
445
446 return ret;
447}
448
449int do_writeprotect_user_set(int nargs, char **argv)
450{
451 __u8 ext_csd[512];
452 int fd, ret;
453 char *device;
454 int blk_start;
455 int blk_cnt;
456 __u32 wp_blks;
457 __u8 user_wp;
458 int x;
459 int wptype;
460
461 if (nargs != 5)
462 goto usage;
463 device = argv[4];
464 fd = open(device, O_RDWR);
465 if (fd < 0) {
466 perror("open");
467 exit(1);
468 }
469 if (!strcmp(argv[1], "none")) {
470 wptype = WPTYPE_NONE;
471 } else if (!strcmp(argv[1], "temp")) {
472 wptype = WPTYPE_TEMP;
473 } else if (!strcmp(argv[1], "pwron")) {
474 wptype = WPTYPE_PWRON;
475#ifdef DANGEROUS_COMMANDS_ENABLED
476 } else if (!strcmp(argv[1], "perm")) {
477 wptype = WPTYPE_PERM;
478#endif /* DANGEROUS_COMMANDS_ENABLED */
479 } else {
480 fprintf(stderr, "Error, invalid \"type\"\n");
481 goto usage;
482 }
483 ret = read_extcsd(fd, ext_csd);
484 if (ret) {
485 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
486 exit(1);
487 }
488 ret = get_wp_group_size_in_blks(ext_csd, &wp_blks);
489 if (ret) {
490 fprintf(stderr, "Operation not supported for this device\n");
491 exit(1);
492 }
493 blk_start = strtol(argv[2], NULL, 0);
494 blk_cnt = strtol(argv[3], NULL, 0);
495 if ((blk_start % wp_blks) || (blk_cnt % wp_blks)) {
496 fprintf(stderr, "<start block> and <blocks> must be a ");
497 fprintf(stderr, "multiple of the Write Protect Group (%d)\n",
498 wp_blks);
499 exit(1);
500 }
501 if (wptype != WPTYPE_NONE) {
502 user_wp = ext_csd[EXT_CSD_USER_WP];
503 user_wp &= ~USER_WP_CLEAR;
504 switch (wptype) {
505 case WPTYPE_TEMP:
506 break;
507 case WPTYPE_PWRON:
508 user_wp |= USER_WP_US_PWR_WP_EN;
509 break;
510 case WPTYPE_PERM:
511 user_wp |= USER_WP_US_PERM_WP_EN;
512 break;
513 }
514 if (user_wp != ext_csd[EXT_CSD_USER_WP]) {
515 ret = write_extcsd_value(fd, EXT_CSD_USER_WP, user_wp);
516 if (ret) {
517 fprintf(stderr, "Error setting EXT_CSD\n");
518 exit(1);
519 }
520 }
521 }
522 for (x = 0; x < blk_cnt; x += wp_blks) {
523 ret = set_write_protect(fd, blk_start + x,
524 wptype != WPTYPE_NONE);
525 if (ret) {
526 fprintf(stderr,
527 "Could not set write protect for %s\n", device);
528 exit(1);
529 }
530 }
531 if (wptype != WPTYPE_NONE) {
532 ret = write_extcsd_value(fd, EXT_CSD_USER_WP,
533 ext_csd[EXT_CSD_USER_WP]);
534 if (ret) {
535 fprintf(stderr, "Error restoring EXT_CSD\n");
536 exit(1);
537 }
538 }
539 return ret;
540
541usage:
542 fprintf(stderr,
543 "Usage: mmc writeprotect user set <type><start block><blocks><device>\n");
544 exit(1);
545}
546
Saugata Dasb7e25992012-05-17 09:26:34 -0400547int do_disable_512B_emulation(int nargs, char **argv)
548{
Nick Sanders9d57aa72014-03-05 21:38:54 -0800549 __u8 ext_csd[EXT_CSD_SIZE], native_sector_size, data_sector_size, wr_rel_param;
Saugata Dasb7e25992012-05-17 09:26:34 -0400550 int fd, ret;
551 char *device;
552
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100553 if (nargs != 2) {
554 fprintf(stderr, "Usage: mmc disable 512B emulation </path/to/mmcblkX>\n");
555 exit(1);
556 }
557
Saugata Dasb7e25992012-05-17 09:26:34 -0400558 device = argv[1];
559
560 fd = open(device, O_RDWR);
561 if (fd < 0) {
562 perror("open");
563 exit(1);
564 }
565
566 ret = read_extcsd(fd, ext_csd);
567 if (ret) {
568 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
569 exit(1);
570 }
571
572 wr_rel_param = ext_csd[EXT_CSD_WR_REL_PARAM];
573 native_sector_size = ext_csd[EXT_CSD_NATIVE_SECTOR_SIZE];
574 data_sector_size = ext_csd[EXT_CSD_DATA_SECTOR_SIZE];
575
576 if (native_sector_size && !data_sector_size &&
577 (wr_rel_param & EN_REL_WR)) {
578 ret = write_extcsd_value(fd, EXT_CSD_USE_NATIVE_SECTOR, 1);
579
580 if (ret) {
581 fprintf(stderr, "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
Tomas Melin295dd7a2016-08-29 11:45:44 -0400582 1, EXT_CSD_NATIVE_SECTOR_SIZE, device);
Saugata Dasb7e25992012-05-17 09:26:34 -0400583 exit(1);
584 }
585 printf("MMC disable 512B emulation successful. Now reset the device to switch to 4KB native sector mode.\n");
586 } else if (native_sector_size && data_sector_size) {
587 printf("MMC 512B emulation mode is already disabled; doing nothing.\n");
588 } else {
589 printf("MMC does not support disabling 512B emulation mode.\n");
590 }
591
592 return ret;
593}
594
Giuseppe CAVALLARO7bd13202012-04-19 10:58:37 +0200595int do_write_boot_en(int nargs, char **argv)
596{
597 __u8 ext_csd[512];
598 __u8 value = 0;
599 int fd, ret;
600 char *device;
601 int boot_area, send_ack;
602
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100603 if (nargs != 4) {
604 fprintf(stderr, "Usage: mmc bootpart enable <partition_number> <send_ack> </path/to/mmcblkX>\n");
605 exit(1);
606 }
Giuseppe CAVALLARO7bd13202012-04-19 10:58:37 +0200607
608 /*
609 * If <send_ack> is 1, the device will send acknowledgment
610 * pattern "010" to the host when boot operation begins.
611 * If <send_ack> is 0, it won't.
612 */
613 boot_area = strtol(argv[1], NULL, 10);
614 send_ack = strtol(argv[2], NULL, 10);
615 device = argv[3];
616
617 fd = open(device, O_RDWR);
618 if (fd < 0) {
619 perror("open");
620 exit(1);
621 }
622
623 ret = read_extcsd(fd, ext_csd);
624 if (ret) {
625 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
626 exit(1);
627 }
628
629 value = ext_csd[EXT_CSD_PART_CONFIG];
630
631 switch (boot_area) {
Markus Schuetterlefbc0e6c2016-03-19 08:42:41 +0100632 case EXT_CSD_PART_CONFIG_ACC_NONE:
633 value &= ~(7 << 3);
634 break;
Giuseppe CAVALLARO7bd13202012-04-19 10:58:37 +0200635 case EXT_CSD_PART_CONFIG_ACC_BOOT0:
636 value |= (1 << 3);
637 value &= ~(3 << 4);
638 break;
639 case EXT_CSD_PART_CONFIG_ACC_BOOT1:
640 value |= (1 << 4);
641 value &= ~(1 << 3);
642 value &= ~(1 << 5);
643 break;
644 case EXT_CSD_PART_CONFIG_ACC_USER_AREA:
645 value |= (boot_area << 3);
646 break;
647 default:
648 fprintf(stderr, "Cannot enable the boot area\n");
649 exit(1);
650 }
651 if (send_ack)
652 value |= EXT_CSD_PART_CONFIG_ACC_ACK;
653 else
654 value &= ~EXT_CSD_PART_CONFIG_ACC_ACK;
655
656 ret = write_extcsd_value(fd, EXT_CSD_PART_CONFIG, value);
657 if (ret) {
658 fprintf(stderr, "Could not write 0x%02x to "
659 "EXT_CSD[%d] in %s\n",
660 value, EXT_CSD_PART_CONFIG, device);
661 exit(1);
662 }
663 return ret;
664}
665
Al Cooper794314c2015-05-01 08:24:37 -0400666int do_boot_bus_conditions_set(int nargs, char **argv)
667{
668 __u8 ext_csd[512];
669 __u8 value = 0;
670 int fd, ret;
671 char *device;
672
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100673 if (nargs != 5) {
674 fprintf(stderr, "Usage: mmc: bootbus set <boot_mode> <reset_boot_bus_conditions> <boot_bus_width> <device>\n");
675 exit(1);
676 }
Al Cooper794314c2015-05-01 08:24:37 -0400677
678 if (strcmp(argv[1], "single_backward") == 0)
679 value |= 0;
680 else if (strcmp(argv[1], "single_hs") == 0)
681 value |= 0x8;
682 else if (strcmp(argv[1], "dual") == 0)
683 value |= 0x10;
684 else {
685 fprintf(stderr, "illegal <boot_mode> specified\n");
686 exit(1);
687 }
688
689 if (strcmp(argv[2], "x1") == 0)
690 value |= 0;
691 else if (strcmp(argv[2], "retain") == 0)
692 value |= 0x4;
693 else {
694 fprintf(stderr,
695 "illegal <reset_boot_bus_conditions> specified\n");
696 exit(1);
697 }
698
699 if (strcmp(argv[3], "x1") == 0)
700 value |= 0;
701 else if (strcmp(argv[3], "x4") == 0)
702 value |= 0x1;
703 else if (strcmp(argv[3], "x8") == 0)
704 value |= 0x2;
705 else {
706 fprintf(stderr, "illegal <boot_bus_width> specified\n");
707 exit(1);
708 }
709
710 device = argv[4];
711 fd = open(device, O_RDWR);
712 if (fd < 0) {
713 perror("open");
714 exit(1);
715 }
716
717 ret = read_extcsd(fd, ext_csd);
718 if (ret) {
719 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
720 exit(1);
721 }
722 printf("Changing ext_csd[BOOT_BUS_CONDITIONS] from 0x%02x to 0x%02x\n",
723 ext_csd[EXT_CSD_BOOT_BUS_CONDITIONS], value);
724
725 ret = write_extcsd_value(fd, EXT_CSD_BOOT_BUS_CONDITIONS, value);
726 if (ret) {
727 fprintf(stderr, "Could not write 0x%02x to "
728 "EXT_CSD[%d] in %s\n",
729 value, EXT_CSD_BOOT_BUS_CONDITIONS, device);
730 exit(1);
731 }
732 close(fd);
733 return ret;
734}
735
Chris Ballf74dfe22012-10-19 16:49:55 -0400736int do_hwreset(int value, int nargs, char **argv)
737{
Nick Sanders9d57aa72014-03-05 21:38:54 -0800738 __u8 ext_csd[EXT_CSD_SIZE];
Chris Ballf74dfe22012-10-19 16:49:55 -0400739 int fd, ret;
740 char *device;
741
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100742 if (nargs != 2) {
743 fprintf(stderr, "Usage: mmc hwreset enable </path/to/mmcblkX>\n");
744 exit(1);
745 }
Chris Ballf74dfe22012-10-19 16:49:55 -0400746
747 device = argv[1];
748
749 fd = open(device, O_RDWR);
750 if (fd < 0) {
751 perror("open");
752 exit(1);
753 }
754
755 ret = read_extcsd(fd, ext_csd);
756 if (ret) {
757 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
758 exit(1);
759 }
760
761 if ((ext_csd[EXT_CSD_RST_N_FUNCTION] & EXT_CSD_RST_N_EN_MASK) ==
762 EXT_CSD_HW_RESET_EN) {
763 fprintf(stderr,
764 "H/W Reset is already permanently enabled on %s\n",
765 device);
766 exit(1);
767 }
768 if ((ext_csd[EXT_CSD_RST_N_FUNCTION] & EXT_CSD_RST_N_EN_MASK) ==
769 EXT_CSD_HW_RESET_DIS) {
770 fprintf(stderr,
771 "H/W Reset is already permanently disabled on %s\n",
772 device);
773 exit(1);
774 }
775
776 ret = write_extcsd_value(fd, EXT_CSD_RST_N_FUNCTION, value);
777 if (ret) {
778 fprintf(stderr,
779 "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
780 value, EXT_CSD_RST_N_FUNCTION, device);
781 exit(1);
782 }
783
784 return ret;
785}
786
787int do_hwreset_en(int nargs, char **argv)
788{
789 return do_hwreset(EXT_CSD_HW_RESET_EN, nargs, argv);
790}
791
792int do_hwreset_dis(int nargs, char **argv)
793{
794 return do_hwreset(EXT_CSD_HW_RESET_DIS, nargs, argv);
795}
796
Jaehoon Chung86496512012-09-21 10:08:05 +0000797int do_write_bkops_en(int nargs, char **argv)
798{
Nick Sanders9d57aa72014-03-05 21:38:54 -0800799 __u8 ext_csd[EXT_CSD_SIZE], value = 0;
Jaehoon Chung86496512012-09-21 10:08:05 +0000800 int fd, ret;
801 char *device;
802
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100803 if (nargs != 2) {
804 fprintf(stderr, "Usage: mmc bkops enable </path/to/mmcblkX>\n");
805 exit(1);
806 }
Jaehoon Chung86496512012-09-21 10:08:05 +0000807
808 device = argv[1];
809
810 fd = open(device, O_RDWR);
811 if (fd < 0) {
812 perror("open");
813 exit(1);
814 }
815
816 ret = read_extcsd(fd, ext_csd);
817 if (ret) {
818 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
819 exit(1);
820 }
821
822 if (!(ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1)) {
823 fprintf(stderr, "%s doesn't support BKOPS\n", device);
824 exit(1);
825 }
826
827 ret = write_extcsd_value(fd, EXT_CSD_BKOPS_EN, BKOPS_ENABLE);
828 if (ret) {
829 fprintf(stderr, "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
830 value, EXT_CSD_BKOPS_EN, device);
831 exit(1);
832 }
833
834 return ret;
835}
836
Ben Gardiner27c357d2013-05-30 17:12:47 -0400837int do_status_get(int nargs, char **argv)
838{
839 __u32 response;
840 int fd, ret;
841 char *device;
842
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +0100843 if (nargs != 2) {
844 fprintf(stderr, "Usage: mmc status get </path/to/mmcblkX>\n");
845 exit(1);
846 }
Ben Gardiner27c357d2013-05-30 17:12:47 -0400847
848 device = argv[1];
849
850 fd = open(device, O_RDWR);
851 if (fd < 0) {
852 perror("open");
853 exit(1);
854 }
855
856 ret = send_status(fd, &response);
857 if (ret) {
858 fprintf(stderr, "Could not read response to SEND_STATUS from %s\n", device);
859 exit(1);
860 }
861
862 printf("SEND_STATUS response: 0x%08x\n", response);
863
864 return ret;
865}
866
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -0800867__u32 get_word_from_ext_csd(__u8 *ext_csd_loc)
868{
869 return (ext_csd_loc[3] << 24) |
870 (ext_csd_loc[2] << 16) |
871 (ext_csd_loc[1] << 8) |
872 ext_csd_loc[0];
873}
874
Ben Gardiner4e850232013-05-30 17:12:49 -0400875unsigned int get_sector_count(__u8 *ext_csd)
876{
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -0800877 return get_word_from_ext_csd(&ext_csd[EXT_CSD_SEC_COUNT_0]);
Ben Gardiner4e850232013-05-30 17:12:49 -0400878}
879
880int is_blockaddresed(__u8 *ext_csd)
881{
882 unsigned int sectors = get_sector_count(ext_csd);
883
Tomas Melin2e610662016-08-29 11:41:10 -0400884 /* over 2GiB devices are block-addressed */
Ben Gardiner4e850232013-05-30 17:12:49 -0400885 return (sectors > (2u * 1024 * 1024 * 1024) / 512);
886}
887
Ben Gardinerf82e27a2013-05-30 17:12:50 -0400888unsigned int get_hc_wp_grp_size(__u8 *ext_csd)
889{
890 return ext_csd[221];
891}
892
893unsigned int get_hc_erase_grp_size(__u8 *ext_csd)
894{
895 return ext_csd[224];
896}
897
Ben Gardinere6e84e92013-09-19 11:14:27 -0400898int set_partitioning_setting_completed(int dry_run, const char * const device,
899 int fd)
900{
901 int ret;
902
Tomas Melin4dadd872016-08-29 11:55:08 -0400903 if (dry_run == 1) {
Ben Gardinere6e84e92013-09-19 11:14:27 -0400904 fprintf(stderr, "NOT setting PARTITION_SETTING_COMPLETED\n");
905 fprintf(stderr, "These changes will not take effect neither "
906 "now nor after a power cycle\n");
907 return 1;
Tomas Melin4dadd872016-08-29 11:55:08 -0400908 } else if (dry_run == 2) {
909 printf("-c given, expecting more partition settings before "
910 "writing PARTITION_SETTING_COMPLETED\n");
911 return 0;
Ben Gardinere6e84e92013-09-19 11:14:27 -0400912 }
913
914 fprintf(stderr, "setting OTP PARTITION_SETTING_COMPLETED!\n");
915 ret = write_extcsd_value(fd, EXT_CSD_PARTITION_SETTING_COMPLETED, 0x1);
916 if (ret) {
917 fprintf(stderr, "Could not write 0x1 to "
918 "EXT_CSD[%d] in %s\n",
919 EXT_CSD_PARTITION_SETTING_COMPLETED, device);
920 return 1;
921 }
922
923 __u32 response;
924 ret = send_status(fd, &response);
925 if (ret) {
926 fprintf(stderr, "Could not get response to SEND_STATUS "
927 "from %s\n", device);
928 return 1;
929 }
930
931 if (response & R1_SWITCH_ERROR) {
932 fprintf(stderr, "Setting OTP PARTITION_SETTING_COMPLETED "
933 "failed on %s\n", device);
934 return 1;
935 }
936
937 fprintf(stderr, "Setting OTP PARTITION_SETTING_COMPLETED on "
938 "%s SUCCESS\n", device);
939 fprintf(stderr, "Device power cycle needed for settings to "
940 "take effect.\n"
941 "Confirm that PARTITION_SETTING_COMPLETED bit is set "
942 "using 'extcsd read' after power cycle\n");
943
944 return 0;
945}
946
Balaji T K1fdb7f92015-04-29 18:12:32 -0400947int check_enhanced_area_total_limit(const char * const device, int fd)
948{
949 __u8 ext_csd[512];
950 __u32 regl;
951 unsigned long max_enh_area_sz, user_area_sz, enh_area_sz = 0;
952 unsigned long gp4_part_sz, gp3_part_sz, gp2_part_sz, gp1_part_sz;
Balaji T Kd78ce082015-04-29 18:12:33 -0400953 unsigned long total_sz, total_gp_user_sz;
Balaji T K1fdb7f92015-04-29 18:12:32 -0400954 unsigned int wp_sz, erase_sz;
955 int ret;
956
957 ret = read_extcsd(fd, ext_csd);
958 if (ret) {
959 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
960 exit(1);
961 }
962 wp_sz = get_hc_wp_grp_size(ext_csd);
963 erase_sz = get_hc_erase_grp_size(ext_csd);
964
965 regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_4_2] << 16) |
966 (ext_csd[EXT_CSD_GP_SIZE_MULT_4_1] << 8) |
967 ext_csd[EXT_CSD_GP_SIZE_MULT_4_0];
968 gp4_part_sz = 512l * regl * erase_sz * wp_sz;
969 if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_4) {
970 enh_area_sz += gp4_part_sz;
971 printf("Enhanced GP4 Partition Size [GP_SIZE_MULT_4]: 0x%06x\n", regl);
972 printf(" i.e. %lu KiB\n", gp4_part_sz);
973 }
974
975 regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_3_2] << 16) |
976 (ext_csd[EXT_CSD_GP_SIZE_MULT_3_1] << 8) |
977 ext_csd[EXT_CSD_GP_SIZE_MULT_3_0];
978 gp3_part_sz = 512l * regl * erase_sz * wp_sz;
979 if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_3) {
980 enh_area_sz += gp3_part_sz;
981 printf("Enhanced GP3 Partition Size [GP_SIZE_MULT_3]: 0x%06x\n", regl);
982 printf(" i.e. %lu KiB\n", gp3_part_sz);
983 }
984
985 regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_2_2] << 16) |
986 (ext_csd[EXT_CSD_GP_SIZE_MULT_2_1] << 8) |
987 ext_csd[EXT_CSD_GP_SIZE_MULT_2_0];
988 gp2_part_sz = 512l * regl * erase_sz * wp_sz;
989 if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_2) {
990 enh_area_sz += gp2_part_sz;
991 printf("Enhanced GP2 Partition Size [GP_SIZE_MULT_2]: 0x%06x\n", regl);
992 printf(" i.e. %lu KiB\n", gp2_part_sz);
993 }
994
995 regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_1_2] << 16) |
996 (ext_csd[EXT_CSD_GP_SIZE_MULT_1_1] << 8) |
997 ext_csd[EXT_CSD_GP_SIZE_MULT_1_0];
998 gp1_part_sz = 512l * regl * erase_sz * wp_sz;
999 if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_1) {
1000 enh_area_sz += gp1_part_sz;
1001 printf("Enhanced GP1 Partition Size [GP_SIZE_MULT_1]: 0x%06x\n", regl);
1002 printf(" i.e. %lu KiB\n", gp1_part_sz);
1003 }
1004
1005 regl = (ext_csd[EXT_CSD_ENH_SIZE_MULT_2] << 16) |
1006 (ext_csd[EXT_CSD_ENH_SIZE_MULT_1] << 8) |
1007 ext_csd[EXT_CSD_ENH_SIZE_MULT_0];
1008 user_area_sz = 512l * regl * erase_sz * wp_sz;
1009 if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_USR) {
1010 enh_area_sz += user_area_sz;
1011 printf("Enhanced User Data Area Size [ENH_SIZE_MULT]: 0x%06x\n", regl);
1012 printf(" i.e. %lu KiB\n", user_area_sz);
1013 }
1014
1015 regl = (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_2] << 16) |
1016 (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_1] << 8) |
1017 ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_0];
1018 max_enh_area_sz = 512l * regl * erase_sz * wp_sz;
1019 printf("Max Enhanced Area Size [MAX_ENH_SIZE_MULT]: 0x%06x\n", regl);
1020 printf(" i.e. %lu KiB\n", max_enh_area_sz);
1021 if (enh_area_sz > max_enh_area_sz) {
1022 fprintf(stderr,
1023 "Programmed total enhanced size %lu KiB cannot exceed max enhanced area %lu KiB %s\n",
1024 enh_area_sz, max_enh_area_sz, device);
1025 return 1;
1026 }
Balaji T Kd78ce082015-04-29 18:12:33 -04001027 total_sz = get_sector_count(ext_csd) / 2;
1028 total_gp_user_sz = gp4_part_sz + gp3_part_sz + gp2_part_sz +
1029 gp1_part_sz + user_area_sz;
1030 if (total_gp_user_sz > total_sz) {
1031 fprintf(stderr,
1032 "requested total partition size %lu KiB cannot exceed card capacity %lu KiB %s\n",
1033 total_gp_user_sz, total_sz, device);
1034 return 1;
1035 }
1036
1037 return 0;
1038}
1039
1040int do_create_gp_partition(int nargs, char **argv)
1041{
1042 __u8 value;
1043 __u8 ext_csd[512];
1044 __u8 address;
1045 int fd, ret;
1046 char *device;
1047 int dry_run = 1;
1048 int partition, enh_attr, ext_attr;
1049 unsigned int length_kib, gp_size_mult;
1050 unsigned long align;
1051
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01001052 if (nargs != 7) {
1053 fprintf(stderr, "Usage: mmc gp create <-y|-n|-c> <length KiB> <partition> <enh_attr> <ext_attr> </path/to/mmcblkX>\n");
1054 exit(1);
1055 }
Balaji T Kd78ce082015-04-29 18:12:33 -04001056
Tomas Melin4dadd872016-08-29 11:55:08 -04001057 if (!strcmp("-y", argv[1])) {
Balaji T Kd78ce082015-04-29 18:12:33 -04001058 dry_run = 0;
Tomas Melin4dadd872016-08-29 11:55:08 -04001059 } else if (!strcmp("-c", argv[1])) {
1060 dry_run = 2;
1061 }
Balaji T Kd78ce082015-04-29 18:12:33 -04001062
1063 length_kib = strtol(argv[2], NULL, 10);
1064 partition = strtol(argv[3], NULL, 10);
1065 enh_attr = strtol(argv[4], NULL, 10);
1066 ext_attr = strtol(argv[5], NULL, 10);
1067 device = argv[6];
1068
Marcus Folkessoncb04fde2015-11-18 15:06:16 -05001069 if (partition < 1 || partition > 4) {
1070 printf("Invalid gp partition number; valid range [1-4].\n");
Balaji T Kd78ce082015-04-29 18:12:33 -04001071 exit(1);
1072 }
1073
1074 if (enh_attr && ext_attr) {
1075 printf("Not allowed to set both enhanced attribute and extended attribute\n");
1076 exit(1);
1077 }
1078
1079 fd = open(device, O_RDWR);
1080 if (fd < 0) {
1081 perror("open");
1082 exit(1);
1083 }
1084
1085 ret = read_extcsd(fd, ext_csd);
1086 if (ret) {
1087 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
1088 exit(1);
1089 }
1090
1091 /* assert not PARTITION_SETTING_COMPLETED */
1092 if (ext_csd[EXT_CSD_PARTITION_SETTING_COMPLETED]) {
1093 printf(" Device is already partitioned\n");
1094 exit(1);
1095 }
1096
1097 align = 512l * get_hc_wp_grp_size(ext_csd) * get_hc_erase_grp_size(ext_csd);
1098 gp_size_mult = (length_kib + align/2l) / align;
1099
1100 /* set EXT_CSD_ERASE_GROUP_DEF bit 0 */
1101 ret = write_extcsd_value(fd, EXT_CSD_ERASE_GROUP_DEF, 0x1);
1102 if (ret) {
1103 fprintf(stderr, "Could not write 0x1 to EXT_CSD[%d] in %s\n",
1104 EXT_CSD_ERASE_GROUP_DEF, device);
1105 exit(1);
1106 }
1107
1108 value = (gp_size_mult >> 16) & 0xff;
1109 address = EXT_CSD_GP_SIZE_MULT_1_2 + (partition - 1) * 3;
1110 ret = write_extcsd_value(fd, address, value);
1111 if (ret) {
1112 fprintf(stderr, "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
1113 value, address, device);
1114 exit(1);
1115 }
1116 value = (gp_size_mult >> 8) & 0xff;
1117 address = EXT_CSD_GP_SIZE_MULT_1_1 + (partition - 1) * 3;
1118 ret = write_extcsd_value(fd, address, value);
1119 if (ret) {
1120 fprintf(stderr, "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
1121 value, address, device);
1122 exit(1);
1123 }
1124 value = gp_size_mult & 0xff;
1125 address = EXT_CSD_GP_SIZE_MULT_1_0 + (partition - 1) * 3;
1126 ret = write_extcsd_value(fd, address, value);
1127 if (ret) {
1128 fprintf(stderr, "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
1129 value, address, device);
1130 exit(1);
1131 }
1132
1133 value = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE];
1134 if (enh_attr)
1135 value |= (1 << partition);
1136 else
1137 value &= ~(1 << partition);
1138
1139 ret = write_extcsd_value(fd, EXT_CSD_PARTITIONS_ATTRIBUTE, value);
1140 if (ret) {
1141 fprintf(stderr, "Could not write EXT_CSD_ENH_%x to EXT_CSD[%d] in %s\n",
1142 partition, EXT_CSD_PARTITIONS_ATTRIBUTE, device);
1143 exit(1);
1144 }
1145
1146 address = EXT_CSD_EXT_PARTITIONS_ATTRIBUTE_0 + (partition - 1) / 2;
1147 value = ext_csd[address];
1148 if (ext_attr)
1149 value |= (ext_attr << (4 * ((partition - 1) % 2)));
1150 else
1151 value &= (0xF << (4 * ((partition % 2))));
1152
1153 ret = write_extcsd_value(fd, address, value);
1154 if (ret) {
1155 fprintf(stderr, "Could not write 0x%x to EXT_CSD[%d] in %s\n",
1156 value, address, device);
1157 exit(1);
1158 }
1159
1160 ret = check_enhanced_area_total_limit(device, fd);
1161 if (ret)
1162 exit(1);
1163
Tomas Melin20379fa2016-08-29 12:02:28 -04001164 if (set_partitioning_setting_completed(dry_run, device, fd))
Balaji T Kd78ce082015-04-29 18:12:33 -04001165 exit(1);
Balaji T K1fdb7f92015-04-29 18:12:32 -04001166
1167 return 0;
1168}
1169
Ben Gardinerd91d3692013-05-30 17:12:51 -04001170int do_enh_area_set(int nargs, char **argv)
1171{
1172 __u8 value;
Nick Sanders9d57aa72014-03-05 21:38:54 -08001173 __u8 ext_csd[EXT_CSD_SIZE];
Ben Gardinerd91d3692013-05-30 17:12:51 -04001174 int fd, ret;
1175 char *device;
1176 int dry_run = 1;
1177 unsigned int start_kib, length_kib, enh_start_addr, enh_size_mult;
1178 unsigned long align;
1179
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01001180 if (nargs != 5) {
1181 fprintf(stderr, "Usage: mmc enh_area set <-y|-n|-c> <start KiB> <length KiB> </path/to/mmcblkX>\n");
1182 exit(1);
1183 }
Ben Gardinerd91d3692013-05-30 17:12:51 -04001184
Tomas Melin4dadd872016-08-29 11:55:08 -04001185 if (!strcmp("-y", argv[1])) {
Ben Gardinerd91d3692013-05-30 17:12:51 -04001186 dry_run = 0;
Tomas Melin4dadd872016-08-29 11:55:08 -04001187 } else if (!strcmp("-c", argv[1])) {
1188 dry_run = 2;
1189 }
Ben Gardinerd91d3692013-05-30 17:12:51 -04001190
1191 start_kib = strtol(argv[2], NULL, 10);
1192 length_kib = strtol(argv[3], NULL, 10);
1193 device = argv[4];
1194
1195 fd = open(device, O_RDWR);
1196 if (fd < 0) {
1197 perror("open");
1198 exit(1);
1199 }
1200
1201 ret = read_extcsd(fd, ext_csd);
1202 if (ret) {
1203 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
1204 exit(1);
1205 }
1206
1207 /* assert ENH_ATTRIBUTE_EN */
1208 if (!(ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & EXT_CSD_ENH_ATTRIBUTE_EN))
1209 {
1210 printf(" Device cannot have enhanced tech.\n");
1211 exit(1);
1212 }
1213
1214 /* assert not PARTITION_SETTING_COMPLETED */
1215 if (ext_csd[EXT_CSD_PARTITION_SETTING_COMPLETED])
1216 {
1217 printf(" Device is already partitioned\n");
1218 exit(1);
1219 }
1220
1221 align = 512l * get_hc_wp_grp_size(ext_csd) * get_hc_erase_grp_size(ext_csd);
1222
1223 enh_size_mult = (length_kib + align/2l) / align;
1224
jason.zeng1c553062018-03-27 02:17:10 -04001225 enh_start_addr = start_kib * (1024 / (is_blockaddresed(ext_csd) ? 512 : 1));
Ben Gardinerd91d3692013-05-30 17:12:51 -04001226 enh_start_addr /= align;
1227 enh_start_addr *= align;
1228
1229 /* set EXT_CSD_ERASE_GROUP_DEF bit 0 */
1230 ret = write_extcsd_value(fd, EXT_CSD_ERASE_GROUP_DEF, 0x1);
1231 if (ret) {
1232 fprintf(stderr, "Could not write 0x1 to "
1233 "EXT_CSD[%d] in %s\n",
1234 EXT_CSD_ERASE_GROUP_DEF, device);
1235 exit(1);
1236 }
1237
1238 /* write to ENH_START_ADDR and ENH_SIZE_MULT and PARTITIONS_ATTRIBUTE's ENH_USR bit */
1239 value = (enh_start_addr >> 24) & 0xff;
1240 ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_3, value);
1241 if (ret) {
1242 fprintf(stderr, "Could not write 0x%02x to "
1243 "EXT_CSD[%d] in %s\n", value,
1244 EXT_CSD_ENH_START_ADDR_3, device);
1245 exit(1);
1246 }
1247 value = (enh_start_addr >> 16) & 0xff;
1248 ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_2, value);
1249 if (ret) {
1250 fprintf(stderr, "Could not write 0x%02x to "
1251 "EXT_CSD[%d] in %s\n", value,
1252 EXT_CSD_ENH_START_ADDR_2, device);
1253 exit(1);
1254 }
1255 value = (enh_start_addr >> 8) & 0xff;
1256 ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_1, value);
1257 if (ret) {
1258 fprintf(stderr, "Could not write 0x%02x to "
1259 "EXT_CSD[%d] in %s\n", value,
1260 EXT_CSD_ENH_START_ADDR_1, device);
1261 exit(1);
1262 }
1263 value = enh_start_addr & 0xff;
1264 ret = write_extcsd_value(fd, EXT_CSD_ENH_START_ADDR_0, value);
1265 if (ret) {
1266 fprintf(stderr, "Could not write 0x%02x to "
1267 "EXT_CSD[%d] in %s\n", value,
1268 EXT_CSD_ENH_START_ADDR_0, device);
1269 exit(1);
1270 }
1271
1272 value = (enh_size_mult >> 16) & 0xff;
1273 ret = write_extcsd_value(fd, EXT_CSD_ENH_SIZE_MULT_2, value);
1274 if (ret) {
1275 fprintf(stderr, "Could not write 0x%02x to "
1276 "EXT_CSD[%d] in %s\n", value,
1277 EXT_CSD_ENH_SIZE_MULT_2, device);
1278 exit(1);
1279 }
1280 value = (enh_size_mult >> 8) & 0xff;
1281 ret = write_extcsd_value(fd, EXT_CSD_ENH_SIZE_MULT_1, value);
1282 if (ret) {
1283 fprintf(stderr, "Could not write 0x%02x to "
1284 "EXT_CSD[%d] in %s\n", value,
1285 EXT_CSD_ENH_SIZE_MULT_1, device);
1286 exit(1);
1287 }
1288 value = enh_size_mult & 0xff;
1289 ret = write_extcsd_value(fd, EXT_CSD_ENH_SIZE_MULT_0, value);
1290 if (ret) {
1291 fprintf(stderr, "Could not write 0x%02x to "
1292 "EXT_CSD[%d] in %s\n", value,
1293 EXT_CSD_ENH_SIZE_MULT_0, device);
1294 exit(1);
1295 }
Balaji T K1fdb7f92015-04-29 18:12:32 -04001296 value = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] | EXT_CSD_ENH_USR;
1297 ret = write_extcsd_value(fd, EXT_CSD_PARTITIONS_ATTRIBUTE, value);
Ben Gardinerd91d3692013-05-30 17:12:51 -04001298 if (ret) {
1299 fprintf(stderr, "Could not write EXT_CSD_ENH_USR to "
1300 "EXT_CSD[%d] in %s\n",
1301 EXT_CSD_PARTITIONS_ATTRIBUTE, device);
1302 exit(1);
1303 }
1304
Balaji T K1fdb7f92015-04-29 18:12:32 -04001305 ret = check_enhanced_area_total_limit(device, fd);
1306 if (ret)
1307 exit(1);
1308
Ben Gardinere6e84e92013-09-19 11:14:27 -04001309 printf("Done setting ENH_USR area on %s\n", device);
Ben Gardinerd91d3692013-05-30 17:12:51 -04001310
Tomas Melin20379fa2016-08-29 12:02:28 -04001311 if (set_partitioning_setting_completed(dry_run, device, fd))
Ben Gardinerd91d3692013-05-30 17:12:51 -04001312 exit(1);
Ben Gardinerd91d3692013-05-30 17:12:51 -04001313
1314 return 0;
1315}
1316
Ben Gardiner196d0d22013-09-19 11:14:29 -04001317int do_write_reliability_set(int nargs, char **argv)
1318{
1319 __u8 value;
Nick Sanders9d57aa72014-03-05 21:38:54 -08001320 __u8 ext_csd[EXT_CSD_SIZE];
Ben Gardiner196d0d22013-09-19 11:14:29 -04001321 int fd, ret;
1322
1323 int dry_run = 1;
1324 int partition;
1325 char *device;
1326
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01001327 if (nargs != 4) {
1328 fprintf(stderr,"Usage: mmc write_reliability set <-y|-n|-c> <partition> </path/to/mmcblkX>\n");
1329 exit(1);
1330 }
Ben Gardiner196d0d22013-09-19 11:14:29 -04001331
Tomas Melin77c22a82016-09-01 11:49:01 -04001332 if (!strcmp("-y", argv[1])) {
Ben Gardiner196d0d22013-09-19 11:14:29 -04001333 dry_run = 0;
Tomas Melin77c22a82016-09-01 11:49:01 -04001334 } else if (!strcmp("-c", argv[1])) {
1335 dry_run = 2;
1336 }
Ben Gardiner196d0d22013-09-19 11:14:29 -04001337
1338 partition = strtol(argv[2], NULL, 10);
1339 device = argv[3];
1340
1341 fd = open(device, O_RDWR);
1342 if (fd < 0) {
1343 perror("open");
1344 exit(1);
1345 }
1346
1347 ret = read_extcsd(fd, ext_csd);
1348 if (ret) {
1349 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
1350 exit(1);
1351 }
1352
1353 /* assert not PARTITION_SETTING_COMPLETED */
1354 if (ext_csd[EXT_CSD_PARTITION_SETTING_COMPLETED])
1355 {
1356 printf(" Device is already partitioned\n");
1357 exit(1);
1358 }
1359
1360 /* assert HS_CTRL_REL */
1361 if (!(ext_csd[EXT_CSD_WR_REL_PARAM] & HS_CTRL_REL)) {
1362 printf("Cannot set write reliability parameters, WR_REL_SET is "
1363 "read-only\n");
1364 exit(1);
1365 }
1366
1367 value = ext_csd[EXT_CSD_WR_REL_SET] | (1<<partition);
1368 ret = write_extcsd_value(fd, EXT_CSD_WR_REL_SET, value);
1369 if (ret) {
1370 fprintf(stderr, "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
1371 value, EXT_CSD_WR_REL_SET, device);
1372 exit(1);
1373 }
1374
1375 printf("Done setting EXT_CSD_WR_REL_SET to 0x%02x on %s\n",
1376 value, device);
1377
Tomas Melin20379fa2016-08-29 12:02:28 -04001378 if (set_partitioning_setting_completed(dry_run, device, fd))
Ben Gardiner196d0d22013-09-19 11:14:29 -04001379 exit(1);
1380
1381 return 0;
1382}
1383
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -05001384int do_read_extcsd(int nargs, char **argv)
1385{
Nick Sanders9d57aa72014-03-05 21:38:54 -08001386 __u8 ext_csd[EXT_CSD_SIZE], ext_csd_rev, reg;
Oliver Metz11f2cea2013-09-23 08:40:52 +02001387 __u32 regl;
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -05001388 int fd, ret;
1389 char *device;
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001390 const char *str;
Gwendal Grignoueb1cd012015-01-08 15:34:55 -08001391 const char *ver_str[] = {
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001392 "4.0", /* 0 */
1393 "4.1", /* 1 */
1394 "4.2", /* 2 */
1395 "4.3", /* 3 */
1396 "Obsolete", /* 4 */
1397 "4.41", /* 5 */
1398 "4.5", /* 6 */
1399 "5.0", /* 7 */
Puthikorn Voravootivatc384aec2015-04-28 11:28:41 -07001400 "5.1", /* 8 */
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001401 };
1402 int boot_access;
1403 const char* boot_access_str[] = {
1404 "No access to boot partition", /* 0 */
1405 "R/W Boot Partition 1", /* 1 */
1406 "R/W Boot Partition 2", /* 2 */
1407 "R/W Replay Protected Memory Block (RPMB)", /* 3 */
1408 };
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -05001409
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01001410 if (nargs != 2) {
1411 fprintf(stderr, "Usage: mmc extcsd read </path/to/mmcblkX>\n");
1412 exit(1);
1413 }
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -05001414
1415 device = argv[1];
1416
1417 fd = open(device, O_RDWR);
1418 if (fd < 0) {
1419 perror("open");
1420 exit(1);
1421 }
1422
1423 ret = read_extcsd(fd, ext_csd);
1424 if (ret) {
1425 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
1426 exit(1);
1427 }
1428
Al Cooper786418c2015-04-29 18:12:35 -04001429 ext_csd_rev = ext_csd[EXT_CSD_REV];
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001430
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001431 if ((ext_csd_rev < sizeof(ver_str)/sizeof(char*)) &&
1432 (ext_csd_rev != 4))
1433 str = ver_str[ext_csd_rev];
1434 else
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001435 goto out_free;
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001436
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001437 printf("=============================================\n");
1438 printf(" Extended CSD rev 1.%d (MMC %s)\n", ext_csd_rev, str);
1439 printf("=============================================\n\n");
1440
1441 if (ext_csd_rev < 3)
1442 goto out_free; /* No ext_csd */
1443
1444 /* Parse the Extended CSD registers.
1445 * Reserved bit should be read as "0" in case of spec older
1446 * than A441.
1447 */
1448 reg = ext_csd[EXT_CSD_S_CMD_SET];
1449 printf("Card Supported Command sets [S_CMD_SET: 0x%02x]\n", reg);
1450 if (!reg)
Chris Ballb9c7a172012-02-20 12:34:25 -05001451 printf(" - Standard MMC command sets\n");
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001452
1453 reg = ext_csd[EXT_CSD_HPI_FEATURE];
1454 printf("HPI Features [HPI_FEATURE: 0x%02x]: ", reg);
1455 if (reg & EXT_CSD_HPI_SUPP) {
1456 if (reg & EXT_CSD_HPI_IMPL)
Chris Ballb9c7a172012-02-20 12:34:25 -05001457 printf("implementation based on CMD12\n");
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001458 else
1459 printf("implementation based on CMD13\n");
1460 }
1461
1462 printf("Background operations support [BKOPS_SUPPORT: 0x%02x]\n",
1463 ext_csd[502]);
1464
1465 if (ext_csd_rev >= 6) {
1466 printf("Max Packet Read Cmd [MAX_PACKED_READS: 0x%02x]\n",
1467 ext_csd[501]);
1468 printf("Max Packet Write Cmd [MAX_PACKED_WRITES: 0x%02x]\n",
1469 ext_csd[500]);
1470 printf("Data TAG support [DATA_TAG_SUPPORT: 0x%02x]\n",
1471 ext_csd[499]);
1472
1473 printf("Data TAG Unit Size [TAG_UNIT_SIZE: 0x%02x]\n",
1474 ext_csd[498]);
1475 printf("Tag Resources Size [TAG_RES_SIZE: 0x%02x]\n",
1476 ext_csd[497]);
1477 printf("Context Management Capabilities"
1478 " [CONTEXT_CAPABILITIES: 0x%02x]\n", ext_csd[496]);
1479 printf("Large Unit Size [LARGE_UNIT_SIZE_M1: 0x%02x]\n",
1480 ext_csd[495]);
1481 printf("Extended partition attribute support"
1482 " [EXT_SUPPORT: 0x%02x]\n", ext_csd[494]);
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001483 }
1484 if (ext_csd_rev >= 7) {
1485 int j;
1486 int eol_info;
1487 char* eol_info_str[] = {
1488 "Not Defined", /* 0 */
1489 "Normal", /* 1 */
1490 "Warning", /* 2 */
1491 "Urgent", /* 3 */
1492 };
1493
1494 printf("Supported modes [SUPPORTED_MODES: 0x%02x]\n",
1495 ext_csd[493]);
1496 printf("FFU features [FFU_FEATURES: 0x%02x]\n",
1497 ext_csd[492]);
1498 printf("Operation codes timeout"
1499 " [OPERATION_CODE_TIMEOUT: 0x%02x]\n",
1500 ext_csd[491]);
1501 printf("FFU Argument [FFU_ARG: 0x%08x]\n",
1502 get_word_from_ext_csd(&ext_csd[487]));
1503 printf("Number of FW sectors correctly programmed"
1504 " [NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED: %d]\n",
1505 get_word_from_ext_csd(&ext_csd[302]));
1506 printf("Vendor proprietary health report:\n");
1507 for (j = 301; j >= 270; j--)
1508 printf("[VENDOR_PROPRIETARY_HEALTH_REPORT[%d]]:"
1509 " 0x%02x\n", j, ext_csd[j]);
1510 for (j = 269; j >= 268; j--) {
1511 __u8 life_used=ext_csd[j];
Puthikorn Voravootivat6bb37ea2014-03-03 17:55:51 -08001512 char est_type = 'B' + (j - 269);
1513 printf("Device life time estimation type %c"
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001514 " [DEVICE_LIFE_TIME_EST_TYP_%c: 0x%02x]\n",
Puthikorn Voravootivat6bb37ea2014-03-03 17:55:51 -08001515 est_type, est_type, life_used);
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001516 if (life_used >= 0x1 && life_used <= 0xa)
1517 printf(" i.e. %d%% - %d%% device life time"
1518 " used\n",
1519 (life_used - 1) * 10, life_used * 10);
1520 else if (life_used == 0xb)
1521 printf(" i.e. Exceeded its maximum estimated"
1522 " device life time\n");
1523 }
1524 eol_info = ext_csd[267];
1525 printf("Pre EOL information [PRE_EOL_INFO: 0x%02x]\n",
1526 eol_info);
1527 if (eol_info < sizeof(eol_info_str)/sizeof(char*))
1528 printf(" i.e. %s\n", eol_info_str[eol_info]);
1529 else
1530 printf(" i.e. Reserved\n");
1531
1532 printf("Optimal read size [OPTIMAL_READ_SIZE: 0x%02x]\n",
1533 ext_csd[266]);
1534 printf("Optimal write size [OPTIMAL_WRITE_SIZE: 0x%02x]\n",
1535 ext_csd[265]);
1536 printf("Optimal trim unit size"
1537 " [OPTIMAL_TRIM_UNIT_SIZE: 0x%02x]\n", ext_csd[264]);
1538 printf("Device version [DEVICE_VERSION: 0x%02x - 0x%02x]\n",
1539 ext_csd[263], ext_csd[262]);
1540 printf("Firmware version:\n");
1541 for (j = 261; j >= 254; j--)
1542 printf("[FIRMWARE_VERSION[%d]]:"
1543 " 0x%02x\n", j, ext_csd[j]);
1544
1545 printf("Power class for 200MHz, DDR at VCC= 3.6V"
1546 " [PWR_CL_DDR_200_360: 0x%02x]\n", ext_csd[253]);
1547 }
1548 if (ext_csd_rev >= 6) {
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001549 printf("Generic CMD6 Timer [GENERIC_CMD6_TIME: 0x%02x]\n",
1550 ext_csd[248]);
1551 printf("Power off notification [POWER_OFF_LONG_TIME: 0x%02x]\n",
1552 ext_csd[247]);
1553 printf("Cache Size [CACHE_SIZE] is %d KiB\n",
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001554 get_word_from_ext_csd(&ext_csd[249]));
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001555 }
1556
1557 /* A441: Reserved [501:247]
1558 A43: reserved [246:229] */
1559 if (ext_csd_rev >= 5) {
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001560 printf("Background operations status"
Chris Ballb9c7a172012-02-20 12:34:25 -05001561 " [BKOPS_STATUS: 0x%02x]\n", ext_csd[246]);
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001562
1563 /* CORRECTLY_PRG_SECTORS_NUM [245:242] TODO */
1564
1565 printf("1st Initialisation Time after programmed sector"
1566 " [INI_TIMEOUT_AP: 0x%02x]\n", ext_csd[241]);
1567
1568 /* A441: reserved [240] */
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001569 printf("Power class for 52MHz, DDR at 3.6V"
1570 " [PWR_CL_DDR_52_360: 0x%02x]\n", ext_csd[239]);
1571 printf("Power class for 52MHz, DDR at 1.95V"
1572 " [PWR_CL_DDR_52_195: 0x%02x]\n", ext_csd[238]);
1573
1574 /* A441: reserved [237-236] */
1575
1576 if (ext_csd_rev >= 6) {
1577 printf("Power class for 200MHz at 3.6V"
1578 " [PWR_CL_200_360: 0x%02x]\n", ext_csd[237]);
1579 printf("Power class for 200MHz, at 1.95V"
1580 " [PWR_CL_200_195: 0x%02x]\n", ext_csd[236]);
1581 }
Chris Ballb9c7a172012-02-20 12:34:25 -05001582 printf("Minimum Performance for 8bit at 52MHz in DDR mode:\n");
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001583 printf(" [MIN_PERF_DDR_W_8_52: 0x%02x]\n", ext_csd[235]);
1584 printf(" [MIN_PERF_DDR_R_8_52: 0x%02x]\n", ext_csd[234]);
1585 /* A441: reserved [233] */
1586 printf("TRIM Multiplier [TRIM_MULT: 0x%02x]\n", ext_csd[232]);
1587 printf("Secure Feature support [SEC_FEATURE_SUPPORT: 0x%02x]\n",
1588 ext_csd[231]);
1589 }
1590 if (ext_csd_rev == 5) { /* Obsolete in 4.5 */
1591 printf("Secure Erase Multiplier [SEC_ERASE_MULT: 0x%02x]\n",
1592 ext_csd[230]);
1593 printf("Secure TRIM Multiplier [SEC_TRIM_MULT: 0x%02x]\n",
1594 ext_csd[229]);
1595 }
1596 reg = ext_csd[EXT_CSD_BOOT_INFO];
1597 printf("Boot Information [BOOT_INFO: 0x%02x]\n", reg);
1598 if (reg & EXT_CSD_BOOT_INFO_ALT)
1599 printf(" Device supports alternative boot method\n");
1600 if (reg & EXT_CSD_BOOT_INFO_DDR_DDR)
1601 printf(" Device supports dual data rate during boot\n");
1602 if (reg & EXT_CSD_BOOT_INFO_HS_MODE)
1603 printf(" Device supports high speed timing during boot\n");
1604
1605 /* A441/A43: reserved [227] */
1606 printf("Boot partition size [BOOT_SIZE_MULTI: 0x%02x]\n", ext_csd[226]);
1607 printf("Access size [ACC_SIZE: 0x%02x]\n", ext_csd[225]);
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001608
1609 reg = get_hc_erase_grp_size(ext_csd);
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001610 printf("High-capacity erase unit size [HC_ERASE_GRP_SIZE: 0x%02x]\n",
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001611 reg);
1612 printf(" i.e. %u KiB\n", 512 * reg);
1613
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001614 printf("High-capacity erase timeout [ERASE_TIMEOUT_MULT: 0x%02x]\n",
1615 ext_csd[223]);
1616 printf("Reliable write sector count [REL_WR_SEC_C: 0x%02x]\n",
1617 ext_csd[222]);
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001618
1619 reg = get_hc_wp_grp_size(ext_csd);
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001620 printf("High-capacity W protect group size [HC_WP_GRP_SIZE: 0x%02x]\n",
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001621 reg);
1622 printf(" i.e. %lu KiB\n", 512l * get_hc_erase_grp_size(ext_csd) * reg);
1623
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001624 printf("Sleep current (VCC) [S_C_VCC: 0x%02x]\n", ext_csd[220]);
1625 printf("Sleep current (VCCQ) [S_C_VCCQ: 0x%02x]\n", ext_csd[219]);
1626 /* A441/A43: reserved [218] */
1627 printf("Sleep/awake timeout [S_A_TIMEOUT: 0x%02x]\n", ext_csd[217]);
1628 /* A441/A43: reserved [216] */
Ben Gardiner4e850232013-05-30 17:12:49 -04001629
1630 unsigned int sectors = get_sector_count(ext_csd);
1631 printf("Sector Count [SEC_COUNT: 0x%08x]\n", sectors);
1632 if (is_blockaddresed(ext_csd))
1633 printf(" Device is block-addressed\n");
1634 else
1635 printf(" Device is NOT block-addressed\n");
1636
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001637 /* A441/A43: reserved [211] */
1638 printf("Minimum Write Performance for 8bit:\n");
1639 printf(" [MIN_PERF_W_8_52: 0x%02x]\n", ext_csd[210]);
1640 printf(" [MIN_PERF_R_8_52: 0x%02x]\n", ext_csd[209]);
1641 printf(" [MIN_PERF_W_8_26_4_52: 0x%02x]\n", ext_csd[208]);
1642 printf(" [MIN_PERF_R_8_26_4_52: 0x%02x]\n", ext_csd[207]);
1643 printf("Minimum Write Performance for 4bit:\n");
1644 printf(" [MIN_PERF_W_4_26: 0x%02x]\n", ext_csd[206]);
1645 printf(" [MIN_PERF_R_4_26: 0x%02x]\n", ext_csd[205]);
1646 /* A441/A43: reserved [204] */
1647 printf("Power classes registers:\n");
1648 printf(" [PWR_CL_26_360: 0x%02x]\n", ext_csd[203]);
1649 printf(" [PWR_CL_52_360: 0x%02x]\n", ext_csd[202]);
1650 printf(" [PWR_CL_26_195: 0x%02x]\n", ext_csd[201]);
1651 printf(" [PWR_CL_52_195: 0x%02x]\n", ext_csd[200]);
1652
1653 /* A43: reserved [199:198] */
1654 if (ext_csd_rev >= 5) {
1655 printf("Partition switching timing "
1656 "[PARTITION_SWITCH_TIME: 0x%02x]\n", ext_csd[199]);
1657 printf("Out-of-interrupt busy timing"
1658 " [OUT_OF_INTERRUPT_TIME: 0x%02x]\n", ext_csd[198]);
1659 }
1660
1661 /* A441/A43: reserved [197] [195] [193] [190] [188]
1662 * [186] [184] [182] [180] [176] */
1663
1664 if (ext_csd_rev >= 6)
1665 printf("I/O Driver Strength [DRIVER_STRENGTH: 0x%02x]\n",
1666 ext_csd[197]);
1667
Oleg Matcovschi64f63a32013-05-23 17:11:07 -07001668 /* DEVICE_TYPE in A45, CARD_TYPE in A441 */
Gwendal Grignouc2faa3d2015-04-28 10:00:45 -07001669 printf("Card Type [CARD_TYPE: 0x%02x - %02x]\n",
1670 ext_csd[196], ext_csd[195]);
1671 reg = ext_csd[195];
1672 if (reg & 0x02) printf(" HS533 Dual Data Rate eMMC @266MHz 1.2VI/O\n");
1673 if (reg & 0x01) printf(" HS533 Dual Data Rate eMMC @266MHz 1.8VI/O\n");
Oleg Matcovschi64f63a32013-05-23 17:11:07 -07001674 reg = ext_csd[196];
Gwendal Grignouc2faa3d2015-04-28 10:00:45 -07001675 if (reg & 0x80) printf(" HS400 Dual Data Rate eMMC @200MHz 1.2VI/O\n");
1676 if (reg & 0x40) printf(" HS400 Dual Data Rate eMMC @200MHz 1.8VI/O\n");
Oleg Matcovschi64f63a32013-05-23 17:11:07 -07001677 if (reg & 0x20) printf(" HS200 Single Data Rate eMMC @200MHz 1.2VI/O\n");
1678 if (reg & 0x10) printf(" HS200 Single Data Rate eMMC @200MHz 1.8VI/O\n");
1679 if (reg & 0x08) printf(" HS Dual Data Rate eMMC @52MHz 1.2VI/O\n");
1680 if (reg & 0x04) printf(" HS Dual Data Rate eMMC @52MHz 1.8V or 3VI/O\n");
1681 if (reg & 0x02) printf(" HS eMMC @52MHz - at rated device voltage(s)\n");
1682 if (reg & 0x01) printf(" HS eMMC @26MHz - at rated device voltage(s)\n");
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001683
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001684 printf("CSD structure version [CSD_STRUCTURE: 0x%02x]\n", ext_csd[194]);
Al Cooper786418c2015-04-29 18:12:35 -04001685 /* ext_csd_rev = ext_csd[EXT_CSD_REV] (already done!!!) */
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001686 printf("Command set [CMD_SET: 0x%02x]\n", ext_csd[191]);
1687 printf("Command set revision [CMD_SET_REV: 0x%02x]\n", ext_csd[189]);
1688 printf("Power class [POWER_CLASS: 0x%02x]\n", ext_csd[187]);
1689 printf("High-speed interface timing [HS_TIMING: 0x%02x]\n",
1690 ext_csd[185]);
1691 /* bus_width: ext_csd[183] not readable */
1692 printf("Erased memory content [ERASED_MEM_CONT: 0x%02x]\n",
1693 ext_csd[181]);
1694 reg = ext_csd[EXT_CSD_BOOT_CFG];
1695 printf("Boot configuration bytes [PARTITION_CONFIG: 0x%02x]\n", reg);
Mario Schuknecht8c0c40d2013-05-15 08:28:04 +02001696 switch ((reg & EXT_CSD_BOOT_CFG_EN)>>3) {
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001697 case 0x0:
1698 printf(" Not boot enable\n");
1699 break;
1700 case 0x1:
1701 printf(" Boot Partition 1 enabled\n");
1702 break;
1703 case 0x2:
1704 printf(" Boot Partition 2 enabled\n");
1705 break;
1706 case 0x7:
1707 printf(" User Area Enabled for boot\n");
1708 break;
1709 }
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001710 boot_access = reg & EXT_CSD_BOOT_CFG_ACC;
1711 if (boot_access < sizeof(boot_access_str) / sizeof(char*))
1712 printf(" %s\n", boot_access_str[boot_access]);
1713 else
Mario Schuknecht8c0c40d2013-05-15 08:28:04 +02001714 printf(" Access to General Purpose partition %d\n",
Gwendal Grignou9b8d99c2014-01-28 13:48:05 -08001715 boot_access - 3);
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001716
1717 printf("Boot config protection [BOOT_CONFIG_PROT: 0x%02x]\n",
1718 ext_csd[178]);
1719 printf("Boot bus Conditions [BOOT_BUS_CONDITIONS: 0x%02x]\n",
1720 ext_csd[177]);
1721 printf("High-density erase group definition"
Ben Gardinerd91d3692013-05-30 17:12:51 -04001722 " [ERASE_GROUP_DEF: 0x%02x]\n", ext_csd[EXT_CSD_ERASE_GROUP_DEF]);
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001723
Al Cooper1b7f5d72016-06-07 16:35:46 -04001724 print_writeprotect_boot_status(ext_csd);
Chris Ballb9c7a172012-02-20 12:34:25 -05001725
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001726 if (ext_csd_rev >= 5) {
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001727 /* A441]: reserved [172] */
1728 printf("User area write protection register"
1729 " [USER_WP]: 0x%02x\n", ext_csd[171]);
1730 /* A441]: reserved [170] */
1731 printf("FW configuration [FW_CONFIG]: 0x%02x\n", ext_csd[169]);
1732 printf("RPMB Size [RPMB_SIZE_MULT]: 0x%02x\n", ext_csd[168]);
Ben Gardiner4da1c0d2013-09-19 11:14:28 -04001733
1734 reg = ext_csd[EXT_CSD_WR_REL_SET];
1735 const char * const fast = "existing data is at risk if a power "
1736 "failure occurs during a write operation";
1737 const char * const reliable = "the device protects existing "
1738 "data if a power failure occurs during a write "
1739 "operation";
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001740 printf("Write reliability setting register"
Ben Gardiner4da1c0d2013-09-19 11:14:28 -04001741 " [WR_REL_SET]: 0x%02x\n", reg);
1742
1743 printf(" user area: %s\n", reg & (1<<0) ? reliable : fast);
1744 int i;
1745 for (i = 1; i <= 4; i++) {
1746 printf(" partition %d: %s\n", i,
1747 reg & (1<<i) ? reliable : fast);
1748 }
1749
1750 reg = ext_csd[EXT_CSD_WR_REL_PARAM];
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001751 printf("Write reliability parameter register"
Ben Gardiner4da1c0d2013-09-19 11:14:28 -04001752 " [WR_REL_PARAM]: 0x%02x\n", reg);
1753 if (reg & 0x01)
1754 printf(" Device supports writing EXT_CSD_WR_REL_SET\n");
1755 if (reg & 0x04)
1756 printf(" Device supports the enhanced def. of reliable "
1757 "write\n");
1758
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001759 /* sanitize_start ext_csd[165]]: not readable
1760 * bkops_start ext_csd[164]]: only writable */
1761 printf("Enable background operations handshake"
1762 " [BKOPS_EN]: 0x%02x\n", ext_csd[163]);
1763 printf("H/W reset function"
1764 " [RST_N_FUNCTION]: 0x%02x\n", ext_csd[162]);
1765 printf("HPI management [HPI_MGMT]: 0x%02x\n", ext_csd[161]);
Ben Gardiner82bd9502013-06-27 11:04:10 -04001766 reg = ext_csd[EXT_CSD_PARTITIONING_SUPPORT];
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001767 printf("Partitioning Support [PARTITIONING_SUPPORT]: 0x%02x\n",
1768 reg);
Ben Gardiner82bd9502013-06-27 11:04:10 -04001769 if (reg & EXT_CSD_PARTITIONING_EN)
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001770 printf(" Device support partitioning feature\n");
1771 else
1772 printf(" Device NOT support partitioning feature\n");
Ben Gardiner82bd9502013-06-27 11:04:10 -04001773 if (reg & EXT_CSD_ENH_ATTRIBUTE_EN)
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001774 printf(" Device can have enhanced tech.\n");
1775 else
1776 printf(" Device cannot have enhanced tech.\n");
1777
Oliver Metz11f2cea2013-09-23 08:40:52 +02001778 regl = (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_2] << 16) |
Oliver Metz22f26412013-09-23 08:40:51 +02001779 (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_1] << 8) |
1780 ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_0];
1781
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001782 printf("Max Enhanced Area Size [MAX_ENH_SIZE_MULT]: 0x%06x\n",
Oliver Metz11f2cea2013-09-23 08:40:52 +02001783 regl);
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001784 unsigned int wp_sz = get_hc_wp_grp_size(ext_csd);
1785 unsigned int erase_sz = get_hc_erase_grp_size(ext_csd);
Oliver Metz11f2cea2013-09-23 08:40:52 +02001786 printf(" i.e. %lu KiB\n", 512l * regl * wp_sz * erase_sz);
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001787
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001788 printf("Partitions attribute [PARTITIONS_ATTRIBUTE]: 0x%02x\n",
Ben Gardinerd91d3692013-05-30 17:12:51 -04001789 ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE]);
Ben Gardinera6cd98d2013-05-30 17:12:46 -04001790 reg = ext_csd[EXT_CSD_PARTITION_SETTING_COMPLETED];
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001791 printf("Partitioning Setting"
1792 " [PARTITION_SETTING_COMPLETED]: 0x%02x\n",
Ben Gardinera6cd98d2013-05-30 17:12:46 -04001793 reg);
1794 if (reg)
1795 printf(" Device partition setting complete\n");
1796 else
1797 printf(" Device partition setting NOT complete\n");
1798
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001799 printf("General Purpose Partition Size\n"
1800 " [GP_SIZE_MULT_4]: 0x%06x\n", (ext_csd[154] << 16) |
1801 (ext_csd[153] << 8) | ext_csd[152]);
1802 printf(" [GP_SIZE_MULT_3]: 0x%06x\n", (ext_csd[151] << 16) |
1803 (ext_csd[150] << 8) | ext_csd[149]);
1804 printf(" [GP_SIZE_MULT_2]: 0x%06x\n", (ext_csd[148] << 16) |
1805 (ext_csd[147] << 8) | ext_csd[146]);
1806 printf(" [GP_SIZE_MULT_1]: 0x%06x\n", (ext_csd[145] << 16) |
1807 (ext_csd[144] << 8) | ext_csd[143]);
1808
Oliver Metz11f2cea2013-09-23 08:40:52 +02001809 regl = (ext_csd[EXT_CSD_ENH_SIZE_MULT_2] << 16) |
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001810 (ext_csd[EXT_CSD_ENH_SIZE_MULT_1] << 8) |
1811 ext_csd[EXT_CSD_ENH_SIZE_MULT_0];
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001812 printf("Enhanced User Data Area Size"
Oliver Metz11f2cea2013-09-23 08:40:52 +02001813 " [ENH_SIZE_MULT]: 0x%06x\n", regl);
1814 printf(" i.e. %lu KiB\n", 512l * regl *
Ben Gardinerf82e27a2013-05-30 17:12:50 -04001815 get_hc_erase_grp_size(ext_csd) *
1816 get_hc_wp_grp_size(ext_csd));
Ben Gardiner68f490b2013-05-30 17:12:48 -04001817
Oliver Metz11f2cea2013-09-23 08:40:52 +02001818 regl = (ext_csd[EXT_CSD_ENH_START_ADDR_3] << 24) |
Ben Gardiner68f490b2013-05-30 17:12:48 -04001819 (ext_csd[EXT_CSD_ENH_START_ADDR_2] << 16) |
1820 (ext_csd[EXT_CSD_ENH_START_ADDR_1] << 8) |
1821 ext_csd[EXT_CSD_ENH_START_ADDR_0];
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001822 printf("Enhanced User Data Start Address"
jason.zeng1c553062018-03-27 02:17:10 -04001823 " [ENH_START_ADDR]: 0x%08x\n", regl);
1824 printf(" i.e. %llu bytes offset\n", (is_blockaddresed(ext_csd) ?
1825 512ll : 1ll) * regl);
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001826
1827 /* A441]: reserved [135] */
1828 printf("Bad Block Management mode"
1829 " [SEC_BAD_BLK_MGMNT]: 0x%02x\n", ext_csd[134]);
1830 /* A441: reserved [133:0] */
1831 }
1832 /* B45 */
1833 if (ext_csd_rev >= 6) {
1834 int j;
1835 /* tcase_support ext_csd[132] not readable */
1836 printf("Periodic Wake-up [PERIODIC_WAKEUP]: 0x%02x\n",
1837 ext_csd[131]);
1838 printf("Program CID/CSD in DDR mode support"
1839 " [PROGRAM_CID_CSD_DDR_SUPPORT]: 0x%02x\n",
1840 ext_csd[130]);
1841
1842 for (j = 127; j >= 64; j--)
1843 printf("Vendor Specific Fields"
1844 " [VENDOR_SPECIFIC_FIELD[%d]]: 0x%02x\n",
1845 j, ext_csd[j]);
1846
Gwendal Grignoue966e672014-07-07 14:03:13 -07001847 reg = ext_csd[63];
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001848 printf("Native sector size [NATIVE_SECTOR_SIZE]: 0x%02x\n",
Gwendal Grignoue966e672014-07-07 14:03:13 -07001849 reg);
1850 if (reg == 0x00)
1851 printf(" i.e. 512 B\n");
1852 else if (reg == 0x01)
1853 printf(" i.e. 4 KiB\n");
1854 else
1855 printf(" i.e. Reserved\n");
1856
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001857 printf("Sector size emulation [USE_NATIVE_SECTOR]: 0x%02x\n",
1858 ext_csd[62]);
Gwendal Grignoue966e672014-07-07 14:03:13 -07001859 reg = ext_csd[61];
1860 printf("Sector size [DATA_SECTOR_SIZE]: 0x%02x\n", reg);
1861 if (reg == 0x00)
1862 printf(" i.e. 512 B\n");
1863 else if (reg == 0x01)
1864 printf(" i.e. 4 KiB\n");
1865 else
1866 printf(" i.e. Reserved\n");
1867
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001868 printf("1st initialization after disabling sector"
1869 " size emulation [INI_TIMEOUT_EMU]: 0x%02x\n",
1870 ext_csd[60]);
1871 printf("Class 6 commands control [CLASS_6_CTRL]: 0x%02x\n",
1872 ext_csd[59]);
1873 printf("Number of addressed group to be Released"
1874 "[DYNCAP_NEEDED]: 0x%02x\n", ext_csd[58]);
1875 printf("Exception events control"
1876 " [EXCEPTION_EVENTS_CTRL]: 0x%04x\n",
1877 (ext_csd[57] << 8) | ext_csd[56]);
1878 printf("Exception events status"
1879 "[EXCEPTION_EVENTS_STATUS]: 0x%04x\n",
1880 (ext_csd[55] << 8) | ext_csd[54]);
1881 printf("Extended Partitions Attribute"
1882 " [EXT_PARTITIONS_ATTRIBUTE]: 0x%04x\n",
1883 (ext_csd[53] << 8) | ext_csd[52]);
1884
1885 for (j = 51; j >= 37; j--)
1886 printf("Context configuration"
1887 " [CONTEXT_CONF[%d]]: 0x%02x\n", j, ext_csd[j]);
1888
1889 printf("Packed command status"
1890 " [PACKED_COMMAND_STATUS]: 0x%02x\n", ext_csd[36]);
1891 printf("Packed command failure index"
1892 " [PACKED_FAILURE_INDEX]: 0x%02x\n", ext_csd[35]);
1893 printf("Power Off Notification"
1894 " [POWER_OFF_NOTIFICATION]: 0x%02x\n", ext_csd[34]);
Oleg Matcovschi64f63a32013-05-23 17:11:07 -07001895 printf("Control to turn the Cache ON/OFF"
1896 " [CACHE_CTRL]: 0x%02x\n", ext_csd[33]);
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001897 /* flush_cache ext_csd[32] not readable */
1898 /*Reserved [31:0] */
1899 }
Gwendal Grignoue966e672014-07-07 14:03:13 -07001900 if (ext_csd_rev >= 7) {
1901 printf("Mode config [MODE_CONFIG: 0x%02x]\n", ext_csd[30]);
1902 printf("Mode operation codes [MODE_OPERATION_CODES: 0x%02x]\n",
1903 ext_csd[29]);
1904
1905 reg = ext_csd[26];
1906 printf("FFU status [FFU_STATUS: 0x%02x]\n", reg);
1907 switch (reg) {
1908 case 0x00:
1909 printf(" Success\n");
1910 break;
1911 case 0x10:
1912 printf(" General error\n");
1913 break;
1914 case 0x11:
1915 printf(" Firmware install error\n");
1916 break;
1917 case 0x12:
1918 printf(" Error in downloading firmware\n");
1919 break;
1920 default:
1921 printf(" Reserved\n");
1922 }
1923 printf("Pre loading data size [PRE_LOADING_DATA_SIZE] is"
1924 " %d sector size\n",
1925 get_word_from_ext_csd(&ext_csd[22]));
1926 printf("Max pre loading data size [MAX_PRE_LOADING_DATA_SIZE] is"
1927 " %d sector size\n",
1928 get_word_from_ext_csd(&ext_csd[18]));
1929 printf("Product state awareness enablement"
1930 " [PRODUCT_STATE_AWARENESS_ENABLEMENT: 0x%02x]\n",
1931 ext_csd[17]);
1932 printf("Secure Removal Type [SECURE_REMOVAL_TYPE: 0x%02x]\n",
1933 ext_csd[16]);
1934 }
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001935
Avi Shchislowskidc7ab962016-03-08 14:22:41 -05001936 if (ext_csd_rev >= 7) {
1937 printf("eMMC Firmware Version: %s\n",
1938 (char*)&ext_csd[EXT_CSD_FIRMWARE_VERSION]);
Boris Schmidtf91b88e2017-03-14 14:03:22 +01001939 printf("eMMC Life Time Estimation A [EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A]: 0x%02x\n",
1940 ext_csd[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A]);
Boris Schmidtf91b88e2017-03-14 14:03:22 +01001941 printf("eMMC Life Time Estimation B [EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B]: 0x%02x\n",
1942 ext_csd[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B]);
Alexander Steincce3d882017-03-20 14:43:00 +01001943 printf("eMMC Pre EOL information [EXT_CSD_PRE_EOL_INFO]: 0x%02x\n",
1944 ext_csd[EXT_CSD_PRE_EOL_INFO]);
1945 }
1946
Adrian Hunterbb1600b2016-06-10 11:28:59 +03001947 if (ext_csd_rev >= 8) {
1948 printf("Command Queue Support [CMDQ_SUPPORT]: 0x%02x\n",
1949 ext_csd[EXT_CSD_CMDQ_SUPPORT]);
1950 printf("Command Queue Depth [CMDQ_DEPTH]: %u\n",
1951 (ext_csd[EXT_CSD_CMDQ_DEPTH] & 0x1f) + 1);
1952 printf("Command Enabled [CMDQ_MODE_EN]: 0x%02x\n",
1953 ext_csd[EXT_CSD_CMDQ_MODE_EN]);
1954 }
Giuseppe CAVALLAROa5bf4a22012-02-20 09:45:29 +01001955out_free:
Johan RUDHOLMa8bfde72012-02-12 11:46:44 -05001956 return ret;
1957}
Yaniv Gardi21bb4732013-05-26 13:25:33 -04001958
Nick Sanders9d57aa72014-03-05 21:38:54 -08001959int do_dump_extcsd(int nargs, char **argv)
1960{
1961 __u8 ext_csd[EXT_CSD_SIZE];
1962 int fd, ret;
1963 char *device;
1964 int i, j;
1965
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01001966 if (nargs != 2) {
1967 fprintf(stderr, "Usage: mmc writeprotect boot get </path/to/mmcblkX>\n");
1968 exit(1);
1969 }
Nick Sanders9d57aa72014-03-05 21:38:54 -08001970 device = argv[1];
1971
1972 fd = open(device, O_RDWR);
1973 if (fd < 0) {
1974 perror("Failed to open mmc device");
1975 exit(1);
1976 }
1977
1978 ret = read_extcsd(fd, ext_csd);
1979 if (ret) {
1980 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
1981 exit(1);
1982 }
1983
1984 /* Dump all bytes so that any undecoded or proprietary registers */
1985 /* can be acessed. */
1986 printf("EXT_CSD binary dump:\n");
1987 for (i = 0; i < EXT_CSD_SIZE; i+= 16) {
1988 printf(" %3d: %3x: ", i, i);
1989 for (j = 0; (j < 16) && (i + j < EXT_CSD_SIZE); j++) {
1990 printf(" %02x", ext_csd[i+j]);
1991 }
1992 printf("\n");
1993 }
1994
1995 return ret;
1996}
1997
Yaniv Gardi21bb4732013-05-26 13:25:33 -04001998int do_sanitize(int nargs, char **argv)
1999{
2000 int fd, ret;
2001 char *device;
2002
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002003 if (nargs != 2) {
2004 fprintf(stderr, "Usage: mmc sanitize </path/to/mmcblkX>\n");
2005 exit(1);
2006 }
Yaniv Gardi21bb4732013-05-26 13:25:33 -04002007
2008 device = argv[1];
2009
2010 fd = open(device, O_RDWR);
2011 if (fd < 0) {
2012 perror("open");
2013 exit(1);
2014 }
2015
2016 ret = write_extcsd_value(fd, EXT_CSD_SANITIZE_START, 1);
2017 if (ret) {
2018 fprintf(stderr, "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
2019 1, EXT_CSD_SANITIZE_START, device);
2020 exit(1);
2021 }
2022
2023 return ret;
2024
2025}
2026
Julius Wernerbcc3e2e2016-04-21 16:53:02 -07002027enum blockprotect_mode {
2028 BLOCKPROTECT_TEMPORARY = 0,
2029 BLOCKPROTECT_POWERON,
2030 BLOCKPROTECT_PERMANENT,
2031};
2032
2033int write_blockprotect(int fd, __u32 sector, int enable)
2034{
2035 struct mmc_ioc_cmd cmd;
2036 int ret;
2037
2038 memset(&cmd, 0, sizeof(cmd));
2039 cmd.write_flag = 1;
2040 cmd.opcode = enable ? MMC_SET_WRITE_PROT : MMC_CLR_WRITE_PROT;
2041 cmd.arg = sector;
2042 cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
2043
2044 ret = ioctl(fd, MMC_IOC_CMD, &cmd);
2045 if (ret)
2046 perror("SET/CLR_WRITE_PROT command");
2047 return ret;
2048}
2049
2050int do_blockprotect_enable(int nargs, char **argv)
2051{
2052 __u8 ext_csd[EXT_CSD_SIZE];
2053 __u8 user_wp;
2054 __u32 sector;
2055 char *end;
2056 int ret, fd;
2057 int arg_index = 0;
2058 enum blockprotect_mode mode = BLOCKPROTECT_TEMPORARY;
2059
2060 if (nargs > 0 && !strcmp(argv[1], "-r")) {
2061 arg_index++;
2062 mode = BLOCKPROTECT_POWERON;
2063 } else if (nargs > 0 && !strcmp(argv[1], "-p")) {
2064 arg_index++;
2065 mode = BLOCKPROTECT_PERMANENT;
2066 }
2067
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002068 if (nargs != 3 + arg_index) {
2069 fprintf(stderr, "Usage: mmc blockprotect enable [-p|-r] <device> <write protect block>\n");
2070 exit(1);
2071 }
Julius Wernerbcc3e2e2016-04-21 16:53:02 -07002072 sector = strtoul(argv[2 + arg_index], &end, 0);
2073 if (*end != '\0') {
2074 fprintf(stderr, "Not a block number: %s\n",
2075 argv[2 + arg_index]);
2076 exit(1);
2077 }
2078
2079 fd = open(argv[1 + arg_index], O_RDWR);
2080 if (fd < 0) {
2081 perror("open");
2082 exit(1);
2083 }
2084
2085 if (read_extcsd(fd, ext_csd))
2086 exit(1);
2087
2088 user_wp = ext_csd[EXT_CSD_USER_WP];
2089 user_wp &= ~(EXT_CSD_US_PERM_WP_EN | EXT_CSD_US_PWR_WP_EN);
2090 if (mode == BLOCKPROTECT_POWERON)
2091 user_wp |= EXT_CSD_US_PWR_WP_EN;
2092 else if (mode == BLOCKPROTECT_PERMANENT)
2093 user_wp |= EXT_CSD_US_PERM_WP_EN;
2094
2095 ret = write_extcsd_value(fd, EXT_CSD_USER_WP, user_wp);
2096 if (ret) {
2097 perror("update EXT_CSD[USER_WP]");
2098 exit(1);
2099 }
2100
2101 usleep(INTER_COMMAND_GAP_US);
2102
2103 ret = write_blockprotect(fd, sector, 1);
2104
2105 usleep(INTER_COMMAND_GAP_US);
2106
2107 user_wp &= ~(EXT_CSD_US_PERM_WP_EN | EXT_CSD_US_PWR_WP_EN);
2108 if (write_extcsd_value(fd, EXT_CSD_USER_WP, user_wp)) {
2109 perror("reset EXT_CSD[USER_WP]");
2110 if (!ret)
2111 ret = -1;
2112 }
2113
2114 return ret;
2115}
2116
2117int do_blockprotect_disable(int nargs, char **argv)
2118{
2119 __u32 sector;
2120 char *end;
2121 int fd;
2122
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002123 if (nargs != 3) {
2124 fprintf(stderr, "Usage: mmc blockprotect disable <device> <write protect block>\n");
2125 exit(1);
2126 }
Julius Wernerbcc3e2e2016-04-21 16:53:02 -07002127 sector = strtoul(argv[2], &end, 0);
2128 if (*end != '\0') {
2129 fprintf(stderr, "Not a block number: %s\n", argv[2]);
2130 exit(1);
2131 }
2132
2133
2134 fd = open(argv[1], O_RDWR);
2135 if (fd < 0) {
2136 perror("open");
2137 exit(1);
2138 }
2139
2140 return write_blockprotect(fd, sector, 0);
2141}
2142
2143int do_blockprotect_read(int nargs, char **argv)
2144{
2145 __u8 wp_bits[8];
2146 __u32 sector;
2147 char *end;
2148 int fd;
2149 struct mmc_ioc_cmd cmd;
2150
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002151 if (nargs != 3) {
2152 fprintf(stderr, "Usage: mmc blockprotect read <device> <write protect block>\n");
2153 exit(1);
2154 }
Julius Wernerbcc3e2e2016-04-21 16:53:02 -07002155 fd = open(argv[1], O_RDWR);
2156 if (fd < 0) {
2157 perror("open");
2158 exit(1);
2159 }
2160
2161 sector = strtoul(argv[2], &end, 0);
2162 if (*end != '\0') {
2163 fprintf(stderr, "Not a block number: %s\n", argv[2]);
2164 exit(1);
2165 }
2166
2167 memset(&cmd, 0, sizeof(cmd));
2168 cmd.write_flag = 0;
2169 cmd.opcode = MMC_SEND_WRITE_PROT_TYPE;
2170 cmd.arg = sector;
2171 cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
2172 cmd.blksz = sizeof(wp_bits);
2173 cmd.blocks = 1;
2174 mmc_ioc_cmd_set_data(cmd, wp_bits);
2175
2176 if (ioctl(fd, MMC_IOC_CMD, &cmd)) {
2177 perror("SEND_WRITE_PROT_TYPE command");
2178 exit(1);
2179 }
2180
2181 printf("Sector %u write protection: ", sector);
2182 switch (wp_bits[7] & 3) {
2183 case 0:
2184 printf("NONE\n");
2185 break;
2186 case 1:
2187 printf("TEMPORARY\n");
2188 break;
2189 case 2:
2190 printf("POWER-ON\n");
2191 break;
2192 case 3:
2193 printf("PERMANENT\n");
2194 break;
2195 }
2196
2197 return 0;
2198}
2199
2200int do_blockprotect_info(int nargs, char **argv)
2201{
2202 __u8 ext_csd[EXT_CSD_SIZE];
2203 __u8 user_wp;
2204 int fd, wp_sz, erase_sz;
2205
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002206 if (nargs != 2) {
2207 fprintf(stderr, "Usage: mmc blockprotect info <device>\n");
2208 exit(1);
2209 }
Julius Wernerbcc3e2e2016-04-21 16:53:02 -07002210 fd = open(argv[1], O_RDWR);
2211 if (fd < 0) {
2212 perror("open");
2213 exit(1);
2214 }
2215
2216 if (read_extcsd(fd, ext_csd))
2217 exit(1);
2218
2219 if (ext_csd[EXT_CSD_CLASS_6_CTRL] != 0) {
2220 fprintf(stderr, "Block protection commands not supported: "
2221 "CLASS_6_CTRL set.\n");
2222 exit(1);
2223 }
2224
2225 if ((ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x1) != 0x1) {
2226 fprintf(stderr, "Block protection commands not supported: "
2227 "high-capacity sizes not enabled.\n");
2228 exit(1);
2229 }
2230
2231 wp_sz = get_hc_wp_grp_size(ext_csd);
2232 erase_sz = get_hc_erase_grp_size(ext_csd);
2233
2234 if (erase_sz == 0 || wp_sz == 0) {
2235 fprintf(stderr, "Block protection commands not supported: "
2236 "no high-capacity size for erase or WP blocks.\n");
2237 exit(1);
2238 }
2239
2240 printf("Write protect block size in sectors: %d\n",
2241 erase_sz * wp_sz * 1024);
2242
2243 user_wp = ext_csd[EXT_CSD_USER_WP];
2244 printf("Permanent write protection: %s\n",
2245 user_wp & EXT_CSD_US_PERM_WP_DIS ? "forbidden" : "allowed");
2246 printf("Power-on write protection: %s\n",
2247 user_wp & EXT_CSD_US_PWR_WP_DIS ? "forbidden" : "allowed");
2248
2249 return 0;
2250}
2251
Gwendal Grignou0da2c512015-01-08 15:36:03 -08002252static const char* const mmc_ffu_hack_names[] = {
2253 [MMC_OVERRIDE_FFU_ARG] = "ffu_arg",
2254};
2255
Gwendal Grignou771984c2014-07-01 12:46:18 -07002256int do_emmc50_ffu (int nargs, char **argv)
2257{
Gwendal Grignou0da2c512015-01-08 15:36:03 -08002258 int fd, ret, i, argc=1, ffu_hack=0;
2259 char *device, *type, *path;
2260 __u64 value;
2261 union {
2262 __u8 data[FFU_DATA_SIZE];
2263 struct mmc_ffu_args ffu_args;
2264 } ffu_data;
2265 struct mmc_ffu_args *ffu_args = &ffu_data.ffu_args;
Gwendal Grignou0f757342014-10-16 16:52:46 -07002266 struct mmc_ioc_cmd mmc_ioc_cmd;
Gwendal Grignou771984c2014-07-01 12:46:18 -07002267
Gwendal Grignou0da2c512015-01-08 15:36:03 -08002268 while (!strcmp("-k", argv[argc])) {
2269 ret = sscanf(argv[++argc], "%m[^:]:0x%llx", &type, &value);
2270 if (ret < 1) {
2271 fprintf(stderr, "Invalid hack: %s\n", argv[argc]);
2272 exit(1);
2273 }
2274 for (i = 0; i < MMC_HACK_LEN; i++) {
2275 if (!strcmp(type, mmc_ffu_hack_names[i])) {
2276 ffu_args->hack[ffu_hack].type = i;
2277 if (ret == 2) {
2278 ffu_args->hack[ffu_hack].value = value;
2279 }
2280 ffu_hack++;
2281 if (ffu_hack * sizeof(struct mmc_ffu_hack) +
2282 sizeof(struct mmc_ffu_args) >
2283 FFU_DATA_SIZE) {
2284 fprintf(stderr, "Too many %d hacks",
2285 ffu_hack);
2286 exit(1);
2287 }
2288 break;
2289 }
2290 }
2291 if (i == MMC_HACK_LEN) {
2292 fprintf(stderr, "Hack type %s not found\n", type);
2293 fprintf(stderr, "Supported types are: ");
2294 for (i = 0; i < MMC_HACK_LEN; i++)
2295 fprintf(stderr, "%s%s", mmc_ffu_hack_names[i],
2296 (i == MMC_HACK_LEN-1 ? "\n": ", "));
Gwendal Grignou771984c2014-07-01 12:46:18 -07002297
Gwendal Grignou0da2c512015-01-08 15:36:03 -08002298 exit(1);
2299 }
2300 free(type);
2301 argc++;
2302 }
2303 ffu_args->hack_nb = ffu_hack;
2304
2305 path = argv[argc++];
2306 if (strlen(path) >= FFU_NAME_LEN) {
Gwendal Grignou771984c2014-07-01 12:46:18 -07002307 fprintf(stderr, "Filename \"%.20s\" too long\n", path);
2308 exit(1);
2309 }
Gwendal Grignou0da2c512015-01-08 15:36:03 -08002310 strcpy(ffu_args->name, path);
2311 device = argv[argc++];
Gwendal Grignou771984c2014-07-01 12:46:18 -07002312 fd = open(device, O_RDWR);
2313 if (fd < 0) {
2314 perror("open");
2315 exit(1);
2316 }
2317
Gwendal Grignou0f757342014-10-16 16:52:46 -07002318 /* prepare and send ioctl */
2319 memset(&mmc_ioc_cmd, 0, sizeof(mmc_ioc_cmd));
2320 mmc_ioc_cmd.opcode = MMC_FFU_INVOKE_OP;
Gwendal Grignou0da2c512015-01-08 15:36:03 -08002321 mmc_ioc_cmd.blksz = FFU_DATA_SIZE;
Gwendal Grignou0f757342014-10-16 16:52:46 -07002322 mmc_ioc_cmd.blocks = 1;
2323 mmc_ioc_cmd.arg = 0;
2324 mmc_ioc_cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
2325 mmc_ioc_cmd.write_flag = 1;
Gwendal Grignou0da2c512015-01-08 15:36:03 -08002326 mmc_ioc_cmd_set_data(mmc_ioc_cmd, ffu_args);
Gwendal Grignou0f757342014-10-16 16:52:46 -07002327 ret = ioctl(fd, MMC_IOC_CMD, &mmc_ioc_cmd);
Gwendal Grignou771984c2014-07-01 12:46:18 -07002328 if (ret) {
2329 fprintf(stderr, "FFU install failed : %s\n", strerror(errno));
2330 exit(1);
2331 }
2332
2333 close(fd);
2334 return 0;
2335}
2336
Roman Peniaev023cc7c2014-08-12 23:25:45 +09002337#define DO_IO(func, fd, buf, nbyte) \
2338 ({ \
2339 ssize_t ret = 0, r; \
2340 do { \
2341 r = func(fd, buf + ret, nbyte - ret); \
2342 if (r < 0 && errno != EINTR) { \
2343 ret = -1; \
2344 break; \
2345 } \
2346 else if (r > 0) \
2347 ret += r; \
2348 } while (r != 0 && (size_t)ret != nbyte); \
2349 \
2350 ret; \
2351 })
2352
2353enum rpmb_op_type {
2354 MMC_RPMB_WRITE_KEY = 0x01,
2355 MMC_RPMB_READ_CNT = 0x02,
2356 MMC_RPMB_WRITE = 0x03,
2357 MMC_RPMB_READ = 0x04,
2358
2359 /* For internal usage only, do not use it directly */
2360 MMC_RPMB_READ_RESP = 0x05
2361};
2362
2363struct rpmb_frame {
2364 u_int8_t stuff[196];
2365 u_int8_t key_mac[32];
2366 u_int8_t data[256];
2367 u_int8_t nonce[16];
2368 u_int32_t write_counter;
2369 u_int16_t addr;
2370 u_int16_t block_count;
2371 u_int16_t result;
2372 u_int16_t req_resp;
2373};
2374
2375/* Performs RPMB operation.
2376 *
2377 * @fd: RPMB device on which we should perform ioctl command
2378 * @frame_in: input RPMB frame, should be properly inited
2379 * @frame_out: output (result) RPMB frame. Caller is responsible for checking
2380 * result and req_resp for output frame.
2381 * @out_cnt: count of outer frames. Used only for multiple blocks reading,
2382 * in the other cases -EINVAL will be returned.
2383 */
2384static int do_rpmb_op(int fd,
2385 const struct rpmb_frame *frame_in,
2386 struct rpmb_frame *frame_out,
2387 unsigned int out_cnt)
2388{
2389 int err;
2390 u_int16_t rpmb_type;
2391
2392 struct mmc_ioc_cmd ioc = {
2393 .arg = 0x0,
2394 .blksz = 512,
2395 .blocks = 1,
2396 .write_flag = 1,
2397 .opcode = MMC_WRITE_MULTIPLE_BLOCK,
2398 .flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC,
2399 .data_ptr = (uintptr_t)frame_in
2400 };
2401
2402 if (!frame_in || !frame_out || !out_cnt)
2403 return -EINVAL;
2404
2405 rpmb_type = be16toh(frame_in->req_resp);
2406
2407 switch(rpmb_type) {
2408 case MMC_RPMB_WRITE:
2409 case MMC_RPMB_WRITE_KEY:
2410 if (out_cnt != 1) {
2411 err = -EINVAL;
2412 goto out;
2413 }
2414
2415 /* Write request */
2416 ioc.write_flag |= (1<<31);
2417 err = ioctl(fd, MMC_IOC_CMD, &ioc);
2418 if (err < 0) {
2419 err = -errno;
2420 goto out;
2421 }
2422
2423 /* Result request */
2424 memset(frame_out, 0, sizeof(*frame_out));
2425 frame_out->req_resp = htobe16(MMC_RPMB_READ_RESP);
2426 ioc.write_flag = 1;
2427 ioc.data_ptr = (uintptr_t)frame_out;
2428 err = ioctl(fd, MMC_IOC_CMD, &ioc);
2429 if (err < 0) {
2430 err = -errno;
2431 goto out;
2432 }
2433
2434 /* Get response */
2435 ioc.write_flag = 0;
2436 ioc.opcode = MMC_READ_MULTIPLE_BLOCK;
2437 err = ioctl(fd, MMC_IOC_CMD, &ioc);
2438 if (err < 0) {
2439 err = -errno;
2440 goto out;
2441 }
2442
2443 break;
2444 case MMC_RPMB_READ_CNT:
2445 if (out_cnt != 1) {
2446 err = -EINVAL;
2447 goto out;
2448 }
2449 /* fall through */
2450
2451 case MMC_RPMB_READ:
2452 /* Request */
2453 err = ioctl(fd, MMC_IOC_CMD, &ioc);
2454 if (err < 0) {
2455 err = -errno;
2456 goto out;
2457 }
2458
2459 /* Get response */
2460 ioc.write_flag = 0;
2461 ioc.opcode = MMC_READ_MULTIPLE_BLOCK;
2462 ioc.blocks = out_cnt;
2463 ioc.data_ptr = (uintptr_t)frame_out;
2464 err = ioctl(fd, MMC_IOC_CMD, &ioc);
2465 if (err < 0) {
2466 err = -errno;
2467 goto out;
2468 }
2469
2470 break;
2471 default:
2472 err = -EINVAL;
2473 goto out;
2474 }
2475
2476out:
2477 return err;
2478}
2479
2480int do_rpmb_write_key(int nargs, char **argv)
2481{
2482 int ret, dev_fd, key_fd;
2483 struct rpmb_frame frame_in = {
2484 .req_resp = htobe16(MMC_RPMB_WRITE_KEY)
2485 }, frame_out;
2486
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002487 if (nargs != 3) {
2488 fprintf(stderr, "Usage: mmc rpmb write-key </path/to/mmcblkXrpmb> </path/to/key>\n");
2489 exit(1);
2490 }
Roman Peniaev023cc7c2014-08-12 23:25:45 +09002491
2492 dev_fd = open(argv[1], O_RDWR);
2493 if (dev_fd < 0) {
2494 perror("device open");
2495 exit(1);
2496 }
2497
2498 if (0 == strcmp(argv[2], "-"))
2499 key_fd = STDIN_FILENO;
2500 else {
2501 key_fd = open(argv[2], O_RDONLY);
2502 if (key_fd < 0) {
2503 perror("can't open key file");
2504 exit(1);
2505 }
2506 }
2507
2508 /* Read the auth key */
2509 ret = DO_IO(read, key_fd, frame_in.key_mac, sizeof(frame_in.key_mac));
2510 if (ret < 0) {
2511 perror("read the key");
2512 exit(1);
2513 } else if (ret != sizeof(frame_in.key_mac)) {
2514 printf("Auth key must be %lu bytes length, but we read only %d, exit\n",
2515 (unsigned long)sizeof(frame_in.key_mac),
2516 ret);
2517 exit(1);
2518 }
2519
2520 /* Execute RPMB op */
2521 ret = do_rpmb_op(dev_fd, &frame_in, &frame_out, 1);
2522 if (ret != 0) {
2523 perror("RPMB ioctl failed");
2524 exit(1);
2525 }
2526
2527 /* Check RPMB response */
2528 if (frame_out.result != 0) {
2529 printf("RPMB operation failed, retcode 0x%04x\n",
2530 be16toh(frame_out.result));
2531 exit(1);
2532 }
2533
2534 close(dev_fd);
2535 if (key_fd != STDIN_FILENO)
2536 close(key_fd);
2537
2538 return ret;
2539}
2540
2541int rpmb_read_counter(int dev_fd, unsigned int *cnt)
2542{
2543 int ret;
2544 struct rpmb_frame frame_in = {
2545 .req_resp = htobe16(MMC_RPMB_READ_CNT)
2546 }, frame_out;
2547
2548 /* Execute RPMB op */
2549 ret = do_rpmb_op(dev_fd, &frame_in, &frame_out, 1);
2550 if (ret != 0) {
2551 perror("RPMB ioctl failed");
2552 exit(1);
2553 }
2554
2555 /* Check RPMB response */
2556 if (frame_out.result != 0)
2557 return be16toh(frame_out.result);
2558
2559 *cnt = be32toh(frame_out.write_counter);
2560
2561 return 0;
2562}
2563
2564int do_rpmb_read_counter(int nargs, char **argv)
2565{
2566 int ret, dev_fd;
2567 unsigned int cnt;
2568
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002569 if (nargs != 2) {
2570 fprintf(stderr, "Usage: mmc rpmb read-counter </path/to/mmcblkXrpmb>\n");
2571 exit(1);
2572 }
Roman Peniaev023cc7c2014-08-12 23:25:45 +09002573
2574 dev_fd = open(argv[1], O_RDWR);
2575 if (dev_fd < 0) {
2576 perror("device open");
2577 exit(1);
2578 }
2579
2580 ret = rpmb_read_counter(dev_fd, &cnt);
2581
2582 /* Check RPMB response */
2583 if (ret != 0) {
2584 printf("RPMB operation failed, retcode 0x%04x\n", ret);
2585 exit(1);
2586 }
2587
2588 close(dev_fd);
2589
2590 printf("Counter value: 0x%08x\n", cnt);
2591
2592 return ret;
2593}
2594
2595int do_rpmb_read_block(int nargs, char **argv)
2596{
2597 int i, ret, dev_fd, data_fd, key_fd = -1;
2598 uint16_t addr, blocks_cnt;
2599 unsigned char key[32];
2600 struct rpmb_frame frame_in = {
2601 .req_resp = htobe16(MMC_RPMB_READ),
2602 }, *frame_out_p;
2603
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002604 if (nargs != 5 && nargs != 6) {
2605 fprintf(stderr, "Usage: mmc rpmb read-block </path/to/mmcblkXrpmb> <address> <blocks count> </path/to/output_file> [/path/to/key]\n");
2606 exit(1);
2607 }
Roman Peniaev023cc7c2014-08-12 23:25:45 +09002608
2609 dev_fd = open(argv[1], O_RDWR);
2610 if (dev_fd < 0) {
2611 perror("device open");
2612 exit(1);
2613 }
2614
2615 /* Get block address */
2616 errno = 0;
2617 addr = strtol(argv[2], NULL, 0);
2618 if (errno) {
2619 perror("incorrect address");
2620 exit(1);
2621 }
2622 frame_in.addr = htobe16(addr);
2623
2624 /* Get blocks count */
2625 errno = 0;
2626 blocks_cnt = strtol(argv[3], NULL, 0);
2627 if (errno) {
2628 perror("incorrect blocks count");
2629 exit(1);
2630 }
2631
2632 if (!blocks_cnt) {
2633 printf("please, specify valid blocks count number\n");
2634 exit(1);
2635 }
2636
2637 frame_out_p = calloc(sizeof(*frame_out_p), blocks_cnt);
2638 if (!frame_out_p) {
2639 printf("can't allocate memory for RPMB outer frames\n");
2640 exit(1);
2641 }
2642
2643 /* Write 256b data */
2644 if (0 == strcmp(argv[4], "-"))
2645 data_fd = STDOUT_FILENO;
2646 else {
2647 data_fd = open(argv[4], O_WRONLY | O_CREAT | O_APPEND,
2648 S_IRUSR | S_IWUSR);
2649 if (data_fd < 0) {
2650 perror("can't open output file");
2651 exit(1);
2652 }
2653 }
2654
2655 /* Key is specified */
2656 if (nargs == 6) {
2657 if (0 == strcmp(argv[5], "-"))
2658 key_fd = STDIN_FILENO;
2659 else {
2660 key_fd = open(argv[5], O_RDONLY);
2661 if (key_fd < 0) {
2662 perror("can't open input key file");
2663 exit(1);
2664 }
2665 }
2666
2667 ret = DO_IO(read, key_fd, key, sizeof(key));
2668 if (ret < 0) {
2669 perror("read the key data");
2670 exit(1);
2671 } else if (ret != sizeof(key)) {
2672 printf("Data must be %lu bytes length, but we read only %d, exit\n",
2673 (unsigned long)sizeof(key),
2674 ret);
2675 exit(1);
2676 }
2677 }
2678
2679 /* Execute RPMB op */
2680 ret = do_rpmb_op(dev_fd, &frame_in, frame_out_p, blocks_cnt);
2681 if (ret != 0) {
2682 perror("RPMB ioctl failed");
2683 exit(1);
2684 }
2685
2686 /* Check RPMB response */
2687 if (frame_out_p[blocks_cnt - 1].result != 0) {
2688 printf("RPMB operation failed, retcode 0x%04x\n",
2689 be16toh(frame_out_p[blocks_cnt - 1].result));
2690 exit(1);
2691 }
2692
2693 /* Do we have to verify data against key? */
2694 if (nargs == 6) {
2695 unsigned char mac[32];
2696 hmac_sha256_ctx ctx;
2697 struct rpmb_frame *frame_out = NULL;
2698
2699 hmac_sha256_init(&ctx, key, sizeof(key));
2700 for (i = 0; i < blocks_cnt; i++) {
2701 frame_out = &frame_out_p[i];
2702 hmac_sha256_update(&ctx, frame_out->data,
2703 sizeof(*frame_out) -
2704 offsetof(struct rpmb_frame, data));
2705 }
2706
2707 hmac_sha256_final(&ctx, mac, sizeof(mac));
2708
2709 /* Impossible */
2710 assert(frame_out);
2711
2712 /* Compare calculated MAC and MAC from last frame */
2713 if (memcmp(mac, frame_out->key_mac, sizeof(mac))) {
2714 printf("RPMB MAC missmatch\n");
2715 exit(1);
2716 }
2717 }
2718
2719 /* Write data */
2720 for (i = 0; i < blocks_cnt; i++) {
2721 struct rpmb_frame *frame_out = &frame_out_p[i];
2722 ret = DO_IO(write, data_fd, frame_out->data, sizeof(frame_out->data));
2723 if (ret < 0) {
2724 perror("write the data");
2725 exit(1);
2726 } else if (ret != sizeof(frame_out->data)) {
2727 printf("Data must be %lu bytes length, but we wrote only %d, exit\n",
2728 (unsigned long)sizeof(frame_out->data),
2729 ret);
2730 exit(1);
2731 }
2732 }
2733
2734 free(frame_out_p);
2735 close(dev_fd);
2736 if (data_fd != STDOUT_FILENO)
2737 close(data_fd);
2738 if (key_fd != -1 && key_fd != STDIN_FILENO)
2739 close(key_fd);
2740
2741 return ret;
2742}
2743
2744int do_rpmb_write_block(int nargs, char **argv)
2745{
2746 int ret, dev_fd, key_fd, data_fd;
2747 unsigned char key[32];
2748 uint16_t addr;
2749 unsigned int cnt;
2750 struct rpmb_frame frame_in = {
2751 .req_resp = htobe16(MMC_RPMB_WRITE),
2752 .block_count = htobe16(1)
2753 }, frame_out;
2754
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002755 if (nargs != 5) {
2756 fprintf(stderr, "Usage: mmc rpmb write-block </path/to/mmcblkXrpmb> <address> </path/to/input_file> </path/to/key>\n");
2757 exit(1);
2758 }
Roman Peniaev023cc7c2014-08-12 23:25:45 +09002759
2760 dev_fd = open(argv[1], O_RDWR);
2761 if (dev_fd < 0) {
2762 perror("device open");
2763 exit(1);
2764 }
2765
2766 ret = rpmb_read_counter(dev_fd, &cnt);
2767 /* Check RPMB response */
2768 if (ret != 0) {
2769 printf("RPMB read counter operation failed, retcode 0x%04x\n", ret);
2770 exit(1);
2771 }
2772 frame_in.write_counter = htobe32(cnt);
2773
2774 /* Get block address */
2775 errno = 0;
2776 addr = strtol(argv[2], NULL, 0);
2777 if (errno) {
2778 perror("incorrect address");
2779 exit(1);
2780 }
2781 frame_in.addr = htobe16(addr);
2782
2783 /* Read 256b data */
2784 if (0 == strcmp(argv[3], "-"))
2785 data_fd = STDIN_FILENO;
2786 else {
2787 data_fd = open(argv[3], O_RDONLY);
2788 if (data_fd < 0) {
2789 perror("can't open input file");
2790 exit(1);
2791 }
2792 }
2793
2794 ret = DO_IO(read, data_fd, frame_in.data, sizeof(frame_in.data));
2795 if (ret < 0) {
2796 perror("read the data");
2797 exit(1);
2798 } else if (ret != sizeof(frame_in.data)) {
2799 printf("Data must be %lu bytes length, but we read only %d, exit\n",
2800 (unsigned long)sizeof(frame_in.data),
2801 ret);
2802 exit(1);
2803 }
2804
2805 /* Read the auth key */
2806 if (0 == strcmp(argv[4], "-"))
2807 key_fd = STDIN_FILENO;
2808 else {
2809 key_fd = open(argv[4], O_RDONLY);
2810 if (key_fd < 0) {
2811 perror("can't open key file");
2812 exit(1);
2813 }
2814 }
2815
2816 ret = DO_IO(read, key_fd, key, sizeof(key));
2817 if (ret < 0) {
2818 perror("read the key");
2819 exit(1);
2820 } else if (ret != sizeof(key)) {
2821 printf("Auth key must be %lu bytes length, but we read only %d, exit\n",
2822 (unsigned long)sizeof(key),
2823 ret);
2824 exit(1);
2825 }
2826
2827 /* Calculate HMAC SHA256 */
2828 hmac_sha256(
2829 key, sizeof(key),
2830 frame_in.data, sizeof(frame_in) - offsetof(struct rpmb_frame, data),
2831 frame_in.key_mac, sizeof(frame_in.key_mac));
2832
2833 /* Execute RPMB op */
2834 ret = do_rpmb_op(dev_fd, &frame_in, &frame_out, 1);
2835 if (ret != 0) {
2836 perror("RPMB ioctl failed");
2837 exit(1);
2838 }
2839
2840 /* Check RPMB response */
2841 if (frame_out.result != 0) {
2842 printf("RPMB operation failed, retcode 0x%04x\n",
2843 be16toh(frame_out.result));
2844 exit(1);
2845 }
2846
2847 close(dev_fd);
2848 if (data_fd != STDIN_FILENO)
2849 close(data_fd);
2850 if (key_fd != STDIN_FILENO)
2851 close(key_fd);
2852
2853 return ret;
2854}
Al Cooper786418c2015-04-29 18:12:35 -04002855
2856int do_cache_ctrl(int value, int nargs, char **argv)
2857{
2858 __u8 ext_csd[512];
2859 int fd, ret;
2860 char *device;
2861
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002862 if (nargs != 2) {
2863 fprintf(stderr, "Usage: mmc cache enable </path/to/mmcblkX>\n");
2864 exit(1);
2865 }
Al Cooper786418c2015-04-29 18:12:35 -04002866
2867 device = argv[1];
2868
2869 fd = open(device, O_RDWR);
2870 if (fd < 0) {
2871 perror("open");
2872 exit(1);
2873 }
2874
2875 ret = read_extcsd(fd, ext_csd);
2876 if (ret) {
2877 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
2878 exit(1);
2879 }
2880
2881 if (ext_csd[EXT_CSD_REV] < EXT_CSD_REV_V4_5) {
2882 fprintf(stderr,
2883 "The CACHE option is only availabe on devices >= "
2884 "MMC 4.5 %s\n", device);
2885 exit(1);
2886 }
2887
2888 /* If the cache size is zero, this device does not have a cache */
2889 if (!(ext_csd[EXT_CSD_CACHE_SIZE_3] ||
2890 ext_csd[EXT_CSD_CACHE_SIZE_2] ||
2891 ext_csd[EXT_CSD_CACHE_SIZE_1] ||
2892 ext_csd[EXT_CSD_CACHE_SIZE_0])) {
2893 fprintf(stderr,
2894 "The CACHE option is not available on %s\n",
2895 device);
2896 exit(1);
2897 }
2898 ret = write_extcsd_value(fd, EXT_CSD_CACHE_CTRL, value);
2899 if (ret) {
2900 fprintf(stderr,
2901 "Could not write 0x%02x to EXT_CSD[%d] in %s\n",
2902 value, EXT_CSD_CACHE_CTRL, device);
2903 exit(1);
2904 }
2905
2906 return ret;
2907}
2908
2909int do_cache_en(int nargs, char **argv)
2910{
2911 return do_cache_ctrl(1, nargs, argv);
2912}
2913
2914int do_cache_dis(int nargs, char **argv)
2915{
2916 return do_cache_ctrl(0, nargs, argv);
2917}
Avi Shchislowskidc7ab962016-03-08 14:22:41 -05002918
2919int do_ffu(int nargs, char **argv)
2920{
2921#ifndef MMC_IOC_MULTI_CMD
2922 fprintf(stderr, "mmc-utils has been compiled without MMC_IOC_MULTI_CMD"
2923 " support, needed by FFU.\n");
2924 exit(1);
2925#else
2926 int dev_fd, img_fd;
2927 int sect_done = 0, retry = 3, ret = -EINVAL;
2928 unsigned int sect_size;
2929 __u8 ext_csd[EXT_CSD_SIZE];
2930 __u8 *buf;
2931 __u32 arg;
2932 off_t fw_size;
2933 ssize_t chunk_size;
2934 char *device;
2935 struct mmc_ioc_multi_cmd *multi_cmd;
2936
Uwe Kleine-Königcd2fea72017-12-21 11:00:11 +01002937 if (nargs != 3) {
2938 fprintf(stderr, "Usage: ffu <image name> </path/to/mmcblkX> \n");
2939 exit(1);
2940 }
Avi Shchislowskidc7ab962016-03-08 14:22:41 -05002941
2942 device = argv[2];
2943 dev_fd = open(device, O_RDWR);
2944 if (dev_fd < 0) {
2945 perror("device open failed");
2946 exit(1);
2947 }
2948 img_fd = open(argv[1], O_RDONLY);
2949 if (img_fd < 0) {
2950 perror("image open failed");
2951 close(dev_fd);
2952 exit(1);
2953 }
2954
2955 buf = malloc(512);
2956 multi_cmd = calloc(1, sizeof(struct mmc_ioc_multi_cmd) +
2957 3 * sizeof(struct mmc_ioc_cmd));
2958 if (!buf || !multi_cmd) {
2959 perror("failed to allocate memory");
2960 goto out;
2961 }
2962
2963 ret = read_extcsd(dev_fd, ext_csd);
2964 if (ret) {
2965 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
2966 goto out;
2967 }
2968
2969 if (ext_csd[EXT_CSD_REV] < EXT_CSD_REV_V5_0) {
2970 fprintf(stderr,
2971 "The FFU feature is only available on devices >= "
2972 "MMC 5.0, not supported in %s\n", device);
2973 goto out;
2974 }
2975
2976 if (!(ext_csd[EXT_CSD_SUPPORTED_MODES] & EXT_CSD_FFU)) {
2977 fprintf(stderr, "FFU is not supported in %s\n", device);
2978 goto out;
2979 }
2980
2981 if (ext_csd[EXT_CSD_FW_CONFIG] & EXT_CSD_UPDATE_DISABLE) {
2982 fprintf(stderr, "Firmware update was disabled in %s\n", device);
2983 goto out;
2984 }
2985
2986 fw_size = lseek(img_fd, 0, SEEK_END);
2987
2988 if (fw_size == 0) {
2989 fprintf(stderr, "Firmware image is empty");
2990 goto out;
2991 }
2992
2993 sect_size = (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 0) ? 512 : 4096;
2994 if (fw_size % sect_size) {
2995 fprintf(stderr, "Firmware data size (%jd) is not aligned!\n", (intmax_t)fw_size);
2996 goto out;
2997 }
2998
2999 /* set CMD ARG */
3000 arg = ext_csd[EXT_CSD_FFU_ARG_0] |
3001 ext_csd[EXT_CSD_FFU_ARG_1] << 8 |
3002 ext_csd[EXT_CSD_FFU_ARG_2] << 16 |
3003 ext_csd[EXT_CSD_FFU_ARG_3] << 24;
3004
3005 /* prepare multi_cmd to be sent */
3006 multi_cmd->num_of_cmds = 3;
3007
3008 /* put device into ffu mode */
3009 multi_cmd->cmds[0].opcode = MMC_SWITCH;
3010 multi_cmd->cmds[0].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
3011 (EXT_CSD_MODE_CONFIG << 16) |
3012 (EXT_CSD_FFU_MODE << 8) |
3013 EXT_CSD_CMD_SET_NORMAL;
3014 multi_cmd->cmds[0].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
3015 multi_cmd->cmds[0].write_flag = 1;
3016
3017 /* send image chunk */
3018 multi_cmd->cmds[1].opcode = MMC_WRITE_BLOCK;
3019 multi_cmd->cmds[1].blksz = sect_size;
3020 multi_cmd->cmds[1].blocks = 1;
3021 multi_cmd->cmds[1].arg = arg;
3022 multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
3023 multi_cmd->cmds[1].write_flag = 1;
3024 mmc_ioc_cmd_set_data(multi_cmd->cmds[1], buf);
3025
3026 /* return device into normal mode */
3027 multi_cmd->cmds[2].opcode = MMC_SWITCH;
3028 multi_cmd->cmds[2].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
3029 (EXT_CSD_MODE_CONFIG << 16) |
3030 (EXT_CSD_NORMAL_MODE << 8) |
3031 EXT_CSD_CMD_SET_NORMAL;
3032 multi_cmd->cmds[2].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
3033 multi_cmd->cmds[2].write_flag = 1;
3034
3035do_retry:
3036 /* read firmware chunk */
3037 lseek(img_fd, 0, SEEK_SET);
3038 chunk_size = read(img_fd, buf, 512);
3039
3040 while (chunk_size > 0) {
3041 /* send ioctl with multi-cmd */
3042 ret = ioctl(dev_fd, MMC_IOC_MULTI_CMD, multi_cmd);
3043
3044 if (ret) {
3045 perror("Multi-cmd ioctl");
3046 /* In case multi-cmd ioctl failed before exiting from ffu mode */
3047 ioctl(dev_fd, MMC_IOC_CMD, &multi_cmd->cmds[2]);
3048 goto out;
3049 }
3050
3051 ret = read_extcsd(dev_fd, ext_csd);
3052 if (ret) {
3053 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
3054 goto out;
3055 }
3056
3057 /* Test if we need to restart the download */
3058 sect_done = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_0] |
3059 ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_1] << 8 |
3060 ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_2] << 16 |
3061 ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_3] << 24;
3062 /* By spec, host should re-start download from the first sector if sect_done is 0 */
3063 if (sect_done == 0) {
3064 if (retry > 0) {
3065 retry--;
3066 fprintf(stderr, "Programming failed. Retrying... (%d)\n", retry);
3067 goto do_retry;
3068 }
3069 fprintf(stderr, "Programming failed! Aborting...\n");
3070 goto out;
3071 } else {
3072 fprintf(stderr, "Programmed %d/%jd bytes\r", sect_done * sect_size, (intmax_t)fw_size);
3073 }
3074
3075 /* read the next firmware chunk (if any) */
3076 chunk_size = read(img_fd, buf, 512);
3077 }
3078
3079 if ((sect_done * sect_size) == fw_size) {
3080 fprintf(stderr, "Programmed %jd/%jd bytes\n", (intmax_t)fw_size, (intmax_t)fw_size);
3081 fprintf(stderr, "Programming finished with status %d \n", ret);
3082 }
3083 else {
3084 fprintf(stderr, "FW size and number of sectors written mismatch. Status return %d\n", ret);
3085 goto out;
3086 }
3087
3088 /* check mode operation for ffu install*/
3089 if (!ext_csd[EXT_CSD_FFU_FEATURES]) {
3090 fprintf(stderr, "Please reboot to complete firmware installation on %s\n", device);
3091 } else {
3092 fprintf(stderr, "Installing firmware on %s...\n", device);
3093 /* Re-enter ffu mode and install the firmware */
3094 multi_cmd->num_of_cmds = 2;
3095
3096 /* set ext_csd to install mode */
3097 multi_cmd->cmds[1].opcode = MMC_SWITCH;
3098 multi_cmd->cmds[1].blksz = 0;
3099 multi_cmd->cmds[1].blocks = 0;
3100 multi_cmd->cmds[1].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
3101 (EXT_CSD_MODE_OPERATION_CODES << 16) |
3102 (EXT_CSD_FFU_INSTALL << 8) |
3103 EXT_CSD_CMD_SET_NORMAL;
3104 multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
3105 multi_cmd->cmds[1].write_flag = 1;
3106
3107 /* send ioctl with multi-cmd */
3108 ret = ioctl(dev_fd, MMC_IOC_MULTI_CMD, multi_cmd);
3109
3110 if (ret) {
3111 perror("Multi-cmd ioctl failed setting install mode");
3112 /* In case multi-cmd ioctl failed before exiting from ffu mode */
3113 ioctl(dev_fd, MMC_IOC_CMD, &multi_cmd->cmds[2]);
3114 goto out;
3115 }
3116
3117 ret = read_extcsd(dev_fd, ext_csd);
3118 if (ret) {
3119 fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
3120 goto out;
3121 }
3122
3123 /* return status */
3124 ret = ext_csd[EXT_CSD_FFU_STATUS];
3125 if (ret) {
3126 fprintf(stderr, "%s: error %d during FFU install:\n", device, ret);
3127 goto out;
3128 } else {
3129 fprintf(stderr, "FFU finished successfully\n");
3130 }
3131 }
3132
3133out:
3134 free(buf);
3135 free(multi_cmd);
3136 close(img_fd);
3137 close(dev_fd);
3138 return ret;
3139#endif
3140}