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