1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * dfu.c -- DFU back-end routines 4 * 5 * Copyright (C) 2012 Samsung Electronics 6 * author: Lukasz Majewski <l.majewski@samsung.com> 7 */ 8 9#include <common.h> 10#include <log.h> 11#include <malloc.h> 12#include <errno.h> 13#include <div64.h> 14#include <dfu.h> 15#include <ext4fs.h> 16#include <fat.h> 17#include <mmc.h> 18#include <part.h> 19#include <command.h> 20#include <linux/printk.h> 21 22static unsigned char *dfu_file_buf; 23static u64 dfu_file_buf_len; 24static u64 dfu_file_buf_offset; 25 26static int mmc_block_op(enum dfu_op op, struct dfu_entity *dfu, 27 u64 offset, void *buf, long *len) 28{ 29 struct mmc *mmc; 30 u32 blk_start, blk_count, n = 0; 31 int ret, part_num_bkp = 0; 32 33 mmc = find_mmc_device(dfu->data.mmc.dev_num); 34 if (!mmc) { 35 pr_err("Device MMC %d - not found!", dfu->data.mmc.dev_num); 36 return -ENODEV; 37 } 38 39 /* 40 * We must ensure that we work in lba_blk_size chunks, so ALIGN 41 * this value. 42 */ 43 *len = ALIGN(*len, dfu->data.mmc.lba_blk_size); 44 45 blk_start = dfu->data.mmc.lba_start + 46 (u32)lldiv(offset, dfu->data.mmc.lba_blk_size); 47 blk_count = *len / dfu->data.mmc.lba_blk_size; 48 if (blk_start + blk_count > 49 dfu->data.mmc.lba_start + dfu->data.mmc.lba_size) { 50 puts("Request would exceed designated area!\n"); 51 return -EINVAL; 52 } 53 54 if (dfu->data.mmc.hw_partition >= 0) { 55 part_num_bkp = mmc_get_blk_desc(mmc)->hwpart; 56 ret = blk_select_hwpart_devnum(UCLASS_MMC, 57 dfu->data.mmc.dev_num, 58 dfu->data.mmc.hw_partition); 59 if (ret) 60 return ret; 61 } 62 63 debug("%s: %s dev: %d start: %d cnt: %d buf: 0x%p\n", __func__, 64 op == DFU_OP_READ ? "MMC READ" : "MMC WRITE", 65 dfu->data.mmc.dev_num, blk_start, blk_count, buf); 66 switch (op) { 67 case DFU_OP_READ: 68 n = blk_dread(mmc_get_blk_desc(mmc), blk_start, blk_count, buf); 69 break; 70 case DFU_OP_WRITE: 71 n = blk_dwrite(mmc_get_blk_desc(mmc), blk_start, blk_count, 72 buf); 73 break; 74 default: 75 pr_err("Operation not supported\n"); 76 } 77 78 if (n != blk_count) { 79 pr_err("MMC operation failed"); 80 if (dfu->data.mmc.hw_partition >= 0) 81 blk_select_hwpart_devnum(UCLASS_MMC, 82 dfu->data.mmc.dev_num, 83 part_num_bkp); 84 return -EIO; 85 } 86 87 if (dfu->data.mmc.hw_partition >= 0) { 88 ret = blk_select_hwpart_devnum(UCLASS_MMC, 89 dfu->data.mmc.dev_num, 90 part_num_bkp); 91 if (ret) 92 return ret; 93 } 94 95 return 0; 96} 97 98static int mmc_file_op(enum dfu_op op, struct dfu_entity *dfu, 99 u64 offset, void *buf, u64 *len) 100{ 101 char dev_part_str[8]; 102 int ret; 103 int fstype; 104 loff_t size = 0; 105 106 switch (dfu->layout) { 107 case DFU_FS_FAT: 108 fstype = FS_TYPE_FAT; 109 break; 110 case DFU_FS_EXT4: 111 fstype = FS_TYPE_EXT; 112 break; 113 case DFU_SKIP: 114 return 0; 115 default: 116 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 117 dfu_get_layout(dfu->layout)); 118 return -1; 119 } 120 121 snprintf(dev_part_str, sizeof(dev_part_str), "%d:%d", 122 dfu->data.mmc.dev, dfu->data.mmc.part); 123 124 ret = fs_set_blk_dev("mmc", dev_part_str, fstype); 125 if (ret) { 126 puts("dfu: fs_set_blk_dev error!\n"); 127 return ret; 128 } 129 130 switch (op) { 131 case DFU_OP_READ: 132 ret = fs_read(dfu->name, (size_t)buf, offset, *len, &size); 133 if (ret) { 134 puts("dfu: fs_read error!\n"); 135 return ret; 136 } 137 *len = size; 138 break; 139 case DFU_OP_WRITE: 140 ret = fs_write(dfu->name, (size_t)buf, offset, *len, &size); 141 if (ret) { 142 puts("dfu: fs_write error!\n"); 143 return ret; 144 } 145 break; 146 case DFU_OP_SIZE: 147 ret = fs_size(dfu->name, &size); 148 if (ret) { 149 puts("dfu: fs_size error!\n"); 150 return ret; 151 } 152 *len = size; 153 break; 154 default: 155 return -1; 156 } 157 158 return ret; 159} 160 161static int mmc_file_buf_write(struct dfu_entity *dfu, u64 offset, void *buf, long *len) 162{ 163 int ret = 0; 164 165 if (offset == 0) { 166 dfu_file_buf_len = 0; 167 dfu_file_buf_offset = 0; 168 } 169 170 /* Add to the current buffer. */ 171 if (dfu_file_buf_len + *len > CONFIG_SYS_DFU_MAX_FILE_SIZE) 172 *len = CONFIG_SYS_DFU_MAX_FILE_SIZE - dfu_file_buf_len; 173 memcpy(dfu_file_buf + dfu_file_buf_len, buf, *len); 174 dfu_file_buf_len += *len; 175 176 if (dfu_file_buf_len == CONFIG_SYS_DFU_MAX_FILE_SIZE) { 177 ret = mmc_file_op(DFU_OP_WRITE, dfu, dfu_file_buf_offset, 178 dfu_file_buf, &dfu_file_buf_len); 179 dfu_file_buf_offset += dfu_file_buf_len; 180 dfu_file_buf_len = 0; 181 } 182 183 return ret; 184} 185 186static int mmc_file_buf_write_finish(struct dfu_entity *dfu) 187{ 188 int ret = mmc_file_op(DFU_OP_WRITE, dfu, dfu_file_buf_offset, 189 dfu_file_buf, &dfu_file_buf_len); 190 191 /* Now that we're done */ 192 dfu_file_buf_len = 0; 193 dfu_file_buf_offset = 0; 194 195 return ret; 196} 197 198int dfu_write_medium_mmc(struct dfu_entity *dfu, 199 u64 offset, void *buf, long *len) 200{ 201 int ret = -1; 202 203 switch (dfu->layout) { 204 case DFU_RAW_ADDR: 205 ret = mmc_block_op(DFU_OP_WRITE, dfu, offset, buf, len); 206 break; 207 case DFU_FS_FAT: 208 case DFU_FS_EXT4: 209 ret = mmc_file_buf_write(dfu, offset, buf, len); 210 break; 211 case DFU_SCRIPT: 212 ret = run_command_list(buf, *len, 0); 213 break; 214 case DFU_SKIP: 215 ret = 0; 216 break; 217 default: 218 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 219 dfu_get_layout(dfu->layout)); 220 } 221 222 return ret; 223} 224 225int dfu_flush_medium_mmc(struct dfu_entity *dfu) 226{ 227 int ret = 0; 228 229 switch (dfu->layout) { 230 case DFU_FS_FAT: 231 case DFU_FS_EXT4: 232 ret = mmc_file_buf_write_finish(dfu); 233 break; 234 case DFU_SCRIPT: 235 /* script may have changed the dfu_alt_info */ 236 dfu_reinit_needed = true; 237 break; 238 case DFU_RAW_ADDR: 239 case DFU_SKIP: 240 break; 241 default: 242 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 243 dfu_get_layout(dfu->layout)); 244 } 245 246 return ret; 247} 248 249int dfu_get_medium_size_mmc(struct dfu_entity *dfu, u64 *size) 250{ 251 int ret; 252 253 switch (dfu->layout) { 254 case DFU_RAW_ADDR: 255 *size = dfu->data.mmc.lba_size * dfu->data.mmc.lba_blk_size; 256 return 0; 257 case DFU_FS_FAT: 258 case DFU_FS_EXT4: 259 ret = mmc_file_op(DFU_OP_SIZE, dfu, 0, NULL, size); 260 if (ret < 0) 261 return ret; 262 return 0; 263 case DFU_SCRIPT: 264 case DFU_SKIP: 265 return 0; 266 default: 267 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 268 dfu_get_layout(dfu->layout)); 269 return -1; 270 } 271} 272 273 274static int mmc_file_buf_read(struct dfu_entity *dfu, u64 offset, void *buf, 275 long *len) 276{ 277 int ret; 278 279 if (offset == 0 || offset >= dfu_file_buf_offset + dfu_file_buf_len || 280 offset + *len < dfu_file_buf_offset) { 281 u64 file_len = CONFIG_SYS_DFU_MAX_FILE_SIZE; 282 283 ret = mmc_file_op(DFU_OP_READ, dfu, offset, dfu_file_buf, 284 &file_len); 285 if (ret < 0) 286 return ret; 287 dfu_file_buf_len = file_len; 288 dfu_file_buf_offset = offset; 289 } 290 if (offset + *len > dfu_file_buf_offset + dfu_file_buf_len) 291 return -EINVAL; 292 293 /* Add to the current buffer. */ 294 memcpy(buf, dfu_file_buf + offset - dfu_file_buf_offset, *len); 295 296 return 0; 297} 298 299int dfu_read_medium_mmc(struct dfu_entity *dfu, u64 offset, void *buf, 300 long *len) 301{ 302 int ret = -1; 303 304 switch (dfu->layout) { 305 case DFU_RAW_ADDR: 306 ret = mmc_block_op(DFU_OP_READ, dfu, offset, buf, len); 307 break; 308 case DFU_FS_FAT: 309 case DFU_FS_EXT4: 310 ret = mmc_file_buf_read(dfu, offset, buf, len); 311 break; 312 default: 313 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 314 dfu_get_layout(dfu->layout)); 315 } 316 317 return ret; 318} 319 320void dfu_free_entity_mmc(struct dfu_entity *dfu) 321{ 322 if (dfu_file_buf) { 323 free(dfu_file_buf); 324 dfu_file_buf = NULL; 325 } 326} 327 328/* 329 * @param s Parameter string containing space-separated arguments: 330 * 1st: 331 * raw (raw read/write) 332 * fat (files) 333 * ext4 (^) 334 * part (partition image) 335 * 2nd and 3rd: 336 * lba_start and lba_size, for raw write 337 * mmc_dev and mmc_part, for filesystems and part 338 * 4th (optional): 339 * mmcpart <num> (access to HW eMMC partitions) 340 */ 341int dfu_fill_entity_mmc(struct dfu_entity *dfu, char *devstr, char **argv, int argc) 342{ 343 const char *entity_type; 344 ssize_t second_arg; 345 size_t third_arg; 346 struct mmc *mmc; 347 char *s; 348 349 if (argc < 3) { 350 pr_err("The number of parameters are not enough.\n"); 351 return -EINVAL; 352 } 353 354 dfu->data.mmc.dev_num = dectoul(devstr, &s); 355 if (*s) 356 return -EINVAL; 357 358 entity_type = argv[0]; 359 /* 360 * Base 0 means we'll accept (prefixed with 0x or 0) base 16, 8, 361 * with default 10. 362 */ 363 second_arg = simple_strtol(argv[1], &s, 0); 364 if (*s) 365 return -EINVAL; 366 third_arg = simple_strtoul(argv[2], &s, 0); 367 if (*s) 368 return -EINVAL; 369 370 mmc = find_mmc_device(dfu->data.mmc.dev_num); 371 if (mmc == NULL) { 372 pr_err("Couldn't find MMC device no. %d.\n", 373 dfu->data.mmc.dev_num); 374 return -ENODEV; 375 } 376 377 if (mmc_init(mmc)) { 378 pr_err("Couldn't init MMC device.\n"); 379 return -ENODEV; 380 } 381 382 dfu->data.mmc.hw_partition = -EINVAL; 383 if (!strcmp(entity_type, "raw")) { 384 dfu->layout = DFU_RAW_ADDR; 385 dfu->data.mmc.lba_start = second_arg; 386 dfu->data.mmc.lba_size = third_arg; 387 dfu->data.mmc.lba_blk_size = mmc->read_bl_len; 388 389 /* 390 * In case the size is zero (i.e. mmc raw 0x10 0), 391 * assume the user intends to use whole device. 392 */ 393 if (third_arg == 0) { 394 struct blk_desc *blk_dev = mmc_get_blk_desc(mmc); 395 396 dfu->data.mmc.lba_size = blk_dev->lba; 397 } 398 399 /* 400 * Check for an extra entry at dfu_alt_info env variable 401 * specifying the mmc HW defined partition number 402 */ 403 if (argc > 3) { 404 if (argc != 5 || strcmp(argv[3], "mmcpart")) { 405 pr_err("DFU mmc raw accept 'mmcpart <partnum>' option.\n"); 406 return -EINVAL; 407 } 408 dfu->data.mmc.hw_partition = 409 simple_strtoul(argv[4], NULL, 0); 410 } 411 412 } else if (!strcmp(entity_type, "part")) { 413 struct disk_partition partinfo; 414 struct blk_desc *blk_dev = mmc_get_blk_desc(mmc); 415 int mmcdev = second_arg; 416 int mmcpart = third_arg; 417 int offset = 0; 418 419 if (part_get_info(blk_dev, mmcpart, &partinfo) != 0) { 420 pr_err("Couldn't find part #%d on mmc device #%d\n", 421 mmcpart, mmcdev); 422 return -ENODEV; 423 } 424 425 /* 426 * Check for an extra entry at dfu_alt_info env variable 427 * specifying the mmc HW defined partition number 428 */ 429 if (argc > 3) { 430 if (argc != 5 || strcmp(argv[3], "offset")) { 431 pr_err("DFU mmc raw accept 'mmcpart <partnum>' option.\n"); 432 return -EINVAL; 433 } 434 dfu->data.mmc.hw_partition = 435 simple_strtoul(argv[4], NULL, 0); 436 } 437 438 dfu->layout = DFU_RAW_ADDR; 439 dfu->data.mmc.lba_start = partinfo.start + offset; 440 dfu->data.mmc.lba_size = partinfo.size - offset; 441 dfu->data.mmc.lba_blk_size = partinfo.blksz; 442 } else if (!strcmp(entity_type, "fat")) { 443 dfu->layout = DFU_FS_FAT; 444 } else if (!strcmp(entity_type, "ext4")) { 445 dfu->layout = DFU_FS_EXT4; 446 } else if (!strcmp(entity_type, "skip")) { 447 dfu->layout = DFU_SKIP; 448 } else if (!strcmp(entity_type, "script")) { 449 dfu->layout = DFU_SCRIPT; 450 } else { 451 pr_err("Memory layout (%s) not supported!\n", entity_type); 452 return -ENODEV; 453 } 454 455 /* if it's NOT a raw write */ 456 if (strcmp(entity_type, "raw")) { 457 dfu->data.mmc.dev = (second_arg != -1) ? second_arg : 458 dfu->data.mmc.dev_num; 459 dfu->data.mmc.part = third_arg; 460 } 461 462 dfu->dev_type = DFU_DEV_MMC; 463 dfu->get_medium_size = dfu_get_medium_size_mmc; 464 dfu->read_medium = dfu_read_medium_mmc; 465 dfu->write_medium = dfu_write_medium_mmc; 466 dfu->flush_medium = dfu_flush_medium_mmc; 467 dfu->inited = 0; 468 dfu->free_entity = dfu_free_entity_mmc; 469 470 /* Check if file buffer is ready */ 471 if (!dfu_file_buf) { 472 dfu_file_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, 473 CONFIG_SYS_DFU_MAX_FILE_SIZE); 474 if (!dfu_file_buf) { 475 pr_err("Could not memalign 0x%x bytes", 476 CONFIG_SYS_DFU_MAX_FILE_SIZE); 477 return -ENOMEM; 478 } 479 } 480 481 return 0; 482} 483