12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2acaebfd8SDavid Howells /* MTD-based superblock management 3acaebfd8SDavid Howells * 4acaebfd8SDavid Howells * Copyright © 2001-2007 Red Hat, Inc. All Rights Reserved. 5a1452a37SDavid Woodhouse * Copyright © 2001-2010 David Woodhouse <dwmw2@infradead.org> 6a1452a37SDavid Woodhouse * 7acaebfd8SDavid Howells * Written by: David Howells <dhowells@redhat.com> 8acaebfd8SDavid Howells * David Woodhouse <dwmw2@infradead.org> 9acaebfd8SDavid Howells */ 10acaebfd8SDavid Howells 11acaebfd8SDavid Howells #include <linux/mtd/super.h> 12acaebfd8SDavid Howells #include <linux/namei.h> 13f3bcc017SPaul Gortmaker #include <linux/export.h> 14acaebfd8SDavid Howells #include <linux/ctype.h> 156de94002SJörn Engel #include <linux/slab.h> 16f83c3838SEzequiel Garcia #include <linux/major.h> 17fa06052dSJan Kara #include <linux/backing-dev.h> 180f071004SDavid Howells #include <linux/fs_context.h> 190f071004SDavid Howells #include "mtdcore.h" 200f071004SDavid Howells 210f071004SDavid Howells /* 220f071004SDavid Howells * compare superblocks to see if they're equivalent 230f071004SDavid Howells * - they are if the underlying MTD device is the same 240f071004SDavid Howells */ 250f071004SDavid Howells static int mtd_test_super(struct super_block *sb, struct fs_context *fc) 260f071004SDavid Howells { 270f071004SDavid Howells struct mtd_info *mtd = fc->sget_key; 280f071004SDavid Howells 290f071004SDavid Howells if (sb->s_mtd == fc->sget_key) { 300f071004SDavid Howells pr_debug("MTDSB: Match on device %d (\"%s\")\n", 310f071004SDavid Howells mtd->index, mtd->name); 320f071004SDavid Howells return 1; 330f071004SDavid Howells } 340f071004SDavid Howells 350f071004SDavid Howells pr_debug("MTDSB: No match, device %d (\"%s\"), device %d (\"%s\")\n", 360f071004SDavid Howells sb->s_mtd->index, sb->s_mtd->name, mtd->index, mtd->name); 370f071004SDavid Howells return 0; 380f071004SDavid Howells } 390f071004SDavid Howells 400f071004SDavid Howells /* 410f071004SDavid Howells * mark the superblock by the MTD device it is using 420f071004SDavid Howells * - set the device number to be the correct MTD block device for pesuperstence 430f071004SDavid Howells * of NFS exports 440f071004SDavid Howells */ 450f071004SDavid Howells static int mtd_set_super(struct super_block *sb, struct fs_context *fc) 460f071004SDavid Howells { 470f071004SDavid Howells sb->s_mtd = fc->sget_key; 480f071004SDavid Howells sb->s_dev = MKDEV(MTD_BLOCK_MAJOR, sb->s_mtd->index); 490f071004SDavid Howells sb->s_bdi = bdi_get(mtd_bdi); 500f071004SDavid Howells return 0; 510f071004SDavid Howells } 520f071004SDavid Howells 530f071004SDavid Howells /* 540f071004SDavid Howells * get a superblock on an MTD-backed filesystem 550f071004SDavid Howells */ 560f071004SDavid Howells static int mtd_get_sb(struct fs_context *fc, 570f071004SDavid Howells struct mtd_info *mtd, 580f071004SDavid Howells int (*fill_super)(struct super_block *, 590f071004SDavid Howells struct fs_context *)) 600f071004SDavid Howells { 610f071004SDavid Howells struct super_block *sb; 620f071004SDavid Howells int ret; 630f071004SDavid Howells 640f071004SDavid Howells fc->sget_key = mtd; 650f071004SDavid Howells sb = sget_fc(fc, mtd_test_super, mtd_set_super); 660f071004SDavid Howells if (IS_ERR(sb)) 670f071004SDavid Howells return PTR_ERR(sb); 680f071004SDavid Howells 690f071004SDavid Howells if (sb->s_root) { 700f071004SDavid Howells /* new mountpoint for an already mounted superblock */ 710f071004SDavid Howells pr_debug("MTDSB: Device %d (\"%s\") is already mounted\n", 720f071004SDavid Howells mtd->index, mtd->name); 730f071004SDavid Howells put_mtd_device(mtd); 740f071004SDavid Howells } else { 750f071004SDavid Howells /* fresh new superblock */ 760f071004SDavid Howells pr_debug("MTDSB: New superblock for device %d (\"%s\")\n", 770f071004SDavid Howells mtd->index, mtd->name); 780f071004SDavid Howells 790f071004SDavid Howells ret = fill_super(sb, fc); 800f071004SDavid Howells if (ret < 0) 810f071004SDavid Howells goto error_sb; 820f071004SDavid Howells 830f071004SDavid Howells sb->s_flags |= SB_ACTIVE; 840f071004SDavid Howells } 850f071004SDavid Howells 860f071004SDavid Howells BUG_ON(fc->root); 870f071004SDavid Howells fc->root = dget(sb->s_root); 880f071004SDavid Howells return 0; 890f071004SDavid Howells 900f071004SDavid Howells error_sb: 910f071004SDavid Howells deactivate_locked_super(sb); 920f071004SDavid Howells return ret; 930f071004SDavid Howells } 940f071004SDavid Howells 950f071004SDavid Howells /* 960f071004SDavid Howells * get a superblock on an MTD-backed filesystem by MTD device number 970f071004SDavid Howells */ 980f071004SDavid Howells static int mtd_get_sb_by_nr(struct fs_context *fc, int mtdnr, 990f071004SDavid Howells int (*fill_super)(struct super_block *, 1000f071004SDavid Howells struct fs_context *)) 1010f071004SDavid Howells { 1020f071004SDavid Howells struct mtd_info *mtd; 1030f071004SDavid Howells 1040f071004SDavid Howells mtd = get_mtd_device(NULL, mtdnr); 1050f071004SDavid Howells if (IS_ERR(mtd)) { 1060f071004SDavid Howells errorf(fc, "MTDSB: Device #%u doesn't appear to exist\n", mtdnr); 1070f071004SDavid Howells return PTR_ERR(mtd); 1080f071004SDavid Howells } 1090f071004SDavid Howells 1100f071004SDavid Howells return mtd_get_sb(fc, mtd, fill_super); 1110f071004SDavid Howells } 1120f071004SDavid Howells 1130f071004SDavid Howells /** 1140f071004SDavid Howells * get_tree_mtd - Get a superblock based on a single MTD device 1150f071004SDavid Howells * @fc: The filesystem context holding the parameters 1160f071004SDavid Howells * @fill_super: Helper to initialise a new superblock 1170f071004SDavid Howells */ 1180f071004SDavid Howells int get_tree_mtd(struct fs_context *fc, 1190f071004SDavid Howells int (*fill_super)(struct super_block *sb, 1200f071004SDavid Howells struct fs_context *fc)) 1210f071004SDavid Howells { 1220f071004SDavid Howells #ifdef CONFIG_BLOCK 1230f071004SDavid Howells struct block_device *bdev; 1240f071004SDavid Howells int ret, major; 1250f071004SDavid Howells #endif 1260f071004SDavid Howells int mtdnr; 1270f071004SDavid Howells 1280f071004SDavid Howells if (!fc->source) 1290f071004SDavid Howells return invalf(fc, "No source specified"); 1300f071004SDavid Howells 1310f071004SDavid Howells pr_debug("MTDSB: dev_name \"%s\"\n", fc->source); 1320f071004SDavid Howells 1330f071004SDavid Howells /* the preferred way of mounting in future; especially when 1340f071004SDavid Howells * CONFIG_BLOCK=n - we specify the underlying MTD device by number or 1350f071004SDavid Howells * by name, so that we don't require block device support to be present 1360f071004SDavid Howells * in the kernel. 1370f071004SDavid Howells */ 1380f071004SDavid Howells if (fc->source[0] == 'm' && 1390f071004SDavid Howells fc->source[1] == 't' && 1400f071004SDavid Howells fc->source[2] == 'd') { 1410f071004SDavid Howells if (fc->source[3] == ':') { 1420f071004SDavid Howells struct mtd_info *mtd; 1430f071004SDavid Howells 1440f071004SDavid Howells /* mount by MTD device name */ 1450f071004SDavid Howells pr_debug("MTDSB: mtd:%%s, name \"%s\"\n", 1460f071004SDavid Howells fc->source + 4); 1470f071004SDavid Howells 1480f071004SDavid Howells mtd = get_mtd_device_nm(fc->source + 4); 1490f071004SDavid Howells if (!IS_ERR(mtd)) 1500f071004SDavid Howells return mtd_get_sb(fc, mtd, fill_super); 1510f071004SDavid Howells 1520f071004SDavid Howells errorf(fc, "MTD: MTD device with name \"%s\" not found", 1530f071004SDavid Howells fc->source + 4); 1540f071004SDavid Howells 1550f071004SDavid Howells } else if (isdigit(fc->source[3])) { 1560f071004SDavid Howells /* mount by MTD device number name */ 1570f071004SDavid Howells char *endptr; 1580f071004SDavid Howells 1590f071004SDavid Howells mtdnr = simple_strtoul(fc->source + 3, &endptr, 0); 1600f071004SDavid Howells if (!*endptr) { 1610f071004SDavid Howells /* It was a valid number */ 1620f071004SDavid Howells pr_debug("MTDSB: mtd%%d, mtdnr %d\n", mtdnr); 1630f071004SDavid Howells return mtd_get_sb_by_nr(fc, mtdnr, fill_super); 1640f071004SDavid Howells } 1650f071004SDavid Howells } 1660f071004SDavid Howells } 1670f071004SDavid Howells 1680f071004SDavid Howells #ifdef CONFIG_BLOCK 1690f071004SDavid Howells /* try the old way - the hack where we allowed users to mount 1700f071004SDavid Howells * /dev/mtdblock$(n) but didn't actually _use_ the blockdev 1710f071004SDavid Howells */ 1720f071004SDavid Howells bdev = lookup_bdev(fc->source); 1730f071004SDavid Howells if (IS_ERR(bdev)) { 1740f071004SDavid Howells ret = PTR_ERR(bdev); 1750f071004SDavid Howells errorf(fc, "MTD: Couldn't look up '%s': %d", fc->source, ret); 1760f071004SDavid Howells return ret; 1770f071004SDavid Howells } 1780f071004SDavid Howells pr_debug("MTDSB: lookup_bdev() returned 0\n"); 1790f071004SDavid Howells 1800f071004SDavid Howells major = MAJOR(bdev->bd_dev); 1810f071004SDavid Howells mtdnr = MINOR(bdev->bd_dev); 1820f071004SDavid Howells bdput(bdev); 1830f071004SDavid Howells 1840f071004SDavid Howells if (major == MTD_BLOCK_MAJOR) 1850f071004SDavid Howells return mtd_get_sb_by_nr(fc, mtdnr, fill_super); 1860f071004SDavid Howells 1870f071004SDavid Howells #endif /* CONFIG_BLOCK */ 1880f071004SDavid Howells 1890f071004SDavid Howells if (!(fc->sb_flags & SB_SILENT)) 1900f071004SDavid Howells errorf(fc, "MTD: Attempt to mount non-MTD device \"%s\"", 1910f071004SDavid Howells fc->source); 1920f071004SDavid Howells return -EINVAL; 1930f071004SDavid Howells } 1940f071004SDavid Howells EXPORT_SYMBOL_GPL(get_tree_mtd); 195acaebfd8SDavid Howells 196acaebfd8SDavid Howells /* 197acaebfd8SDavid Howells * compare superblocks to see if they're equivalent 198acaebfd8SDavid Howells * - they are if the underlying MTD device is the same 199acaebfd8SDavid Howells */ 200acaebfd8SDavid Howells static int get_sb_mtd_compare(struct super_block *sb, void *_mtd) 201acaebfd8SDavid Howells { 202acaebfd8SDavid Howells struct mtd_info *mtd = _mtd; 203acaebfd8SDavid Howells 204acaebfd8SDavid Howells if (sb->s_mtd == mtd) { 205289c0522SBrian Norris pr_debug("MTDSB: Match on device %d (\"%s\")\n", 206acaebfd8SDavid Howells mtd->index, mtd->name); 207acaebfd8SDavid Howells return 1; 208acaebfd8SDavid Howells } 209acaebfd8SDavid Howells 210289c0522SBrian Norris pr_debug("MTDSB: No match, device %d (\"%s\"), device %d (\"%s\")\n", 211acaebfd8SDavid Howells sb->s_mtd->index, sb->s_mtd->name, mtd->index, mtd->name); 212acaebfd8SDavid Howells return 0; 213acaebfd8SDavid Howells } 214acaebfd8SDavid Howells 215acaebfd8SDavid Howells /* 216acaebfd8SDavid Howells * mark the superblock by the MTD device it is using 217acaebfd8SDavid Howells * - set the device number to be the correct MTD block device for pesuperstence 218acaebfd8SDavid Howells * of NFS exports 219acaebfd8SDavid Howells */ 220acaebfd8SDavid Howells static int get_sb_mtd_set(struct super_block *sb, void *_mtd) 221acaebfd8SDavid Howells { 222acaebfd8SDavid Howells struct mtd_info *mtd = _mtd; 223acaebfd8SDavid Howells 224acaebfd8SDavid Howells sb->s_mtd = mtd; 225acaebfd8SDavid Howells sb->s_dev = MKDEV(MTD_BLOCK_MAJOR, mtd->index); 226fa06052dSJan Kara sb->s_bdi = bdi_get(mtd_bdi); 227fa06052dSJan Kara 228acaebfd8SDavid Howells return 0; 229acaebfd8SDavid Howells } 230acaebfd8SDavid Howells 231acaebfd8SDavid Howells /* 232acaebfd8SDavid Howells * get a superblock on an MTD-backed filesystem 233acaebfd8SDavid Howells */ 234848b83a5SAl Viro static struct dentry *mount_mtd_aux(struct file_system_type *fs_type, int flags, 235acaebfd8SDavid Howells const char *dev_name, void *data, 236acaebfd8SDavid Howells struct mtd_info *mtd, 237848b83a5SAl Viro int (*fill_super)(struct super_block *, void *, int)) 238acaebfd8SDavid Howells { 239acaebfd8SDavid Howells struct super_block *sb; 240acaebfd8SDavid Howells int ret; 241acaebfd8SDavid Howells 2429249e17fSDavid Howells sb = sget(fs_type, get_sb_mtd_compare, get_sb_mtd_set, flags, mtd); 243acaebfd8SDavid Howells if (IS_ERR(sb)) 244acaebfd8SDavid Howells goto out_error; 245acaebfd8SDavid Howells 246acaebfd8SDavid Howells if (sb->s_root) 247acaebfd8SDavid Howells goto already_mounted; 248acaebfd8SDavid Howells 249acaebfd8SDavid Howells /* fresh new superblock */ 250289c0522SBrian Norris pr_debug("MTDSB: New superblock for device %d (\"%s\")\n", 251acaebfd8SDavid Howells mtd->index, mtd->name); 252acaebfd8SDavid Howells 2531751e8a6SLinus Torvalds ret = fill_super(sb, data, flags & SB_SILENT ? 1 : 0); 254acaebfd8SDavid Howells if (ret < 0) { 2556f5bbff9SAl Viro deactivate_locked_super(sb); 256848b83a5SAl Viro return ERR_PTR(ret); 257acaebfd8SDavid Howells } 258acaebfd8SDavid Howells 259acaebfd8SDavid Howells /* go */ 2601751e8a6SLinus Torvalds sb->s_flags |= SB_ACTIVE; 261848b83a5SAl Viro return dget(sb->s_root); 262acaebfd8SDavid Howells 263acaebfd8SDavid Howells /* new mountpoint for an already mounted superblock */ 264acaebfd8SDavid Howells already_mounted: 265289c0522SBrian Norris pr_debug("MTDSB: Device %d (\"%s\") is already mounted\n", 266acaebfd8SDavid Howells mtd->index, mtd->name); 267848b83a5SAl Viro put_mtd_device(mtd); 268848b83a5SAl Viro return dget(sb->s_root); 269acaebfd8SDavid Howells 270acaebfd8SDavid Howells out_error: 271acaebfd8SDavid Howells put_mtd_device(mtd); 272848b83a5SAl Viro return ERR_CAST(sb); 273acaebfd8SDavid Howells } 274acaebfd8SDavid Howells 275acaebfd8SDavid Howells /* 276acaebfd8SDavid Howells * get a superblock on an MTD-backed filesystem by MTD device number 277acaebfd8SDavid Howells */ 278848b83a5SAl Viro static struct dentry *mount_mtd_nr(struct file_system_type *fs_type, int flags, 279acaebfd8SDavid Howells const char *dev_name, void *data, int mtdnr, 280848b83a5SAl Viro int (*fill_super)(struct super_block *, void *, int)) 281acaebfd8SDavid Howells { 282acaebfd8SDavid Howells struct mtd_info *mtd; 283acaebfd8SDavid Howells 284acaebfd8SDavid Howells mtd = get_mtd_device(NULL, mtdnr); 285718ea836SDavid Woodhouse if (IS_ERR(mtd)) { 286289c0522SBrian Norris pr_debug("MTDSB: Device #%u doesn't appear to exist\n", mtdnr); 287848b83a5SAl Viro return ERR_CAST(mtd); 288acaebfd8SDavid Howells } 289acaebfd8SDavid Howells 290848b83a5SAl Viro return mount_mtd_aux(fs_type, flags, dev_name, data, mtd, fill_super); 291acaebfd8SDavid Howells } 292acaebfd8SDavid Howells 293acaebfd8SDavid Howells /* 294acaebfd8SDavid Howells * set up an MTD-based superblock 295acaebfd8SDavid Howells */ 296848b83a5SAl Viro struct dentry *mount_mtd(struct file_system_type *fs_type, int flags, 297acaebfd8SDavid Howells const char *dev_name, void *data, 298848b83a5SAl Viro int (*fill_super)(struct super_block *, void *, int)) 299acaebfd8SDavid Howells { 300f1136d02SDavid Woodhouse #ifdef CONFIG_BLOCK 301d5686b44SAl Viro struct block_device *bdev; 302f1136d02SDavid Woodhouse int ret, major; 303f1136d02SDavid Woodhouse #endif 304f1136d02SDavid Woodhouse int mtdnr; 305acaebfd8SDavid Howells 306acaebfd8SDavid Howells if (!dev_name) 307848b83a5SAl Viro return ERR_PTR(-EINVAL); 308acaebfd8SDavid Howells 309289c0522SBrian Norris pr_debug("MTDSB: dev_name \"%s\"\n", dev_name); 310acaebfd8SDavid Howells 311acaebfd8SDavid Howells /* the preferred way of mounting in future; especially when 312acaebfd8SDavid Howells * CONFIG_BLOCK=n - we specify the underlying MTD device by number or 313acaebfd8SDavid Howells * by name, so that we don't require block device support to be present 314acaebfd8SDavid Howells * in the kernel. */ 315acaebfd8SDavid Howells if (dev_name[0] == 'm' && dev_name[1] == 't' && dev_name[2] == 'd') { 316acaebfd8SDavid Howells if (dev_name[3] == ':') { 317acaebfd8SDavid Howells struct mtd_info *mtd; 318acaebfd8SDavid Howells 319acaebfd8SDavid Howells /* mount by MTD device name */ 320289c0522SBrian Norris pr_debug("MTDSB: mtd:%%s, name \"%s\"\n", 321acaebfd8SDavid Howells dev_name + 4); 322acaebfd8SDavid Howells 323677c2aecSBen Hutchings mtd = get_mtd_device_nm(dev_name + 4); 324677c2aecSBen Hutchings if (!IS_ERR(mtd)) 325848b83a5SAl Viro return mount_mtd_aux( 326acaebfd8SDavid Howells fs_type, flags, 327acaebfd8SDavid Howells dev_name, data, mtd, 328848b83a5SAl Viro fill_super); 329acaebfd8SDavid Howells 330acaebfd8SDavid Howells printk(KERN_NOTICE "MTD:" 331acaebfd8SDavid Howells " MTD device with name \"%s\" not found.\n", 332acaebfd8SDavid Howells dev_name + 4); 333acaebfd8SDavid Howells 334acaebfd8SDavid Howells } else if (isdigit(dev_name[3])) { 335acaebfd8SDavid Howells /* mount by MTD device number name */ 336acaebfd8SDavid Howells char *endptr; 337acaebfd8SDavid Howells 338acaebfd8SDavid Howells mtdnr = simple_strtoul(dev_name + 3, &endptr, 0); 339acaebfd8SDavid Howells if (!*endptr) { 340acaebfd8SDavid Howells /* It was a valid number */ 341289c0522SBrian Norris pr_debug("MTDSB: mtd%%d, mtdnr %d\n", 342acaebfd8SDavid Howells mtdnr); 343848b83a5SAl Viro return mount_mtd_nr(fs_type, flags, 344acaebfd8SDavid Howells dev_name, data, 345848b83a5SAl Viro mtdnr, fill_super); 346acaebfd8SDavid Howells } 347acaebfd8SDavid Howells } 348acaebfd8SDavid Howells } 349acaebfd8SDavid Howells 350f1136d02SDavid Woodhouse #ifdef CONFIG_BLOCK 351acaebfd8SDavid Howells /* try the old way - the hack where we allowed users to mount 352acaebfd8SDavid Howells * /dev/mtdblock$(n) but didn't actually _use_ the blockdev 353acaebfd8SDavid Howells */ 354d5686b44SAl Viro bdev = lookup_bdev(dev_name); 355d5686b44SAl Viro if (IS_ERR(bdev)) { 356d5686b44SAl Viro ret = PTR_ERR(bdev); 357289c0522SBrian Norris pr_debug("MTDSB: lookup_bdev() returned %d\n", ret); 358848b83a5SAl Viro return ERR_PTR(ret); 359d5686b44SAl Viro } 360289c0522SBrian Norris pr_debug("MTDSB: lookup_bdev() returned 0\n"); 361acaebfd8SDavid Howells 362acaebfd8SDavid Howells ret = -EINVAL; 363acaebfd8SDavid Howells 364f1136d02SDavid Woodhouse major = MAJOR(bdev->bd_dev); 365d5686b44SAl Viro mtdnr = MINOR(bdev->bd_dev); 366d5686b44SAl Viro bdput(bdev); 367acaebfd8SDavid Howells 368f1136d02SDavid Woodhouse if (major != MTD_BLOCK_MAJOR) 369f1136d02SDavid Woodhouse goto not_an_MTD_device; 370f1136d02SDavid Woodhouse 371848b83a5SAl Viro return mount_mtd_nr(fs_type, flags, dev_name, data, mtdnr, fill_super); 372acaebfd8SDavid Howells 373acaebfd8SDavid Howells not_an_MTD_device: 374f1136d02SDavid Woodhouse #endif /* CONFIG_BLOCK */ 375f1136d02SDavid Woodhouse 3761751e8a6SLinus Torvalds if (!(flags & SB_SILENT)) 377acaebfd8SDavid Howells printk(KERN_NOTICE 378acaebfd8SDavid Howells "MTD: Attempt to mount non-MTD device \"%s\"\n", 379acaebfd8SDavid Howells dev_name); 380848b83a5SAl Viro return ERR_PTR(-EINVAL); 381acaebfd8SDavid Howells } 382acaebfd8SDavid Howells 383848b83a5SAl Viro EXPORT_SYMBOL_GPL(mount_mtd); 384acaebfd8SDavid Howells 385acaebfd8SDavid Howells /* 386acaebfd8SDavid Howells * destroy an MTD-based superblock 387acaebfd8SDavid Howells */ 388acaebfd8SDavid Howells void kill_mtd_super(struct super_block *sb) 389acaebfd8SDavid Howells { 390acaebfd8SDavid Howells generic_shutdown_super(sb); 391acaebfd8SDavid Howells put_mtd_device(sb->s_mtd); 392acaebfd8SDavid Howells sb->s_mtd = NULL; 393acaebfd8SDavid Howells } 394acaebfd8SDavid Howells 395acaebfd8SDavid Howells EXPORT_SYMBOL_GPL(kill_mtd_super); 396