linux/drivers/mtd/mtdpart.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Simple MTD partitioning layer
   4 *
   5 * Copyright © 2000 Nicolas Pitre <nico@fluxnic.net>
   6 * Copyright © 2002 Thomas Gleixner <gleixner@linutronix.de>
   7 * Copyright © 2000-2010 David Woodhouse <dwmw2@infradead.org>
   8 */
   9
  10#include <linux/module.h>
  11#include <linux/types.h>
  12#include <linux/kernel.h>
  13#include <linux/slab.h>
  14#include <linux/list.h>
  15#include <linux/kmod.h>
  16#include <linux/mtd/mtd.h>
  17#include <linux/mtd/partitions.h>
  18#include <linux/err.h>
  19#include <linux/of.h>
  20
  21#include "mtdcore.h"
  22
  23/*
  24 * MTD methods which simply translate the effective address and pass through
  25 * to the _real_ device.
  26 */
  27
  28static inline void free_partition(struct mtd_info *mtd)
  29{
  30        kfree(mtd->name);
  31        kfree(mtd);
  32}
  33
  34static struct mtd_info *allocate_partition(struct mtd_info *parent,
  35                                           const struct mtd_partition *part,
  36                                           int partno, uint64_t cur_offset)
  37{
  38        struct mtd_info *master = mtd_get_master(parent);
  39        int wr_alignment = (parent->flags & MTD_NO_ERASE) ?
  40                           master->writesize : master->erasesize;
  41        u64 parent_size = mtd_is_partition(parent) ?
  42                          parent->part.size : parent->size;
  43        struct mtd_info *child;
  44        u32 remainder;
  45        char *name;
  46        u64 tmp;
  47
  48        /* allocate the partition structure */
  49        child = kzalloc(sizeof(*child), GFP_KERNEL);
  50        name = kstrdup(part->name, GFP_KERNEL);
  51        if (!name || !child) {
  52                printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n",
  53                       parent->name);
  54                kfree(name);
  55                kfree(child);
  56                return ERR_PTR(-ENOMEM);
  57        }
  58
  59        /* set up the MTD object for this partition */
  60        child->type = parent->type;
  61        child->part.flags = parent->flags & ~part->mask_flags;
  62        child->part.flags |= part->add_flags;
  63        child->flags = child->part.flags;
  64        child->part.size = part->size;
  65        child->writesize = parent->writesize;
  66        child->writebufsize = parent->writebufsize;
  67        child->oobsize = parent->oobsize;
  68        child->oobavail = parent->oobavail;
  69        child->subpage_sft = parent->subpage_sft;
  70
  71        child->name = name;
  72        child->owner = parent->owner;
  73
  74        /* NOTE: Historically, we didn't arrange MTDs as a tree out of
  75         * concern for showing the same data in multiple partitions.
  76         * However, it is very useful to have the master node present,
  77         * so the MTD_PARTITIONED_MASTER option allows that. The master
  78         * will have device nodes etc only if this is set, so make the
  79         * parent conditional on that option. Note, this is a way to
  80         * distinguish between the parent and its partitions in sysfs.
  81         */
  82        child->dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ?
  83                            &parent->dev : parent->dev.parent;
  84        child->dev.of_node = part->of_node;
  85        child->parent = parent;
  86        child->part.offset = part->offset;
  87        INIT_LIST_HEAD(&child->partitions);
  88
  89        if (child->part.offset == MTDPART_OFS_APPEND)
  90                child->part.offset = cur_offset;
  91        if (child->part.offset == MTDPART_OFS_NXTBLK) {
  92                tmp = cur_offset;
  93                child->part.offset = cur_offset;
  94                remainder = do_div(tmp, wr_alignment);
  95                if (remainder) {
  96                        child->part.offset += wr_alignment - remainder;
  97                        printk(KERN_NOTICE "Moving partition %d: "
  98                               "0x%012llx -> 0x%012llx\n", partno,
  99                               (unsigned long long)cur_offset,
 100                               child->part.offset);
 101                }
 102        }
 103        if (child->part.offset == MTDPART_OFS_RETAIN) {
 104                child->part.offset = cur_offset;
 105                if (parent_size - child->part.offset >= child->part.size) {
 106                        child->part.size = parent_size - child->part.offset -
 107                                           child->part.size;
 108                } else {
 109                        printk(KERN_ERR "mtd partition \"%s\" doesn't have enough space: %#llx < %#llx, disabled\n",
 110                                part->name, parent_size - child->part.offset,
 111                                child->part.size);
 112                        /* register to preserve ordering */
 113                        goto out_register;
 114                }
 115        }
 116        if (child->part.size == MTDPART_SIZ_FULL)
 117                child->part.size = parent_size - child->part.offset;
 118
 119        printk(KERN_NOTICE "0x%012llx-0x%012llx : \"%s\"\n",
 120               child->part.offset, child->part.offset + child->part.size,
 121               child->name);
 122
 123        /* let's do some sanity checks */
 124        if (child->part.offset >= parent_size) {
 125                /* let's register it anyway to preserve ordering */
 126                child->part.offset = 0;
 127                child->part.size = 0;
 128
 129                /* Initialize ->erasesize to make add_mtd_device() happy. */
 130                child->erasesize = parent->erasesize;
 131                printk(KERN_ERR"mtd: partition \"%s\" is out of reach -- disabled\n",
 132                        part->name);
 133                goto out_register;
 134        }
 135        if (child->part.offset + child->part.size > parent->size) {
 136                child->part.size = parent_size - child->part.offset;
 137                printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#llx\n",
 138                        part->name, parent->name, child->part.size);
 139        }
 140
 141        if (parent->numeraseregions > 1) {
 142                /* Deal with variable erase size stuff */
 143                int i, max = parent->numeraseregions;
 144                u64 end = child->part.offset + child->part.size;
 145                struct mtd_erase_region_info *regions = parent->eraseregions;
 146
 147                /* Find the first erase regions which is part of this
 148                 * partition. */
 149                for (i = 0; i < max && regions[i].offset <= child->part.offset;
 150                     i++)
 151                        ;
 152                /* The loop searched for the region _behind_ the first one */
 153                if (i > 0)
 154                        i--;
 155
 156                /* Pick biggest erasesize */
 157                for (; i < max && regions[i].offset < end; i++) {
 158                        if (child->erasesize < regions[i].erasesize)
 159                                child->erasesize = regions[i].erasesize;
 160                }
 161                BUG_ON(child->erasesize == 0);
 162        } else {
 163                /* Single erase size */
 164                child->erasesize = master->erasesize;
 165        }
 166
 167        /*
 168         * Child erasesize might differ from the parent one if the parent
 169         * exposes several regions with different erasesize. Adjust
 170         * wr_alignment accordingly.
 171         */
 172        if (!(child->flags & MTD_NO_ERASE))
 173                wr_alignment = child->erasesize;
 174
 175        tmp = mtd_get_master_ofs(child, 0);
 176        remainder = do_div(tmp, wr_alignment);
 177        if ((child->flags & MTD_WRITEABLE) && remainder) {
 178                /* Doesn't start on a boundary of major erase size */
 179                /* FIXME: Let it be writable if it is on a boundary of
 180                 * _minor_ erase size though */
 181                child->flags &= ~MTD_WRITEABLE;
 182                printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
 183                        part->name);
 184        }
 185
 186        tmp = mtd_get_master_ofs(child, 0) + child->part.size;
 187        remainder = do_div(tmp, wr_alignment);
 188        if ((child->flags & MTD_WRITEABLE) && remainder) {
 189                child->flags &= ~MTD_WRITEABLE;
 190                printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
 191                        part->name);
 192        }
 193
 194        child->size = child->part.size;
 195        child->ecc_step_size = parent->ecc_step_size;
 196        child->ecc_strength = parent->ecc_strength;
 197        child->bitflip_threshold = parent->bitflip_threshold;
 198
 199        if (master->_block_isbad) {
 200                uint64_t offs = 0;
 201
 202                while (offs < child->part.size) {
 203                        if (mtd_block_isreserved(child, offs))
 204                                child->ecc_stats.bbtblocks++;
 205                        else if (mtd_block_isbad(child, offs))
 206                                child->ecc_stats.badblocks++;
 207                        offs += child->erasesize;
 208                }
 209        }
 210
 211out_register:
 212        return child;
 213}
 214
 215static ssize_t offset_show(struct device *dev,
 216                           struct device_attribute *attr, char *buf)
 217{
 218        struct mtd_info *mtd = dev_get_drvdata(dev);
 219
 220        return sysfs_emit(buf, "%lld\n", mtd->part.offset);
 221}
 222static DEVICE_ATTR_RO(offset);  /* mtd partition offset */
 223
 224static const struct attribute *mtd_partition_attrs[] = {
 225        &dev_attr_offset.attr,
 226        NULL
 227};
 228
 229static int mtd_add_partition_attrs(struct mtd_info *new)
 230{
 231        int ret = sysfs_create_files(&new->dev.kobj, mtd_partition_attrs);
 232        if (ret)
 233                printk(KERN_WARNING
 234                       "mtd: failed to create partition attrs, err=%d\n", ret);
 235        return ret;
 236}
 237
 238int mtd_add_partition(struct mtd_info *parent, const char *name,
 239                      long long offset, long long length)
 240{
 241        struct mtd_info *master = mtd_get_master(parent);
 242        u64 parent_size = mtd_is_partition(parent) ?
 243                          parent->part.size : parent->size;
 244        struct mtd_partition part;
 245        struct mtd_info *child;
 246        int ret = 0;
 247
 248        /* the direct offset is expected */
 249        if (offset == MTDPART_OFS_APPEND ||
 250            offset == MTDPART_OFS_NXTBLK)
 251                return -EINVAL;
 252
 253        if (length == MTDPART_SIZ_FULL)
 254                length = parent_size - offset;
 255
 256        if (length <= 0)
 257                return -EINVAL;
 258
 259        memset(&part, 0, sizeof(part));
 260        part.name = name;
 261        part.size = length;
 262        part.offset = offset;
 263
 264        child = allocate_partition(parent, &part, -1, offset);
 265        if (IS_ERR(child))
 266                return PTR_ERR(child);
 267
 268        mutex_lock(&master->master.partitions_lock);
 269        list_add_tail(&child->part.node, &parent->partitions);
 270        mutex_unlock(&master->master.partitions_lock);
 271
 272        ret = add_mtd_device(child);
 273        if (ret)
 274                goto err_remove_part;
 275
 276        mtd_add_partition_attrs(child);
 277
 278        return 0;
 279
 280err_remove_part:
 281        mutex_lock(&master->master.partitions_lock);
 282        list_del(&child->part.node);
 283        mutex_unlock(&master->master.partitions_lock);
 284
 285        free_partition(child);
 286
 287        return ret;
 288}
 289EXPORT_SYMBOL_GPL(mtd_add_partition);
 290
 291/**
 292 * __mtd_del_partition - delete MTD partition
 293 *
 294 * @mtd: MTD structure to be deleted
 295 *
 296 * This function must be called with the partitions mutex locked.
 297 */
 298static int __mtd_del_partition(struct mtd_info *mtd)
 299{
 300        struct mtd_info *child, *next;
 301        int err;
 302
 303        list_for_each_entry_safe(child, next, &mtd->partitions, part.node) {
 304                err = __mtd_del_partition(child);
 305                if (err)
 306                        return err;
 307        }
 308
 309        sysfs_remove_files(&mtd->dev.kobj, mtd_partition_attrs);
 310
 311        err = del_mtd_device(mtd);
 312        if (err)
 313                return err;
 314
 315        list_del(&child->part.node);
 316        free_partition(mtd);
 317
 318        return 0;
 319}
 320
 321/*
 322 * This function unregisters and destroy all slave MTD objects which are
 323 * attached to the given MTD object, recursively.
 324 */
 325static int __del_mtd_partitions(struct mtd_info *mtd)
 326{
 327        struct mtd_info *child, *next;
 328        LIST_HEAD(tmp_list);
 329        int ret, err = 0;
 330
 331        list_for_each_entry_safe(child, next, &mtd->partitions, part.node) {
 332                if (mtd_has_partitions(child))
 333                        __del_mtd_partitions(child);
 334
 335                pr_info("Deleting %s MTD partition\n", child->name);
 336                ret = del_mtd_device(child);
 337                if (ret < 0) {
 338                        pr_err("Error when deleting partition \"%s\" (%d)\n",
 339                               child->name, ret);
 340                        err = ret;
 341                        continue;
 342                }
 343
 344                list_del(&child->part.node);
 345                free_partition(child);
 346        }
 347
 348        return err;
 349}
 350
 351int del_mtd_partitions(struct mtd_info *mtd)
 352{
 353        struct mtd_info *master = mtd_get_master(mtd);
 354        int ret;
 355
 356        pr_info("Deleting MTD partitions on \"%s\":\n", mtd->name);
 357
 358        mutex_lock(&master->master.partitions_lock);
 359        ret = __del_mtd_partitions(mtd);
 360        mutex_unlock(&master->master.partitions_lock);
 361
 362        return ret;
 363}
 364
 365int mtd_del_partition(struct mtd_info *mtd, int partno)
 366{
 367        struct mtd_info *child, *master = mtd_get_master(mtd);
 368        int ret = -EINVAL;
 369
 370        mutex_lock(&master->master.partitions_lock);
 371        list_for_each_entry(child, &mtd->partitions, part.node) {
 372                if (child->index == partno) {
 373                        ret = __mtd_del_partition(child);
 374                        break;
 375                }
 376        }
 377        mutex_unlock(&master->master.partitions_lock);
 378
 379        return ret;
 380}
 381EXPORT_SYMBOL_GPL(mtd_del_partition);
 382
 383/*
 384 * This function, given a parent MTD object and a partition table, creates
 385 * and registers the child MTD objects which are bound to the parent according
 386 * to the partition definitions.
 387 *
 388 * For historical reasons, this function's caller only registers the parent
 389 * if the MTD_PARTITIONED_MASTER config option is set.
 390 */
 391
 392int add_mtd_partitions(struct mtd_info *parent,
 393                       const struct mtd_partition *parts,
 394                       int nbparts)
 395{
 396        struct mtd_info *child, *master = mtd_get_master(parent);
 397        uint64_t cur_offset = 0;
 398        int i, ret;
 399
 400        printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n",
 401               nbparts, parent->name);
 402
 403        for (i = 0; i < nbparts; i++) {
 404                child = allocate_partition(parent, parts + i, i, cur_offset);
 405                if (IS_ERR(child)) {
 406                        ret = PTR_ERR(child);
 407                        goto err_del_partitions;
 408                }
 409
 410                mutex_lock(&master->master.partitions_lock);
 411                list_add_tail(&child->part.node, &parent->partitions);
 412                mutex_unlock(&master->master.partitions_lock);
 413
 414                ret = add_mtd_device(child);
 415                if (ret) {
 416                        mutex_lock(&master->master.partitions_lock);
 417                        list_del(&child->part.node);
 418                        mutex_unlock(&master->master.partitions_lock);
 419
 420                        free_partition(child);
 421                        goto err_del_partitions;
 422                }
 423
 424                mtd_add_partition_attrs(child);
 425
 426                /* Look for subpartitions */
 427                parse_mtd_partitions(child, parts[i].types, NULL);
 428
 429                cur_offset = child->part.offset + child->part.size;
 430        }
 431
 432        return 0;
 433
 434err_del_partitions:
 435        del_mtd_partitions(master);
 436
 437        return ret;
 438}
 439
 440static DEFINE_SPINLOCK(part_parser_lock);
 441static LIST_HEAD(part_parsers);
 442
 443static struct mtd_part_parser *mtd_part_parser_get(const char *name)
 444{
 445        struct mtd_part_parser *p, *ret = NULL;
 446
 447        spin_lock(&part_parser_lock);
 448
 449        list_for_each_entry(p, &part_parsers, list)
 450                if (!strcmp(p->name, name) && try_module_get(p->owner)) {
 451                        ret = p;
 452                        break;
 453                }
 454
 455        spin_unlock(&part_parser_lock);
 456
 457        return ret;
 458}
 459
 460static inline void mtd_part_parser_put(const struct mtd_part_parser *p)
 461{
 462        module_put(p->owner);
 463}
 464
 465/*
 466 * Many partition parsers just expected the core to kfree() all their data in
 467 * one chunk. Do that by default.
 468 */
 469static void mtd_part_parser_cleanup_default(const struct mtd_partition *pparts,
 470                                            int nr_parts)
 471{
 472        kfree(pparts);
 473}
 474
 475int __register_mtd_parser(struct mtd_part_parser *p, struct module *owner)
 476{
 477        p->owner = owner;
 478
 479        if (!p->cleanup)
 480                p->cleanup = &mtd_part_parser_cleanup_default;
 481
 482        spin_lock(&part_parser_lock);
 483        list_add(&p->list, &part_parsers);
 484        spin_unlock(&part_parser_lock);
 485
 486        return 0;
 487}
 488EXPORT_SYMBOL_GPL(__register_mtd_parser);
 489
 490void deregister_mtd_parser(struct mtd_part_parser *p)
 491{
 492        spin_lock(&part_parser_lock);
 493        list_del(&p->list);
 494        spin_unlock(&part_parser_lock);
 495}
 496EXPORT_SYMBOL_GPL(deregister_mtd_parser);
 497
 498/*
 499 * Do not forget to update 'parse_mtd_partitions()' kerneldoc comment if you
 500 * are changing this array!
 501 */
 502static const char * const default_mtd_part_types[] = {
 503        "cmdlinepart",
 504        "ofpart",
 505        NULL
 506};
 507
 508/* Check DT only when looking for subpartitions. */
 509static const char * const default_subpartition_types[] = {
 510        "ofpart",
 511        NULL
 512};
 513
 514static int mtd_part_do_parse(struct mtd_part_parser *parser,
 515                             struct mtd_info *master,
 516                             struct mtd_partitions *pparts,
 517                             struct mtd_part_parser_data *data)
 518{
 519        int ret;
 520
 521        ret = (*parser->parse_fn)(master, &pparts->parts, data);
 522        pr_debug("%s: parser %s: %i\n", master->name, parser->name, ret);
 523        if (ret <= 0)
 524                return ret;
 525
 526        pr_notice("%d %s partitions found on MTD device %s\n", ret,
 527                  parser->name, master->name);
 528
 529        pparts->nr_parts = ret;
 530        pparts->parser = parser;
 531
 532        return ret;
 533}
 534
 535/**
 536 * mtd_part_get_compatible_parser - find MTD parser by a compatible string
 537 *
 538 * @compat: compatible string describing partitions in a device tree
 539 *
 540 * MTD parsers can specify supported partitions by providing a table of
 541 * compatibility strings. This function finds a parser that advertises support
 542 * for a passed value of "compatible".
 543 */
 544static struct mtd_part_parser *mtd_part_get_compatible_parser(const char *compat)
 545{
 546        struct mtd_part_parser *p, *ret = NULL;
 547
 548        spin_lock(&part_parser_lock);
 549
 550        list_for_each_entry(p, &part_parsers, list) {
 551                const struct of_device_id *matches;
 552
 553                matches = p->of_match_table;
 554                if (!matches)
 555                        continue;
 556
 557                for (; matches->compatible[0]; matches++) {
 558                        if (!strcmp(matches->compatible, compat) &&
 559                            try_module_get(p->owner)) {
 560                                ret = p;
 561                                break;
 562                        }
 563                }
 564
 565                if (ret)
 566                        break;
 567        }
 568
 569        spin_unlock(&part_parser_lock);
 570
 571        return ret;
 572}
 573
 574static int mtd_part_of_parse(struct mtd_info *master,
 575                             struct mtd_partitions *pparts)
 576{
 577        struct mtd_part_parser *parser;
 578        struct device_node *np;
 579        struct property *prop;
 580        const char *compat;
 581        const char *fixed = "fixed-partitions";
 582        int ret, err = 0;
 583
 584        np = mtd_get_of_node(master);
 585        if (mtd_is_partition(master))
 586                of_node_get(np);
 587        else
 588                np = of_get_child_by_name(np, "partitions");
 589
 590        of_property_for_each_string(np, "compatible", prop, compat) {
 591                parser = mtd_part_get_compatible_parser(compat);
 592                if (!parser)
 593                        continue;
 594                ret = mtd_part_do_parse(parser, master, pparts, NULL);
 595                if (ret > 0) {
 596                        of_node_put(np);
 597                        return ret;
 598                }
 599                mtd_part_parser_put(parser);
 600                if (ret < 0 && !err)
 601                        err = ret;
 602        }
 603        of_node_put(np);
 604
 605        /*
 606         * For backward compatibility we have to try the "fixed-partitions"
 607         * parser. It supports old DT format with partitions specified as a
 608         * direct subnodes of a flash device DT node without any compatibility
 609         * specified we could match.
 610         */
 611        parser = mtd_part_parser_get(fixed);
 612        if (!parser && !request_module("%s", fixed))
 613                parser = mtd_part_parser_get(fixed);
 614        if (parser) {
 615                ret = mtd_part_do_parse(parser, master, pparts, NULL);
 616                if (ret > 0)
 617                        return ret;
 618                mtd_part_parser_put(parser);
 619                if (ret < 0 && !err)
 620                        err = ret;
 621        }
 622
 623        return err;
 624}
 625
 626/**
 627 * parse_mtd_partitions - parse and register MTD partitions
 628 *
 629 * @master: the master partition (describes whole MTD device)
 630 * @types: names of partition parsers to try or %NULL
 631 * @data: MTD partition parser-specific data
 632 *
 633 * This function tries to find & register partitions on MTD device @master. It
 634 * uses MTD partition parsers, specified in @types. However, if @types is %NULL,
 635 * then the default list of parsers is used. The default list contains only the
 636 * "cmdlinepart" and "ofpart" parsers ATM.
 637 * Note: If there are more then one parser in @types, the kernel only takes the
 638 * partitions parsed out by the first parser.
 639 *
 640 * This function may return:
 641 * o a negative error code in case of failure
 642 * o number of found partitions otherwise
 643 */
 644int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
 645                         struct mtd_part_parser_data *data)
 646{
 647        struct mtd_partitions pparts = { };
 648        struct mtd_part_parser *parser;
 649        int ret, err = 0;
 650
 651        if (!types)
 652                types = mtd_is_partition(master) ? default_subpartition_types :
 653                        default_mtd_part_types;
 654
 655        for ( ; *types; types++) {
 656                /*
 657                 * ofpart is a special type that means OF partitioning info
 658                 * should be used. It requires a bit different logic so it is
 659                 * handled in a separated function.
 660                 */
 661                if (!strcmp(*types, "ofpart")) {
 662                        ret = mtd_part_of_parse(master, &pparts);
 663                } else {
 664                        pr_debug("%s: parsing partitions %s\n", master->name,
 665                                 *types);
 666                        parser = mtd_part_parser_get(*types);
 667                        if (!parser && !request_module("%s", *types))
 668                                parser = mtd_part_parser_get(*types);
 669                        pr_debug("%s: got parser %s\n", master->name,
 670                                parser ? parser->name : NULL);
 671                        if (!parser)
 672                                continue;
 673                        ret = mtd_part_do_parse(parser, master, &pparts, data);
 674                        if (ret <= 0)
 675                                mtd_part_parser_put(parser);
 676                }
 677                /* Found partitions! */
 678                if (ret > 0) {
 679                        err = add_mtd_partitions(master, pparts.parts,
 680                                                 pparts.nr_parts);
 681                        mtd_part_parser_cleanup(&pparts);
 682                        return err ? err : pparts.nr_parts;
 683                }
 684                /*
 685                 * Stash the first error we see; only report it if no parser
 686                 * succeeds
 687                 */
 688                if (ret < 0 && !err)
 689                        err = ret;
 690        }
 691        return err;
 692}
 693
 694void mtd_part_parser_cleanup(struct mtd_partitions *parts)
 695{
 696        const struct mtd_part_parser *parser;
 697
 698        if (!parts)
 699                return;
 700
 701        parser = parts->parser;
 702        if (parser) {
 703                if (parser->cleanup)
 704                        parser->cleanup(parts->parts, parts->nr_parts);
 705
 706                mtd_part_parser_put(parser);
 707        }
 708}
 709
 710/* Returns the size of the entire flash chip */
 711uint64_t mtd_get_device_size(const struct mtd_info *mtd)
 712{
 713        struct mtd_info *master = mtd_get_master((struct mtd_info *)mtd);
 714
 715        return master->size;
 716}
 717EXPORT_SYMBOL_GPL(mtd_get_device_size);
 718