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