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 <malloc.h> 11 #include <errno.h> 12 #include <div64.h> 13 #include <dfu.h> 14 #include <ext4fs.h> 15 #include <fat.h> 16 #include <mmc.h> 17 18 static unsigned char *dfu_file_buf; 19 static u64 dfu_file_buf_len; 20 static long dfu_file_buf_filled; 21 22 static int mmc_block_op(enum dfu_op op, struct dfu_entity *dfu, 23 u64 offset, void *buf, long *len) 24 { 25 struct mmc *mmc; 26 u32 blk_start, blk_count, n = 0; 27 int ret, part_num_bkp = 0; 28 29 mmc = find_mmc_device(dfu->data.mmc.dev_num); 30 if (!mmc) { 31 pr_err("Device MMC %d - not found!", dfu->data.mmc.dev_num); 32 return -ENODEV; 33 } 34 35 /* 36 * We must ensure that we work in lba_blk_size chunks, so ALIGN 37 * this value. 38 */ 39 *len = ALIGN(*len, dfu->data.mmc.lba_blk_size); 40 41 blk_start = dfu->data.mmc.lba_start + 42 (u32)lldiv(offset, dfu->data.mmc.lba_blk_size); 43 blk_count = *len / dfu->data.mmc.lba_blk_size; 44 if (blk_start + blk_count > 45 dfu->data.mmc.lba_start + dfu->data.mmc.lba_size) { 46 puts("Request would exceed designated area!\n"); 47 return -EINVAL; 48 } 49 50 if (dfu->data.mmc.hw_partition >= 0) { 51 part_num_bkp = mmc_get_blk_desc(mmc)->hwpart; 52 ret = blk_select_hwpart_devnum(IF_TYPE_MMC, 53 dfu->data.mmc.dev_num, 54 dfu->data.mmc.hw_partition); 55 if (ret) 56 return ret; 57 } 58 59 debug("%s: %s dev: %d start: %d cnt: %d buf: 0x%p\n", __func__, 60 op == DFU_OP_READ ? "MMC READ" : "MMC WRITE", 61 dfu->data.mmc.dev_num, blk_start, blk_count, buf); 62 switch (op) { 63 case DFU_OP_READ: 64 n = blk_dread(mmc_get_blk_desc(mmc), blk_start, blk_count, buf); 65 break; 66 case DFU_OP_WRITE: 67 n = blk_dwrite(mmc_get_blk_desc(mmc), blk_start, blk_count, 68 buf); 69 break; 70 default: 71 pr_err("Operation not supported\n"); 72 } 73 74 if (n != blk_count) { 75 pr_err("MMC operation failed"); 76 if (dfu->data.mmc.hw_partition >= 0) 77 blk_select_hwpart_devnum(IF_TYPE_MMC, 78 dfu->data.mmc.dev_num, 79 part_num_bkp); 80 return -EIO; 81 } 82 83 if (dfu->data.mmc.hw_partition >= 0) { 84 ret = blk_select_hwpart_devnum(IF_TYPE_MMC, 85 dfu->data.mmc.dev_num, 86 part_num_bkp); 87 if (ret) 88 return ret; 89 } 90 91 return 0; 92 } 93 94 static int mmc_file_buffer(struct dfu_entity *dfu, void *buf, long *len) 95 { 96 if (dfu_file_buf_len + *len > CONFIG_SYS_DFU_MAX_FILE_SIZE) { 97 dfu_file_buf_len = 0; 98 return -EINVAL; 99 } 100 101 /* Add to the current buffer. */ 102 memcpy(dfu_file_buf + dfu_file_buf_len, buf, *len); 103 dfu_file_buf_len += *len; 104 105 return 0; 106 } 107 108 static int mmc_file_op(enum dfu_op op, struct dfu_entity *dfu, 109 void *buf, u64 *len) 110 { 111 const char *fsname, *opname; 112 char cmd_buf[DFU_CMD_BUF_SIZE]; 113 char *str_env; 114 int ret; 115 116 switch (dfu->layout) { 117 case DFU_FS_FAT: 118 fsname = "fat"; 119 break; 120 case DFU_FS_EXT4: 121 fsname = "ext4"; 122 break; 123 default: 124 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 125 dfu_get_layout(dfu->layout)); 126 return -1; 127 } 128 129 switch (op) { 130 case DFU_OP_READ: 131 opname = "load"; 132 break; 133 case DFU_OP_WRITE: 134 opname = "write"; 135 break; 136 case DFU_OP_SIZE: 137 opname = "size"; 138 break; 139 default: 140 return -1; 141 } 142 143 sprintf(cmd_buf, "%s%s mmc %d:%d", fsname, opname, 144 dfu->data.mmc.dev, dfu->data.mmc.part); 145 146 if (op != DFU_OP_SIZE) 147 sprintf(cmd_buf + strlen(cmd_buf), " %p", buf); 148 149 sprintf(cmd_buf + strlen(cmd_buf), " %s", dfu->name); 150 151 if (op == DFU_OP_WRITE) 152 sprintf(cmd_buf + strlen(cmd_buf), " %llx", *len); 153 154 debug("%s: %s 0x%p\n", __func__, cmd_buf, cmd_buf); 155 156 ret = run_command(cmd_buf, 0); 157 if (ret) { 158 puts("dfu: Read error!\n"); 159 return ret; 160 } 161 162 if (op != DFU_OP_WRITE) { 163 str_env = env_get("filesize"); 164 if (str_env == NULL) { 165 puts("dfu: Wrong file size!\n"); 166 return -1; 167 } 168 *len = simple_strtoul(str_env, NULL, 16); 169 } 170 171 return ret; 172 } 173 174 int dfu_write_medium_mmc(struct dfu_entity *dfu, 175 u64 offset, void *buf, long *len) 176 { 177 int ret = -1; 178 179 switch (dfu->layout) { 180 case DFU_RAW_ADDR: 181 ret = mmc_block_op(DFU_OP_WRITE, dfu, offset, buf, len); 182 break; 183 case DFU_FS_FAT: 184 case DFU_FS_EXT4: 185 ret = mmc_file_buffer(dfu, buf, len); 186 break; 187 default: 188 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 189 dfu_get_layout(dfu->layout)); 190 } 191 192 return ret; 193 } 194 195 int dfu_flush_medium_mmc(struct dfu_entity *dfu) 196 { 197 int ret = 0; 198 199 if (dfu->layout != DFU_RAW_ADDR) { 200 /* Do stuff here. */ 201 ret = mmc_file_op(DFU_OP_WRITE, dfu, dfu_file_buf, 202 &dfu_file_buf_len); 203 204 /* Now that we're done */ 205 dfu_file_buf_len = 0; 206 } 207 208 return ret; 209 } 210 211 int dfu_get_medium_size_mmc(struct dfu_entity *dfu, u64 *size) 212 { 213 int ret; 214 215 switch (dfu->layout) { 216 case DFU_RAW_ADDR: 217 *size = dfu->data.mmc.lba_size * dfu->data.mmc.lba_blk_size; 218 return 0; 219 case DFU_FS_FAT: 220 case DFU_FS_EXT4: 221 dfu_file_buf_filled = -1; 222 ret = mmc_file_op(DFU_OP_SIZE, dfu, NULL, size); 223 if (ret < 0) 224 return ret; 225 if (*size > CONFIG_SYS_DFU_MAX_FILE_SIZE) 226 return -1; 227 return 0; 228 default: 229 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 230 dfu_get_layout(dfu->layout)); 231 return -1; 232 } 233 } 234 235 static int mmc_file_unbuffer(struct dfu_entity *dfu, u64 offset, void *buf, 236 long *len) 237 { 238 int ret; 239 u64 file_len; 240 241 if (dfu_file_buf_filled == -1) { 242 ret = mmc_file_op(DFU_OP_READ, dfu, dfu_file_buf, &file_len); 243 if (ret < 0) 244 return ret; 245 dfu_file_buf_filled = file_len; 246 } 247 if (offset + *len > dfu_file_buf_filled) 248 return -EINVAL; 249 250 /* Add to the current buffer. */ 251 memcpy(buf, dfu_file_buf + offset, *len); 252 253 return 0; 254 } 255 256 int dfu_read_medium_mmc(struct dfu_entity *dfu, u64 offset, void *buf, 257 long *len) 258 { 259 int ret = -1; 260 261 switch (dfu->layout) { 262 case DFU_RAW_ADDR: 263 ret = mmc_block_op(DFU_OP_READ, dfu, offset, buf, len); 264 break; 265 case DFU_FS_FAT: 266 case DFU_FS_EXT4: 267 ret = mmc_file_unbuffer(dfu, offset, buf, len); 268 break; 269 default: 270 printf("%s: Layout (%s) not (yet) supported!\n", __func__, 271 dfu_get_layout(dfu->layout)); 272 } 273 274 return ret; 275 } 276 277 void dfu_free_entity_mmc(struct dfu_entity *dfu) 278 { 279 if (dfu_file_buf) { 280 free(dfu_file_buf); 281 dfu_file_buf = NULL; 282 } 283 } 284 285 /* 286 * @param s Parameter string containing space-separated arguments: 287 * 1st: 288 * raw (raw read/write) 289 * fat (files) 290 * ext4 (^) 291 * part (partition image) 292 * 2nd and 3rd: 293 * lba_start and lba_size, for raw write 294 * mmc_dev and mmc_part, for filesystems and part 295 * 4th (optional): 296 * mmcpart <num> (access to HW eMMC partitions) 297 */ 298 int dfu_fill_entity_mmc(struct dfu_entity *dfu, char *devstr, char *s) 299 { 300 const char *entity_type; 301 size_t second_arg; 302 size_t third_arg; 303 304 struct mmc *mmc; 305 306 const char *argv[3]; 307 const char **parg = argv; 308 309 dfu->data.mmc.dev_num = simple_strtoul(devstr, NULL, 10); 310 311 for (; parg < argv + sizeof(argv) / sizeof(*argv); ++parg) { 312 *parg = strsep(&s, " "); 313 if (*parg == NULL) { 314 pr_err("Invalid number of arguments.\n"); 315 return -ENODEV; 316 } 317 } 318 319 entity_type = argv[0]; 320 /* 321 * Base 0 means we'll accept (prefixed with 0x or 0) base 16, 8, 322 * with default 10. 323 */ 324 second_arg = simple_strtoul(argv[1], NULL, 0); 325 third_arg = simple_strtoul(argv[2], NULL, 0); 326 327 mmc = find_mmc_device(dfu->data.mmc.dev_num); 328 if (mmc == NULL) { 329 pr_err("Couldn't find MMC device no. %d.\n", 330 dfu->data.mmc.dev_num); 331 return -ENODEV; 332 } 333 334 if (mmc_init(mmc)) { 335 pr_err("Couldn't init MMC device.\n"); 336 return -ENODEV; 337 } 338 339 dfu->data.mmc.hw_partition = -EINVAL; 340 if (!strcmp(entity_type, "raw")) { 341 dfu->layout = DFU_RAW_ADDR; 342 dfu->data.mmc.lba_start = second_arg; 343 dfu->data.mmc.lba_size = third_arg; 344 dfu->data.mmc.lba_blk_size = mmc->read_bl_len; 345 346 /* 347 * Check for an extra entry at dfu_alt_info env variable 348 * specifying the mmc HW defined partition number 349 */ 350 if (s) 351 if (!strcmp(strsep(&s, " "), "mmcpart")) 352 dfu->data.mmc.hw_partition = 353 simple_strtoul(s, NULL, 0); 354 355 } else if (!strcmp(entity_type, "part")) { 356 disk_partition_t partinfo; 357 struct blk_desc *blk_dev = mmc_get_blk_desc(mmc); 358 int mmcdev = second_arg; 359 int mmcpart = third_arg; 360 361 if (part_get_info(blk_dev, mmcpart, &partinfo) != 0) { 362 pr_err("Couldn't find part #%d on mmc device #%d\n", 363 mmcpart, mmcdev); 364 return -ENODEV; 365 } 366 367 dfu->layout = DFU_RAW_ADDR; 368 dfu->data.mmc.lba_start = partinfo.start; 369 dfu->data.mmc.lba_size = partinfo.size; 370 dfu->data.mmc.lba_blk_size = partinfo.blksz; 371 } else if (!strcmp(entity_type, "fat")) { 372 dfu->layout = DFU_FS_FAT; 373 } else if (!strcmp(entity_type, "ext4")) { 374 dfu->layout = DFU_FS_EXT4; 375 } else { 376 pr_err("Memory layout (%s) not supported!\n", entity_type); 377 return -ENODEV; 378 } 379 380 /* if it's NOT a raw write */ 381 if (strcmp(entity_type, "raw")) { 382 dfu->data.mmc.dev = second_arg; 383 dfu->data.mmc.part = third_arg; 384 } 385 386 dfu->dev_type = DFU_DEV_MMC; 387 dfu->get_medium_size = dfu_get_medium_size_mmc; 388 dfu->read_medium = dfu_read_medium_mmc; 389 dfu->write_medium = dfu_write_medium_mmc; 390 dfu->flush_medium = dfu_flush_medium_mmc; 391 dfu->inited = 0; 392 dfu->free_entity = dfu_free_entity_mmc; 393 394 /* Check if file buffer is ready */ 395 if (!dfu_file_buf) { 396 dfu_file_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, 397 CONFIG_SYS_DFU_MAX_FILE_SIZE); 398 if (!dfu_file_buf) { 399 pr_err("Could not memalign 0x%x bytes", 400 CONFIG_SYS_DFU_MAX_FILE_SIZE); 401 return -ENOMEM; 402 } 403 } 404 405 return 0; 406 } 407