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