uboot/drivers/dfu/dfu.c
<<
>>
Prefs
   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
  20static LIST_HEAD(dfu_list);
  21static int dfu_alt_num;
  22static int alt_num_cnt;
  23static struct hash_algo *dfu_hash_algo;
  24
  25/*
  26 * The purpose of the dfu_usb_get_reset() function is to
  27 * provide information if after USB_DETACH request
  28 * being sent the dfu-util performed reset of USB
  29 * bus.
  30 *
  31 * Described behaviour is the only way to distinct if
  32 * user has typed -e (detach) or -R (reset) when invoking
  33 * dfu-util command.
  34 *
  35 */
  36__weak bool dfu_usb_get_reset(void)
  37{
  38        return true;
  39}
  40
  41static int dfu_find_alt_num(const char *s)
  42{
  43        int i = 0;
  44
  45        for (; *s; s++)
  46                if (*s == ';')
  47                        i++;
  48
  49        return ++i;
  50}
  51
  52int dfu_init_env_entities(char *interface, char *devstr)
  53{
  54        const char *str_env;
  55        char *env_bkp;
  56        int ret;
  57
  58#ifdef CONFIG_SET_DFU_ALT_INFO
  59        set_dfu_alt_info(interface, devstr);
  60#endif
  61        str_env = getenv("dfu_alt_info");
  62        if (!str_env) {
  63                error("\"dfu_alt_info\" env variable not defined!\n");
  64                return -EINVAL;
  65        }
  66
  67        env_bkp = strdup(str_env);
  68        ret = dfu_config_entities(env_bkp, interface, devstr);
  69        if (ret) {
  70                error("DFU entities configuration failed!\n");
  71                return ret;
  72        }
  73
  74        free(env_bkp);
  75        return 0;
  76}
  77
  78static unsigned char *dfu_buf;
  79static unsigned long dfu_buf_size;
  80
  81unsigned char *dfu_free_buf(void)
  82{
  83        free(dfu_buf);
  84        dfu_buf = NULL;
  85        return dfu_buf;
  86}
  87
  88unsigned long dfu_get_buf_size(void)
  89{
  90        return dfu_buf_size;
  91}
  92
  93unsigned char *dfu_get_buf(struct dfu_entity *dfu)
  94{
  95        char *s;
  96
  97        if (dfu_buf != NULL)
  98                return dfu_buf;
  99
 100        s = getenv("dfu_bufsiz");
 101        if (s)
 102                dfu_buf_size = (unsigned long)simple_strtol(s, NULL, 0);
 103
 104        if (!s || !dfu_buf_size)
 105                dfu_buf_size = CONFIG_SYS_DFU_DATA_BUF_SIZE;
 106
 107        if (dfu->max_buf_size && dfu_buf_size > dfu->max_buf_size)
 108                dfu_buf_size = dfu->max_buf_size;
 109
 110        dfu_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, dfu_buf_size);
 111        if (dfu_buf == NULL)
 112                printf("%s: Could not memalign 0x%lx bytes\n",
 113                       __func__, dfu_buf_size);
 114
 115        return dfu_buf;
 116}
 117
 118static char *dfu_get_hash_algo(void)
 119{
 120        char *s;
 121
 122        s = getenv("dfu_hash_algo");
 123        if (!s)
 124                return NULL;
 125
 126        if (!strcmp(s, "crc32")) {
 127                debug("%s: DFU hash method: %s\n", __func__, s);
 128                return s;
 129        }
 130
 131        error("DFU hash method: %s not supported!\n", s);
 132        return NULL;
 133}
 134
 135static int dfu_write_buffer_drain(struct dfu_entity *dfu)
 136{
 137        long w_size;
 138        int ret;
 139
 140        /* flush size? */
 141        w_size = dfu->i_buf - dfu->i_buf_start;
 142        if (w_size == 0)
 143                return 0;
 144
 145        if (dfu_hash_algo)
 146                dfu_hash_algo->hash_update(dfu_hash_algo, &dfu->crc,
 147                                           dfu->i_buf_start, w_size, 0);
 148
 149        ret = dfu->write_medium(dfu, dfu->offset, dfu->i_buf_start, &w_size);
 150        if (ret)
 151                debug("%s: Write error!\n", __func__);
 152
 153        /* point back */
 154        dfu->i_buf = dfu->i_buf_start;
 155
 156        /* update offset */
 157        dfu->offset += w_size;
 158
 159        puts("#");
 160
 161        return ret;
 162}
 163
 164void dfu_write_transaction_cleanup(struct dfu_entity *dfu)
 165{
 166        /* clear everything */
 167        dfu->crc = 0;
 168        dfu->offset = 0;
 169        dfu->i_blk_seq_num = 0;
 170        dfu->i_buf_start = dfu_buf;
 171        dfu->i_buf_end = dfu_buf;
 172        dfu->i_buf = dfu->i_buf_start;
 173        dfu->inited = 0;
 174}
 175
 176int dfu_flush(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num)
 177{
 178        int ret = 0;
 179
 180        ret = dfu_write_buffer_drain(dfu);
 181        if (ret)
 182                return ret;
 183
 184        if (dfu->flush_medium)
 185                ret = dfu->flush_medium(dfu);
 186
 187        if (dfu_hash_algo)
 188                printf("\nDFU complete %s: 0x%08x\n", dfu_hash_algo->name,
 189                       dfu->crc);
 190
 191        dfu_write_transaction_cleanup(dfu);
 192
 193        return ret;
 194}
 195
 196int dfu_write(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num)
 197{
 198        int ret;
 199
 200        debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x offset: 0x%llx bufoffset: 0x%lx\n",
 201              __func__, dfu->name, buf, size, blk_seq_num, dfu->offset,
 202              (unsigned long)(dfu->i_buf - dfu->i_buf_start));
 203
 204        if (!dfu->inited) {
 205                /* initial state */
 206                dfu->crc = 0;
 207                dfu->offset = 0;
 208                dfu->bad_skip = 0;
 209                dfu->i_blk_seq_num = 0;
 210                dfu->i_buf_start = dfu_get_buf(dfu);
 211                if (dfu->i_buf_start == NULL)
 212                        return -ENOMEM;
 213                dfu->i_buf_end = dfu_get_buf(dfu) + dfu_buf_size;
 214                dfu->i_buf = dfu->i_buf_start;
 215
 216                dfu->inited = 1;
 217        }
 218
 219        if (dfu->i_blk_seq_num != blk_seq_num) {
 220                printf("%s: Wrong sequence number! [%d] [%d]\n",
 221                       __func__, dfu->i_blk_seq_num, blk_seq_num);
 222                dfu_write_transaction_cleanup(dfu);
 223                return -1;
 224        }
 225
 226        /* DFU 1.1 standard says:
 227         * The wBlockNum field is a block sequence number. It increments each
 228         * time a block is transferred, wrapping to zero from 65,535. It is used
 229         * to provide useful context to the DFU loader in the device."
 230         *
 231         * This means that it's a 16 bit counter that roll-overs at
 232         * 0xffff -> 0x0000. By having a typical 4K transfer block
 233         * we roll-over at exactly 256MB. Not very fun to debug.
 234         *
 235         * Handling rollover, and having an inited variable,
 236         * makes things work.
 237         */
 238
 239        /* handle rollover */
 240        dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff;
 241
 242        /* flush buffer if overflow */
 243        if ((dfu->i_buf + size) > dfu->i_buf_end) {
 244                ret = dfu_write_buffer_drain(dfu);
 245                if (ret) {
 246                        dfu_write_transaction_cleanup(dfu);
 247                        return ret;
 248                }
 249        }
 250
 251        /* we should be in buffer now (if not then size too large) */
 252        if ((dfu->i_buf + size) > dfu->i_buf_end) {
 253                error("Buffer overflow! (0x%p + 0x%x > 0x%p)\n", dfu->i_buf,
 254                      size, dfu->i_buf_end);
 255                dfu_write_transaction_cleanup(dfu);
 256                return -1;
 257        }
 258
 259        memcpy(dfu->i_buf, buf, size);
 260        dfu->i_buf += size;
 261
 262        /* if end or if buffer full flush */
 263        if (size == 0 || (dfu->i_buf + size) > dfu->i_buf_end) {
 264                ret = dfu_write_buffer_drain(dfu);
 265                if (ret) {
 266                        dfu_write_transaction_cleanup(dfu);
 267                        return ret;
 268                }
 269        }
 270
 271        return 0;
 272}
 273
 274static int dfu_read_buffer_fill(struct dfu_entity *dfu, void *buf, int size)
 275{
 276        long chunk;
 277        int ret, readn;
 278
 279        readn = 0;
 280        while (size > 0) {
 281                /* get chunk that can be read */
 282                chunk = min((long)size, dfu->b_left);
 283                /* consume */
 284                if (chunk > 0) {
 285                        memcpy(buf, dfu->i_buf, chunk);
 286                        if (dfu_hash_algo)
 287                                dfu_hash_algo->hash_update(dfu_hash_algo,
 288                                                           &dfu->crc, buf,
 289                                                           chunk, 0);
 290
 291                        dfu->i_buf += chunk;
 292                        dfu->b_left -= chunk;
 293                        size -= chunk;
 294                        buf += chunk;
 295                        readn += chunk;
 296                }
 297
 298                /* all done */
 299                if (size > 0) {
 300                        /* no more to read */
 301                        if (dfu->r_left == 0)
 302                                break;
 303
 304                        dfu->i_buf = dfu->i_buf_start;
 305                        dfu->b_left = dfu->i_buf_end - dfu->i_buf_start;
 306
 307                        /* got to read, but buffer is empty */
 308                        if (dfu->b_left > dfu->r_left)
 309                                dfu->b_left = dfu->r_left;
 310                        ret = dfu->read_medium(dfu, dfu->offset, dfu->i_buf,
 311                                        &dfu->b_left);
 312                        if (ret != 0) {
 313                                debug("%s: Read error!\n", __func__);
 314                                return ret;
 315                        }
 316                        dfu->offset += dfu->b_left;
 317                        dfu->r_left -= dfu->b_left;
 318
 319                        puts("#");
 320                }
 321        }
 322
 323        return readn;
 324}
 325
 326int dfu_read(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num)
 327{
 328        int ret = 0;
 329
 330        debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x i_buf: 0x%p\n",
 331               __func__, dfu->name, buf, size, blk_seq_num, dfu->i_buf);
 332
 333        if (!dfu->inited) {
 334                dfu->i_buf_start = dfu_get_buf(dfu);
 335                if (dfu->i_buf_start == NULL)
 336                        return -ENOMEM;
 337
 338                dfu->r_left = dfu->get_medium_size(dfu);
 339                if (dfu->r_left < 0)
 340                        return dfu->r_left;
 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->i_blk_seq_num = 0;
 377                dfu->crc = 0;
 378                dfu->offset = 0;
 379                dfu->i_buf_start = dfu_buf;
 380                dfu->i_buf_end = dfu_buf;
 381                dfu->i_buf = dfu->i_buf_start;
 382                dfu->b_left = 0;
 383
 384                dfu->bad_skip = 0;
 385
 386                dfu->inited = 0;
 387        }
 388
 389        return ret;
 390}
 391
 392static int dfu_fill_entity(struct dfu_entity *dfu, char *s, int alt,
 393                           char *interface, char *devstr)
 394{
 395        char *st;
 396
 397        debug("%s: %s interface: %s dev: %s\n", __func__, s, interface, devstr);
 398        st = strsep(&s, " ");
 399        strcpy(dfu->name, st);
 400
 401        dfu->alt = alt;
 402        dfu->max_buf_size = 0;
 403        dfu->free_entity = NULL;
 404
 405        /* Specific for mmc device */
 406        if (strcmp(interface, "mmc") == 0) {
 407                if (dfu_fill_entity_mmc(dfu, devstr, s))
 408                        return -1;
 409        } else if (strcmp(interface, "nand") == 0) {
 410                if (dfu_fill_entity_nand(dfu, devstr, s))
 411                        return -1;
 412        } else if (strcmp(interface, "ram") == 0) {
 413                if (dfu_fill_entity_ram(dfu, devstr, s))
 414                        return -1;
 415        } else if (strcmp(interface, "sf") == 0) {
 416                if (dfu_fill_entity_sf(dfu, devstr, s))
 417                        return -1;
 418        } else {
 419                printf("%s: Device %s not (yet) supported!\n",
 420                       __func__,  interface);
 421                return -1;
 422        }
 423        dfu_get_buf(dfu);
 424
 425        return 0;
 426}
 427
 428void dfu_free_entities(void)
 429{
 430        struct dfu_entity *dfu, *p, *t = NULL;
 431
 432        dfu_free_buf();
 433        list_for_each_entry_safe_reverse(dfu, p, &dfu_list, list) {
 434                list_del(&dfu->list);
 435                if (dfu->free_entity)
 436                        dfu->free_entity(dfu);
 437                t = dfu;
 438        }
 439        if (t)
 440                free(t);
 441        INIT_LIST_HEAD(&dfu_list);
 442
 443        alt_num_cnt = 0;
 444}
 445
 446int dfu_config_entities(char *env, char *interface, char *devstr)
 447{
 448        struct dfu_entity *dfu;
 449        int i, ret;
 450        char *s;
 451
 452        dfu_alt_num = dfu_find_alt_num(env);
 453        debug("%s: dfu_alt_num=%d\n", __func__, dfu_alt_num);
 454
 455        dfu_hash_algo = NULL;
 456        s = dfu_get_hash_algo();
 457        if (s) {
 458                ret = hash_lookup_algo(s, &dfu_hash_algo);
 459                if (ret)
 460                        error("Hash algorithm %s not supported\n", s);
 461        }
 462
 463        dfu = calloc(sizeof(*dfu), dfu_alt_num);
 464        if (!dfu)
 465                return -1;
 466        for (i = 0; i < dfu_alt_num; i++) {
 467
 468                s = strsep(&env, ";");
 469                ret = dfu_fill_entity(&dfu[i], s, alt_num_cnt, interface,
 470                                      devstr);
 471                if (ret) {
 472                        free(dfu);
 473                        return -1;
 474                }
 475
 476                list_add_tail(&dfu[i].list, &dfu_list);
 477                alt_num_cnt++;
 478        }
 479
 480        return 0;
 481}
 482
 483const char *dfu_get_dev_type(enum dfu_device_type t)
 484{
 485        const char *dev_t[] = {NULL, "eMMC", "OneNAND", "NAND", "RAM", "SF" };
 486        return dev_t[t];
 487}
 488
 489const char *dfu_get_layout(enum dfu_layout l)
 490{
 491        const char *dfu_layout[] = {NULL, "RAW_ADDR", "FAT", "EXT2",
 492                                           "EXT3", "EXT4", "RAM_ADDR" };
 493        return dfu_layout[l];
 494}
 495
 496void dfu_show_entities(void)
 497{
 498        struct dfu_entity *dfu;
 499
 500        puts("DFU alt settings list:\n");
 501
 502        list_for_each_entry(dfu, &dfu_list, list) {
 503                printf("dev: %s alt: %d name: %s layout: %s\n",
 504                       dfu_get_dev_type(dfu->dev_type), dfu->alt,
 505                       dfu->name, dfu_get_layout(dfu->layout));
 506        }
 507}
 508
 509int dfu_get_alt_number(void)
 510{
 511        return dfu_alt_num;
 512}
 513
 514struct dfu_entity *dfu_get_entity(int alt)
 515{
 516        struct dfu_entity *dfu;
 517
 518        list_for_each_entry(dfu, &dfu_list, list) {
 519                if (dfu->alt == alt)
 520                        return dfu;
 521        }
 522
 523        return NULL;
 524}
 525
 526int dfu_get_alt(char *name)
 527{
 528        struct dfu_entity *dfu;
 529        char *str;
 530
 531        list_for_each_entry(dfu, &dfu_list, list) {
 532                if (dfu->name[0] != '/') {
 533                        if (!strncmp(dfu->name, name, strlen(dfu->name)))
 534                                return dfu->alt;
 535                } else {
 536                        /*
 537                         * One must also consider absolute path
 538                         * (/boot/bin/uImage) available at dfu->name when
 539                         * compared "plain" file name (uImage)
 540                         *
 541                         * It is the case for e.g. thor gadget where lthor SW
 542                         * sends only the file name, so only the very last part
 543                         * of path must be checked for equality
 544                         */
 545
 546                        str = strstr(dfu->name, name);
 547                        if (!str)
 548                                continue;
 549
 550                        /*
 551                         * Check if matching substring is the last element of
 552                         * dfu->name (uImage)
 553                         */
 554                        if (strlen(dfu->name) ==
 555                            ((str - dfu->name) + strlen(name)))
 556                                return dfu->alt;
 557                }
 558        }
 559
 560        return -ENODEV;
 561}
 562
 563int dfu_write_from_mem_addr(struct dfu_entity *dfu, void *buf, int size)
 564{
 565        unsigned long dfu_buf_size, write, left = size;
 566        int i, ret = 0;
 567        void *dp = buf;
 568
 569        /*
 570         * Here we must call dfu_get_buf(dfu) first to be sure that dfu_buf_size
 571         * has been properly initialized - e.g. if "dfu_bufsiz" has been taken
 572         * into account.
 573         */
 574        dfu_get_buf(dfu);
 575        dfu_buf_size = dfu_get_buf_size();
 576        debug("%s: dfu buf size: %lu\n", __func__, dfu_buf_size);
 577
 578        for (i = 0; left > 0; i++) {
 579                write = min(dfu_buf_size, left);
 580
 581                debug("%s: dp: 0x%p left: %lu write: %lu\n", __func__,
 582                      dp, left, write);
 583                ret = dfu_write(dfu, dp, write, i);
 584                if (ret) {
 585                        error("DFU write failed\n");
 586                        return ret;
 587                }
 588
 589                dp += write;
 590                left -= write;
 591        }
 592
 593        ret = dfu_flush(dfu, NULL, 0, i);
 594        if (ret)
 595                error("DFU flush failed!");
 596
 597        return ret;
 598}
 599