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 <errno.h> 12 #include <malloc.h> 13 #include <mmc.h> 14 #include <fat.h> 15 #include <dfu.h> 16 #include <hash.h> 17 #include <linux/list.h> 18 #include <linux/compiler.h> 19 20 static bool dfu_reset_request; 21 static LIST_HEAD(dfu_list); 22 static int dfu_alt_num; 23 static int alt_num_cnt; 24 static struct hash_algo *dfu_hash_algo; 25 26 bool dfu_reset(void) 27 { 28 return dfu_reset_request; 29 } 30 31 void dfu_trigger_reset() 32 { 33 dfu_reset_request = true; 34 } 35 36 static int dfu_find_alt_num(const char *s) 37 { 38 int i = 0; 39 40 for (; *s; s++) 41 if (*s == ';') 42 i++; 43 44 return ++i; 45 } 46 47 int dfu_init_env_entities(char *interface, char *devstr) 48 { 49 const char *str_env; 50 char *env_bkp; 51 int ret; 52 53 str_env = getenv("dfu_alt_info"); 54 if (!str_env) { 55 error("\"dfu_alt_info\" env variable not defined!\n"); 56 return -EINVAL; 57 } 58 59 env_bkp = strdup(str_env); 60 ret = dfu_config_entities(env_bkp, interface, devstr); 61 if (ret) { 62 error("DFU entities configuration failed!\n"); 63 return ret; 64 } 65 66 free(env_bkp); 67 return 0; 68 } 69 70 static unsigned char *dfu_buf; 71 static unsigned long dfu_buf_size = CONFIG_SYS_DFU_DATA_BUF_SIZE; 72 73 unsigned char *dfu_free_buf(void) 74 { 75 free(dfu_buf); 76 dfu_buf = NULL; 77 return dfu_buf; 78 } 79 80 unsigned long dfu_get_buf_size(void) 81 { 82 return dfu_buf_size; 83 } 84 85 unsigned char *dfu_get_buf(struct dfu_entity *dfu) 86 { 87 char *s; 88 89 if (dfu_buf != NULL) 90 return dfu_buf; 91 92 s = getenv("dfu_bufsiz"); 93 dfu_buf_size = s ? (unsigned long)simple_strtol(s, NULL, 16) : 94 CONFIG_SYS_DFU_DATA_BUF_SIZE; 95 if (dfu->max_buf_size && dfu_buf_size > dfu->max_buf_size) 96 dfu_buf_size = dfu->max_buf_size; 97 98 dfu_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, dfu_buf_size); 99 if (dfu_buf == NULL) 100 printf("%s: Could not memalign 0x%lx bytes\n", 101 __func__, dfu_buf_size); 102 103 return dfu_buf; 104 } 105 106 static char *dfu_get_hash_algo(void) 107 { 108 char *s; 109 110 s = getenv("dfu_hash_algo"); 111 if (!s) 112 return NULL; 113 114 if (!strcmp(s, "crc32")) { 115 debug("%s: DFU hash method: %s\n", __func__, s); 116 return s; 117 } 118 119 error("DFU hash method: %s not supported!\n", s); 120 return NULL; 121 } 122 123 static int dfu_write_buffer_drain(struct dfu_entity *dfu) 124 { 125 long w_size; 126 int ret; 127 128 /* flush size? */ 129 w_size = dfu->i_buf - dfu->i_buf_start; 130 if (w_size == 0) 131 return 0; 132 133 if (dfu_hash_algo) 134 dfu_hash_algo->hash_update(dfu_hash_algo, &dfu->crc, 135 dfu->i_buf_start, w_size, 0); 136 137 ret = dfu->write_medium(dfu, dfu->offset, dfu->i_buf_start, &w_size); 138 if (ret) 139 debug("%s: Write error!\n", __func__); 140 141 /* point back */ 142 dfu->i_buf = dfu->i_buf_start; 143 144 /* update offset */ 145 dfu->offset += w_size; 146 147 puts("#"); 148 149 return ret; 150 } 151 152 void dfu_write_transaction_cleanup(struct dfu_entity *dfu) 153 { 154 /* clear everything */ 155 dfu_free_buf(); 156 dfu->crc = 0; 157 dfu->offset = 0; 158 dfu->i_blk_seq_num = 0; 159 dfu->i_buf_start = dfu_buf; 160 dfu->i_buf_end = dfu_buf; 161 dfu->i_buf = dfu->i_buf_start; 162 dfu->inited = 0; 163 } 164 165 int dfu_flush(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) 166 { 167 int ret = 0; 168 169 ret = dfu_write_buffer_drain(dfu); 170 if (ret) 171 return ret; 172 173 if (dfu->flush_medium) 174 ret = dfu->flush_medium(dfu); 175 176 if (dfu_hash_algo) 177 printf("\nDFU complete %s: 0x%08x\n", dfu_hash_algo->name, 178 dfu->crc); 179 180 dfu_write_transaction_cleanup(dfu); 181 182 return ret; 183 } 184 185 int dfu_write(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) 186 { 187 int ret; 188 189 debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x offset: 0x%llx bufoffset: 0x%x\n", 190 __func__, dfu->name, buf, size, blk_seq_num, dfu->offset, 191 dfu->i_buf - dfu->i_buf_start); 192 193 if (!dfu->inited) { 194 /* initial state */ 195 dfu->crc = 0; 196 dfu->offset = 0; 197 dfu->bad_skip = 0; 198 dfu->i_blk_seq_num = 0; 199 dfu->i_buf_start = dfu_get_buf(dfu); 200 if (dfu->i_buf_start == NULL) 201 return -ENOMEM; 202 dfu->i_buf_end = dfu_get_buf(dfu) + dfu_buf_size; 203 dfu->i_buf = dfu->i_buf_start; 204 205 dfu->inited = 1; 206 } 207 208 if (dfu->i_blk_seq_num != blk_seq_num) { 209 printf("%s: Wrong sequence number! [%d] [%d]\n", 210 __func__, dfu->i_blk_seq_num, blk_seq_num); 211 dfu_write_transaction_cleanup(dfu); 212 return -1; 213 } 214 215 /* DFU 1.1 standard says: 216 * The wBlockNum field is a block sequence number. It increments each 217 * time a block is transferred, wrapping to zero from 65,535. It is used 218 * to provide useful context to the DFU loader in the device." 219 * 220 * This means that it's a 16 bit counter that roll-overs at 221 * 0xffff -> 0x0000. By having a typical 4K transfer block 222 * we roll-over at exactly 256MB. Not very fun to debug. 223 * 224 * Handling rollover, and having an inited variable, 225 * makes things work. 226 */ 227 228 /* handle rollover */ 229 dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff; 230 231 /* flush buffer if overflow */ 232 if ((dfu->i_buf + size) > dfu->i_buf_end) { 233 ret = dfu_write_buffer_drain(dfu); 234 if (ret) { 235 dfu_write_transaction_cleanup(dfu); 236 return ret; 237 } 238 } 239 240 /* we should be in buffer now (if not then size too large) */ 241 if ((dfu->i_buf + size) > dfu->i_buf_end) { 242 error("Buffer overflow! (0x%p + 0x%x > 0x%p)\n", dfu->i_buf, 243 size, dfu->i_buf_end); 244 dfu_write_transaction_cleanup(dfu); 245 return -1; 246 } 247 248 memcpy(dfu->i_buf, buf, size); 249 dfu->i_buf += size; 250 251 /* if end or if buffer full flush */ 252 if (size == 0 || (dfu->i_buf + size) > dfu->i_buf_end) { 253 ret = dfu_write_buffer_drain(dfu); 254 if (ret) { 255 dfu_write_transaction_cleanup(dfu); 256 return ret; 257 } 258 } 259 260 return 0; 261 } 262 263 static int dfu_read_buffer_fill(struct dfu_entity *dfu, void *buf, int size) 264 { 265 long chunk; 266 int ret, readn; 267 268 readn = 0; 269 while (size > 0) { 270 /* get chunk that can be read */ 271 chunk = min(size, dfu->b_left); 272 /* consume */ 273 if (chunk > 0) { 274 memcpy(buf, dfu->i_buf, chunk); 275 if (dfu_hash_algo) 276 dfu_hash_algo->hash_update(dfu_hash_algo, 277 &dfu->crc, buf, 278 chunk, 0); 279 280 dfu->i_buf += chunk; 281 dfu->b_left -= chunk; 282 size -= chunk; 283 buf += chunk; 284 readn += chunk; 285 } 286 287 /* all done */ 288 if (size > 0) { 289 /* no more to read */ 290 if (dfu->r_left == 0) 291 break; 292 293 dfu->i_buf = dfu->i_buf_start; 294 dfu->b_left = dfu->i_buf_end - dfu->i_buf_start; 295 296 /* got to read, but buffer is empty */ 297 if (dfu->b_left > dfu->r_left) 298 dfu->b_left = dfu->r_left; 299 ret = dfu->read_medium(dfu, dfu->offset, dfu->i_buf, 300 &dfu->b_left); 301 if (ret != 0) { 302 debug("%s: Read error!\n", __func__); 303 return ret; 304 } 305 dfu->offset += dfu->b_left; 306 dfu->r_left -= dfu->b_left; 307 308 puts("#"); 309 } 310 } 311 312 return readn; 313 } 314 315 int dfu_read(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) 316 { 317 int ret = 0; 318 319 debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x i_buf: 0x%p\n", 320 __func__, dfu->name, buf, size, blk_seq_num, dfu->i_buf); 321 322 if (!dfu->inited) { 323 dfu->i_buf_start = dfu_get_buf(dfu); 324 if (dfu->i_buf_start == NULL) 325 return -ENOMEM; 326 327 dfu->r_left = dfu->get_medium_size(dfu); 328 if (dfu->r_left < 0) 329 return dfu->r_left; 330 switch (dfu->layout) { 331 case DFU_RAW_ADDR: 332 case DFU_RAM_ADDR: 333 break; 334 default: 335 if (dfu->r_left > dfu_buf_size) { 336 printf("%s: File too big for buffer\n", 337 __func__); 338 return -EOVERFLOW; 339 } 340 } 341 342 debug("%s: %s %ld [B]\n", __func__, dfu->name, dfu->r_left); 343 344 dfu->i_blk_seq_num = 0; 345 dfu->crc = 0; 346 dfu->offset = 0; 347 dfu->i_buf_end = dfu_get_buf(dfu) + dfu_buf_size; 348 dfu->i_buf = dfu->i_buf_start; 349 dfu->b_left = 0; 350 351 dfu->bad_skip = 0; 352 353 dfu->inited = 1; 354 } 355 356 if (dfu->i_blk_seq_num != blk_seq_num) { 357 printf("%s: Wrong sequence number! [%d] [%d]\n", 358 __func__, dfu->i_blk_seq_num, blk_seq_num); 359 return -1; 360 } 361 /* handle rollover */ 362 dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff; 363 364 ret = dfu_read_buffer_fill(dfu, buf, size); 365 if (ret < 0) { 366 printf("%s: Failed to fill buffer\n", __func__); 367 return -1; 368 } 369 370 if (ret < size) { 371 if (dfu_hash_algo) 372 debug("%s: %s %s: 0x%x\n", __func__, dfu->name, 373 dfu_hash_algo->name, dfu->crc); 374 puts("\nUPLOAD ... done\nCtrl+C to exit ...\n"); 375 376 dfu_free_buf(); 377 dfu->i_blk_seq_num = 0; 378 dfu->crc = 0; 379 dfu->offset = 0; 380 dfu->i_buf_start = dfu_buf; 381 dfu->i_buf_end = dfu_buf; 382 dfu->i_buf = dfu->i_buf_start; 383 dfu->b_left = 0; 384 385 dfu->bad_skip = 0; 386 387 dfu->inited = 0; 388 } 389 390 return ret; 391 } 392 393 static int dfu_fill_entity(struct dfu_entity *dfu, char *s, int alt, 394 char *interface, char *devstr) 395 { 396 char *st; 397 398 debug("%s: %s interface: %s dev: %s\n", __func__, s, interface, devstr); 399 st = strsep(&s, " "); 400 strcpy(dfu->name, st); 401 402 dfu->alt = alt; 403 dfu->max_buf_size = 0; 404 dfu->free_entity = NULL; 405 406 /* Specific for mmc device */ 407 if (strcmp(interface, "mmc") == 0) { 408 if (dfu_fill_entity_mmc(dfu, devstr, s)) 409 return -1; 410 } else if (strcmp(interface, "nand") == 0) { 411 if (dfu_fill_entity_nand(dfu, devstr, s)) 412 return -1; 413 } else if (strcmp(interface, "ram") == 0) { 414 if (dfu_fill_entity_ram(dfu, devstr, s)) 415 return -1; 416 } else if (strcmp(interface, "sf") == 0) { 417 if (dfu_fill_entity_sf(dfu, devstr, s)) 418 return -1; 419 } else { 420 printf("%s: Device %s not (yet) supported!\n", 421 __func__, interface); 422 return -1; 423 } 424 425 return 0; 426 } 427 428 void dfu_free_entities(void) 429 { 430 struct dfu_entity *dfu, *p, *t = NULL; 431 432 list_for_each_entry_safe_reverse(dfu, p, &dfu_list, list) { 433 list_del(&dfu->list); 434 if (dfu->free_entity) 435 dfu->free_entity(dfu); 436 t = dfu; 437 } 438 if (t) 439 free(t); 440 INIT_LIST_HEAD(&dfu_list); 441 442 alt_num_cnt = 0; 443 } 444 445 int dfu_config_entities(char *env, char *interface, char *devstr) 446 { 447 struct dfu_entity *dfu; 448 int i, ret; 449 char *s; 450 451 dfu_alt_num = dfu_find_alt_num(env); 452 debug("%s: dfu_alt_num=%d\n", __func__, dfu_alt_num); 453 454 dfu_hash_algo = NULL; 455 s = dfu_get_hash_algo(); 456 if (s) { 457 ret = hash_lookup_algo(s, &dfu_hash_algo); 458 if (ret) 459 error("Hash algorithm %s not supported\n", s); 460 } 461 462 dfu = calloc(sizeof(*dfu), dfu_alt_num); 463 if (!dfu) 464 return -1; 465 for (i = 0; i < dfu_alt_num; i++) { 466 467 s = strsep(&env, ";"); 468 ret = dfu_fill_entity(&dfu[i], s, alt_num_cnt, interface, 469 devstr); 470 if (ret) 471 return -1; 472 473 list_add_tail(&dfu[i].list, &dfu_list); 474 alt_num_cnt++; 475 } 476 477 return 0; 478 } 479 480 const char *dfu_get_dev_type(enum dfu_device_type t) 481 { 482 const char *dev_t[] = {NULL, "eMMC", "OneNAND", "NAND", "RAM" }; 483 return dev_t[t]; 484 } 485 486 const char *dfu_get_layout(enum dfu_layout l) 487 { 488 const char *dfu_layout[] = {NULL, "RAW_ADDR", "FAT", "EXT2", 489 "EXT3", "EXT4", "RAM_ADDR" }; 490 return dfu_layout[l]; 491 } 492 493 void dfu_show_entities(void) 494 { 495 struct dfu_entity *dfu; 496 497 puts("DFU alt settings list:\n"); 498 499 list_for_each_entry(dfu, &dfu_list, list) { 500 printf("dev: %s alt: %d name: %s layout: %s\n", 501 dfu_get_dev_type(dfu->dev_type), dfu->alt, 502 dfu->name, dfu_get_layout(dfu->layout)); 503 } 504 } 505 506 int dfu_get_alt_number(void) 507 { 508 return dfu_alt_num; 509 } 510 511 struct dfu_entity *dfu_get_entity(int alt) 512 { 513 struct dfu_entity *dfu; 514 515 list_for_each_entry(dfu, &dfu_list, list) { 516 if (dfu->alt == alt) 517 return dfu; 518 } 519 520 return NULL; 521 } 522 523 int dfu_get_alt(char *name) 524 { 525 struct dfu_entity *dfu; 526 527 list_for_each_entry(dfu, &dfu_list, list) { 528 if (!strncmp(dfu->name, name, strlen(dfu->name))) 529 return dfu->alt; 530 } 531 532 return -ENODEV; 533 } 534