--- zzzz-none-000/linux-3.10.107/drivers/mtd/mtdcore.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/mtd/mtdcore.c 2021-02-04 17:41:59.000000000 +0000 @@ -37,49 +37,46 @@ #include #include #include +#include +#include +#include #include #include #include "mtdcore.h" -/* - * backing device capabilities for non-mappable devices (such as NAND flash) - * - permits private mappings, copies are taken of the data - */ -static struct backing_dev_info mtd_bdi_unmappable = { - .capabilities = BDI_CAP_MAP_COPY, +static struct backing_dev_info mtd_bdi = { }; -/* - * backing device capabilities for R/O mappable devices (such as ROM) - * - permits private mappings, copies are taken of the data - * - permits non-writable shared mappings - */ -static struct backing_dev_info mtd_bdi_ro_mappable = { - .capabilities = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT | - BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP), -}; +#ifdef CONFIG_PM_SLEEP -/* - * backing device capabilities for writable mappable devices (such as RAM) - * - permits private mappings, copies are taken of the data - * - permits non-writable shared mappings - */ -static struct backing_dev_info mtd_bdi_rw_mappable = { - .capabilities = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT | - BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP | - BDI_CAP_WRITE_MAP), -}; +static int mtd_cls_suspend(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + return mtd ? mtd_suspend(mtd) : 0; +} + +static int mtd_cls_resume(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + if (mtd) + mtd_resume(mtd); + return 0; +} -static int mtd_cls_suspend(struct device *dev, pm_message_t state); -static int mtd_cls_resume(struct device *dev); +static SIMPLE_DEV_PM_OPS(mtd_cls_pm_ops, mtd_cls_suspend, mtd_cls_resume); +#define MTD_CLS_PM_OPS (&mtd_cls_pm_ops) +#else +#define MTD_CLS_PM_OPS NULL +#endif static struct class mtd_class = { .name = "mtd", .owner = THIS_MODULE, - .suspend = mtd_cls_suspend, - .resume = mtd_cls_resume, + .pm = MTD_CLS_PM_OPS, }; static DEFINE_IDR(mtd_idr); @@ -105,28 +102,11 @@ */ static void mtd_release(struct device *dev) { - struct mtd_info __maybe_unused *mtd = dev_get_drvdata(dev); - dev_t index = MTD_DEVT(mtd->index); - - /* remove /dev/mtdXro node if needed */ - if (index) - device_destroy(&mtd_class, index + 1); -} - -static int mtd_cls_suspend(struct device *dev, pm_message_t state) -{ - struct mtd_info *mtd = dev_get_drvdata(dev); - - return mtd ? mtd_suspend(mtd) : 0; -} - -static int mtd_cls_resume(struct device *dev) -{ struct mtd_info *mtd = dev_get_drvdata(dev); + dev_t index = MTD_DEVT(mtd->index); - if (mtd) - mtd_resume(mtd); - return 0; + /* remove /dev/mtdXro node */ + device_destroy(&mtd_class, index + 1); } static ssize_t mtd_type_show(struct device *dev, @@ -157,6 +137,9 @@ case MTD_UBIVOLUME: type = "ubi"; break; + case MTD_MLCNANDFLASH: + type = "mlc-nand"; + break; default: type = "unknown"; } @@ -285,6 +268,57 @@ mtd_bitflip_threshold_show, mtd_bitflip_threshold_store); +static ssize_t mtd_ecc_step_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", mtd->ecc_step_size); + +} +static DEVICE_ATTR(ecc_step_size, S_IRUGO, mtd_ecc_step_size_show, NULL); + +static ssize_t mtd_ecc_stats_corrected_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; + + return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->corrected); +} +static DEVICE_ATTR(corrected_bits, S_IRUGO, + mtd_ecc_stats_corrected_show, NULL); + +static ssize_t mtd_ecc_stats_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; + + return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->failed); +} +static DEVICE_ATTR(ecc_failures, S_IRUGO, mtd_ecc_stats_errors_show, NULL); + +static ssize_t mtd_badblocks_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; + + return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->badblocks); +} +static DEVICE_ATTR(bad_blocks, S_IRUGO, mtd_badblocks_show, NULL); + +static ssize_t mtd_bbtblocks_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct mtd_ecc_stats *ecc_stats = &mtd->ecc_stats; + + return snprintf(buf, PAGE_SIZE, "%u\n", ecc_stats->bbtblocks); +} +static DEVICE_ATTR(bbt_blocks, S_IRUGO, mtd_bbtblocks_show, NULL); + static struct attribute *mtd_attrs[] = { &dev_attr_type.attr, &dev_attr_flags.attr, @@ -296,18 +330,15 @@ &dev_attr_numeraseregions.attr, &dev_attr_name.attr, &dev_attr_ecc_strength.attr, + &dev_attr_ecc_step_size.attr, + &dev_attr_corrected_bits.attr, + &dev_attr_ecc_failures.attr, + &dev_attr_bad_blocks.attr, + &dev_attr_bbt_blocks.attr, &dev_attr_bitflip_threshold.attr, NULL, }; - -static struct attribute_group mtd_group = { - .attrs = mtd_attrs, -}; - -static const struct attribute_group *mtd_groups[] = { - &mtd_group, - NULL, -}; +ATTRIBUTE_GROUPS(mtd); static struct device_type mtd_devtype = { .name = "mtd", @@ -315,14 +346,41 @@ .release = mtd_release, }; +#ifndef CONFIG_MMU +unsigned mtd_mmap_capabilities(struct mtd_info *mtd) +{ + switch (mtd->type) { + case MTD_RAM: + return NOMMU_MAP_COPY | NOMMU_MAP_DIRECT | NOMMU_MAP_EXEC | + NOMMU_MAP_READ | NOMMU_MAP_WRITE; + case MTD_ROM: + return NOMMU_MAP_COPY | NOMMU_MAP_DIRECT | NOMMU_MAP_EXEC | + NOMMU_MAP_READ; + default: + return NOMMU_MAP_COPY; + } +} +EXPORT_SYMBOL_GPL(mtd_mmap_capabilities); +#endif + +static int mtd_reboot_notifier(struct notifier_block *n, unsigned long state, + void *cmd) +{ + struct mtd_info *mtd; + + mtd = container_of(n, struct mtd_info, reboot_notifier); + mtd->_reboot(mtd); + + return NOTIFY_DONE; +} + /** * add_mtd_device - register an MTD device * @mtd: pointer to new MTD device info structure * * Add a device to the list of MTD devices present in the system, and * notify each currently active MTD 'user' of its arrival. Returns - * zero on success or 1 on failure, which currently will only happen - * if there is insufficient memory or a sysfs error. + * zero on success or non-zero on failure. */ int add_mtd_device(struct mtd_info *mtd) @@ -330,26 +388,24 @@ struct mtd_notifier *not; int i, error; - if (!mtd->backing_dev_info) { - switch (mtd->type) { - case MTD_RAM: - mtd->backing_dev_info = &mtd_bdi_rw_mappable; - break; - case MTD_ROM: - mtd->backing_dev_info = &mtd_bdi_ro_mappable; - break; - default: - mtd->backing_dev_info = &mtd_bdi_unmappable; - break; - } - } + /* + * May occur, for instance, on buggy drivers which call + * mtd_device_parse_register() multiple times on the same master MTD, + * especially with CONFIG_MTD_PARTITIONED_MASTER=y. + */ + if (WARN_ONCE(mtd->backing_dev_info, "MTD already registered\n")) + return -EEXIST; + + mtd->backing_dev_info = &mtd_bdi; BUG_ON(mtd->writesize == 0); mutex_lock(&mtd_table_mutex); i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL); - if (i < 0) + if (i < 0) { + error = i; goto fail_locked; + } mtd->index = i; mtd->usecount = 0; @@ -378,23 +434,24 @@ printk(KERN_WARNING "%s: unlock failed, writes may not work\n", mtd->name); + /* Ignore unlock failures? */ + error = 0; } /* Caller should have set dev.parent to match the - * physical device. + * physical device, if appropriate. */ mtd->dev.type = &mtd_devtype; mtd->dev.class = &mtd_class; mtd->dev.devt = MTD_DEVT(i); dev_set_name(&mtd->dev, "mtd%d", i); dev_set_drvdata(&mtd->dev, mtd); - if (device_register(&mtd->dev) != 0) + error = device_register(&mtd->dev); + if (error) goto fail_added; - if (MTD_DEVT(i)) - device_create(&mtd_class, mtd->dev.parent, - MTD_DEVT(i) + 1, - NULL, "mtd%dro", i); + device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL, + "mtd%dro", i); pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name); /* No need to get a refcount on the module containing @@ -408,13 +465,22 @@ of this try_ nonsense, and no bitching about it either. :) */ __module_get(THIS_MODULE); + + if (!strcmp(mtd->name, "rootfs") && + config_enabled(CONFIG_MTD_ROOTFS_ROOT_DEV) && + ROOT_DEV == 0) { + pr_notice("mtd: device %d (%s) set to be root filesystem\n", + mtd->index, mtd->name); + ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index); + } + return 0; fail_added: idr_remove(&mtd_idr, i); fail_locked: mutex_unlock(&mtd_table_mutex); - return 1; + return error; } /** @@ -462,6 +528,44 @@ return ret; } +static int mtd_add_device_partitions(struct mtd_info *mtd, + struct mtd_partition *real_parts, + int nbparts) +{ + int ret; + + if (nbparts == 0 || IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) { + ret = add_mtd_device(mtd); + if (ret) + return ret; + } + + if (nbparts > 0) { + ret = add_mtd_partitions(mtd, real_parts, nbparts); + if (ret && IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) + del_mtd_device(mtd); + return ret; + } + + return 0; +} + +/* + * Set a few defaults based on the parent devices, if not provided by the + * driver + */ +static void mtd_set_dev_defaults(struct mtd_info *mtd) +{ + if (mtd->dev.parent) { + if (!mtd->owner && mtd->dev.parent->driver) + mtd->owner = mtd->dev.parent->driver->owner; + if (!mtd->name) + mtd->name = dev_name(mtd->dev.parent); + } else { + pr_debug("mtd device won't show a device symlink in sysfs\n"); + } +} + /** * mtd_device_parse_register - parse partitions and register an MTD device. * @@ -484,7 +588,8 @@ * found this functions tries to fallback to information specified in * @parts/@nr_parts. * * If any partitioning info was found, this function registers the found - * partitions. + * partitions. If the MTD_PARTITIONED_MASTER option is set, then the device + * as a whole is registered first. * * If no partitions were found this function just registers the MTD device * @mtd and exits. * @@ -495,29 +600,50 @@ const struct mtd_partition *parts, int nr_parts) { - int err; - struct mtd_partition *real_parts; + int ret; + struct mtd_partition *real_parts = NULL; - err = parse_mtd_partitions(mtd, types, &real_parts, parser_data); - if (err <= 0 && nr_parts && parts) { + mtd_set_dev_defaults(mtd); + + ret = parse_mtd_partitions(mtd, types, &real_parts, parser_data); + if (ret <= 0 && nr_parts && parts) { real_parts = kmemdup(parts, sizeof(*parts) * nr_parts, GFP_KERNEL); if (!real_parts) - err = -ENOMEM; + ret = -ENOMEM; else - err = nr_parts; + ret = nr_parts; + } + /* Didn't come up with either parsed OR fallback partitions */ + if (ret < 0) { + pr_info("mtd: failed to find partitions; one or more parsers reports errors (%d)\n", + ret); + /* Don't abort on errors; we can still use unpartitioned MTD */ + ret = 0; } - if (err > 0) { - err = add_mtd_partitions(mtd, real_parts, err); - kfree(real_parts); - } else if (err == 0) { - err = add_mtd_device(mtd); - if (err == 1) - err = -ENODEV; + ret = mtd_add_device_partitions(mtd, real_parts, ret); + if (ret) + goto out; + + /* + * FIXME: some drivers unfortunately call this function more than once. + * So we have to check if we've already assigned the reboot notifier. + * + * Generally, we can make multiple calls work for most cases, but it + * does cause problems with parse_mtd_partitions() above (e.g., + * cmdlineparts will register partitions more than once). + */ + WARN_ONCE(mtd->_reboot && mtd->reboot_notifier.notifier_call, + "MTD already registered\n"); + if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) { + mtd->reboot_notifier.notifier_call = mtd_reboot_notifier; + register_reboot_notifier(&mtd->reboot_notifier); } - return err; +out: + kfree(real_parts); + return ret; } EXPORT_SYMBOL_GPL(mtd_device_parse_register); @@ -531,6 +657,9 @@ { int err; + if (master->_reboot) + unregister_reboot_notifier(&master->reboot_notifier); + err = del_mtd_partitions(master); if (err) return err; @@ -727,7 +856,7 @@ */ int mtd_erase(struct mtd_info *mtd, struct erase_info *instr) { - if (instr->addr > mtd->size || instr->len > mtd->size - instr->addr) + if (instr->addr >= mtd->size || instr->len > mtd->size - instr->addr) return -EINVAL; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; @@ -753,7 +882,7 @@ *phys = 0; if (!mtd->_point) return -EOPNOTSUPP; - if (from < 0 || from > mtd->size || len > mtd->size - from) + if (from < 0 || from >= mtd->size || len > mtd->size - from) return -EINVAL; if (!len) return 0; @@ -766,7 +895,7 @@ { if (!mtd->_point) return -EOPNOTSUPP; - if (from < 0 || from > mtd->size || len > mtd->size - from) + if (from < 0 || from >= mtd->size || len > mtd->size - from) return -EINVAL; if (!len) return 0; @@ -784,7 +913,7 @@ { if (!mtd->_get_unmapped_area) return -EOPNOTSUPP; - if (offset > mtd->size || len > mtd->size - offset) + if (offset >= mtd->size || len > mtd->size - offset) return -EINVAL; return mtd->_get_unmapped_area(mtd, len, offset, flags); } @@ -795,7 +924,7 @@ { int ret_code; *retlen = 0; - if (from < 0 || from > mtd->size || len > mtd->size - from) + if (from < 0 || from >= mtd->size || len > mtd->size - from) return -EINVAL; if (!len) return 0; @@ -818,7 +947,7 @@ const u_char *buf) { *retlen = 0; - if (to < 0 || to > mtd->size || len > mtd->size - to) + if (to < 0 || to >= mtd->size || len > mtd->size - to) return -EINVAL; if (!mtd->_write || !(mtd->flags & MTD_WRITEABLE)) return -EROFS; @@ -841,7 +970,7 @@ *retlen = 0; if (!mtd->_panic_write) return -EOPNOTSUPP; - if (to < 0 || to > mtd->size || len > mtd->size - to) + if (to < 0 || to >= mtd->size || len > mtd->size - to) return -EINVAL; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; @@ -877,14 +1006,14 @@ * devices. The user data is one time programmable but the factory data is read * only. */ -int mtd_get_fact_prot_info(struct mtd_info *mtd, struct otp_info *buf, - size_t len) +int mtd_get_fact_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, + struct otp_info *buf) { if (!mtd->_get_fact_prot_info) return -EOPNOTSUPP; if (!len) return 0; - return mtd->_get_fact_prot_info(mtd, buf, len); + return mtd->_get_fact_prot_info(mtd, len, retlen, buf); } EXPORT_SYMBOL_GPL(mtd_get_fact_prot_info); @@ -900,14 +1029,14 @@ } EXPORT_SYMBOL_GPL(mtd_read_fact_prot_reg); -int mtd_get_user_prot_info(struct mtd_info *mtd, struct otp_info *buf, - size_t len) +int mtd_get_user_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, + struct otp_info *buf) { if (!mtd->_get_user_prot_info) return -EOPNOTSUPP; if (!len) return 0; - return mtd->_get_user_prot_info(mtd, buf, len); + return mtd->_get_user_prot_info(mtd, len, retlen, buf); } EXPORT_SYMBOL_GPL(mtd_get_user_prot_info); @@ -926,12 +1055,22 @@ int mtd_write_user_prot_reg(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, u_char *buf) { + int ret; + *retlen = 0; if (!mtd->_write_user_prot_reg) return -EOPNOTSUPP; if (!len) return 0; - return mtd->_write_user_prot_reg(mtd, to, len, retlen, buf); + ret = mtd->_write_user_prot_reg(mtd, to, len, retlen, buf); + if (ret) + return ret; + + /* + * If no data could be written at all, we are out of memory and + * must return -ENOSPC. + */ + return (*retlen) ? 0 : -ENOSPC; } EXPORT_SYMBOL_GPL(mtd_write_user_prot_reg); @@ -950,7 +1089,7 @@ { if (!mtd->_lock) return -EOPNOTSUPP; - if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs) + if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs) return -EINVAL; if (!len) return 0; @@ -962,7 +1101,7 @@ { if (!mtd->_unlock) return -EOPNOTSUPP; - if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs) + if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs) return -EINVAL; if (!len) return 0; @@ -974,7 +1113,7 @@ { if (!mtd->_is_locked) return -EOPNOTSUPP; - if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs) + if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs) return -EINVAL; if (!len) return 0; @@ -982,12 +1121,22 @@ } EXPORT_SYMBOL_GPL(mtd_is_locked); +int mtd_block_isreserved(struct mtd_info *mtd, loff_t ofs) +{ + if (ofs < 0 || ofs >= mtd->size) + return -EINVAL; + if (!mtd->_block_isreserved) + return 0; + return mtd->_block_isreserved(mtd, ofs); +} +EXPORT_SYMBOL_GPL(mtd_block_isreserved); + int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs) { + if (ofs < 0 || ofs >= mtd->size) + return -EINVAL; if (!mtd->_block_isbad) return 0; - if (ofs < 0 || ofs > mtd->size) - return -EINVAL; return mtd->_block_isbad(mtd, ofs); } EXPORT_SYMBOL_GPL(mtd_block_isbad); @@ -996,7 +1145,7 @@ { if (!mtd->_block_markbad) return -EOPNOTSUPP; - if (ofs < 0 || ofs > mtd->size) + if (ofs < 0 || ofs >= mtd->size) return -EINVAL; if (!(mtd->flags & MTD_WRITEABLE)) return -EROFS; @@ -1085,8 +1234,7 @@ */ void *mtd_kmalloc_up_to(const struct mtd_info *mtd, size_t *size) { - gfp_t flags = __GFP_NOWARN | __GFP_WAIT | - __GFP_NORETRY | __GFP_NO_KSWAPD; + gfp_t flags = __GFP_NOWARN | __GFP_DIRECT_RECLAIM | __GFP_NORETRY; size_t min_alloc = max_t(size_t, mtd->writesize, PAGE_SIZE); void *kbuf; @@ -1140,6 +1288,42 @@ .llseek = seq_lseek, .release = single_release, }; + +#if defined(CONFIG_AVM_ENHANCED) +static struct proc_dir_entry *proc_bbt_mtd; +static int mtd_bbt_proc_show(struct seq_file *m, void *v) +{ + struct mtd_info *mtd; + + mutex_lock(&mtd_table_mutex); + mtd_for_each_device(mtd) { + if (mtd->_block_isbad) { + uint64_t i; + unsigned int badblocks=0; + for (i = 0; i < mtd->size; i += mtd->erasesize) { + if (mtd->_block_isbad(mtd, i) && ((badblocks+1)>badblocks) ) + badblocks++; + } + seq_printf(m, "mtd%d: %u bad blocks\n", mtd->index, badblocks); + } + } + mutex_unlock(&mtd_table_mutex); + return 0; +} + +static int mtd_bbt_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, mtd_bbt_proc_show, NULL); +} + +static const struct file_operations mtd_bbt_proc_ops = { + .open = mtd_bbt_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif /*--- #if #defined(CONFIG_AVM_ENHANCED) ---*/ + #endif /* CONFIG_PROC_FS */ /*====================================================================*/ @@ -1151,7 +1335,7 @@ ret = bdi_init(bdi); if (!ret) - ret = bdi_register(bdi, NULL, name); + ret = bdi_register(bdi, NULL, "%s", name); if (ret) bdi_destroy(bdi); @@ -1169,20 +1353,16 @@ if (ret) goto err_reg; - ret = mtd_bdi_init(&mtd_bdi_unmappable, "mtd-unmap"); - if (ret) - goto err_bdi1; - - ret = mtd_bdi_init(&mtd_bdi_ro_mappable, "mtd-romap"); - if (ret) - goto err_bdi2; - - ret = mtd_bdi_init(&mtd_bdi_rw_mappable, "mtd-rwmap"); + ret = mtd_bdi_init(&mtd_bdi, "mtd"); if (ret) - goto err_bdi3; + goto err_bdi; proc_mtd = proc_create("mtd", 0, NULL, &mtd_proc_ops); +#if defined(CONFIG_AVM_ENHANCED) + proc_bbt_mtd = proc_create("avm/mtd_bbt", 0, NULL, &mtd_bbt_proc_ops); +#endif + ret = init_mtdchar(); if (ret) goto out_procfs; @@ -1192,11 +1372,13 @@ out_procfs: if (proc_mtd) remove_proc_entry("mtd", NULL); -err_bdi3: - bdi_destroy(&mtd_bdi_ro_mappable); -err_bdi2: - bdi_destroy(&mtd_bdi_unmappable); -err_bdi1: + +#if defined(CONFIG_AVM_ENHANCED) + if (proc_bbt_mtd) + remove_proc_entry("mtd_bbt", NULL); +#endif + +err_bdi: class_unregister(&mtd_class); err_reg: pr_err("Error registering mtd class or bdi: %d\n", ret); @@ -1208,10 +1390,15 @@ cleanup_mtdchar(); if (proc_mtd) remove_proc_entry("mtd", NULL); + +#if defined(CONFIG_AVM_ENHANCED) + if (proc_bbt_mtd) + remove_proc_entry("mtd_bbt", NULL); +#endif + class_unregister(&mtd_class); - bdi_destroy(&mtd_bdi_unmappable); - bdi_destroy(&mtd_bdi_ro_mappable); - bdi_destroy(&mtd_bdi_rw_mappable); + bdi_destroy(&mtd_bdi); + idr_destroy(&mtd_idr); } module_init(init_mtd);