1203 lines · 30.7 KB · C
RawDownload
/* * Core registration and callback routines for MTD * drivers and users. * * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> * Copyright © 2006      Red Hat UK Limited  * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA * */ #include <linux/module.h>#include <linux/kernel.h>#include <linux/ptrace.h>#include <linux/seq_file.h>#include <linux/string.h>#include <linux/timer.h>#include <linux/major.h>#include <linux/fs.h>#include <linux/err.h>#include <linux/ioctl.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/idr.h>#include <linux/backing-dev.h>#include <linux/gfp.h> #include <linux/mtd/mtd.h>#include <linux/mtd/partitions.h> #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,}; /* * 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),}; /* * 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, pm_message_t state);static int mtd_cls_resume(struct device *dev); static struct class mtd_class = {	.name = "mtd",	.owner = THIS_MODULE,	.suspend = mtd_cls_suspend,	.resume = mtd_cls_resume,}; static DEFINE_IDR(mtd_idr); /* These are exported solely for the purpose of mtd_blkdevs.c. You   should not use them for _anything_ else */DEFINE_MUTEX(mtd_table_mutex);EXPORT_SYMBOL_GPL(mtd_table_mutex); struct mtd_info *__mtd_next_device(int i){	return idr_get_next(&mtd_idr, &i);}EXPORT_SYMBOL_GPL(__mtd_next_device); static LIST_HEAD(mtd_notifiers);  #if defined(CONFIG_MTD_CHAR) || defined(CONFIG_MTD_CHAR_MODULE)#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)#else#define MTD_DEVT(index) 0#endif /* REVISIT once MTD uses the driver model better, whoever allocates * the mtd_info will probably want to use the release() hook... */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); 	if (mtd)		mtd_resume(mtd);	return 0;} static ssize_t mtd_type_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev);	char *type; 	switch (mtd->type) {	case MTD_ABSENT:		type = "absent";		break;	case MTD_RAM:		type = "ram";		break;	case MTD_ROM:		type = "rom";		break;	case MTD_NORFLASH:		type = "nor";		break;	case MTD_NANDFLASH:		type = "nand";		break;	case MTD_DATAFLASH:		type = "dataflash";		break;	case MTD_UBIVOLUME:		type = "ubi";		break;	default:		type = "unknown";	} 	return snprintf(buf, PAGE_SIZE, "%s\n", type);}static DEVICE_ATTR(type, S_IRUGO, mtd_type_show, NULL); static ssize_t mtd_flags_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev); 	return snprintf(buf, PAGE_SIZE, "0x%lx\n", (unsigned long)mtd->flags); }static DEVICE_ATTR(flags, S_IRUGO, mtd_flags_show, NULL); static ssize_t mtd_size_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev); 	return snprintf(buf, PAGE_SIZE, "%llu\n",		(unsigned long long)mtd->size); }static DEVICE_ATTR(size, S_IRUGO, mtd_size_show, NULL); static ssize_t mtd_erasesize_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev); 	return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->erasesize); }static DEVICE_ATTR(erasesize, S_IRUGO, mtd_erasesize_show, NULL); static ssize_t mtd_writesize_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev); 	return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->writesize); }static DEVICE_ATTR(writesize, S_IRUGO, mtd_writesize_show, NULL); static ssize_t mtd_subpagesize_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev);	unsigned int subpagesize = mtd->writesize >> mtd->subpage_sft; 	return snprintf(buf, PAGE_SIZE, "%u\n", subpagesize); }static DEVICE_ATTR(subpagesize, S_IRUGO, mtd_subpagesize_show, NULL); static ssize_t mtd_oobsize_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev); 	return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->oobsize); }static DEVICE_ATTR(oobsize, S_IRUGO, mtd_oobsize_show, NULL); static ssize_t mtd_numeraseregions_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->numeraseregions); }static DEVICE_ATTR(numeraseregions, S_IRUGO, mtd_numeraseregions_show,	NULL); static ssize_t mtd_name_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct mtd_info *mtd = dev_get_drvdata(dev); 	return snprintf(buf, PAGE_SIZE, "%s\n", mtd->name); }static DEVICE_ATTR(name, S_IRUGO, mtd_name_show, NULL); static ssize_t mtd_ecc_strength_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_strength);}static DEVICE_ATTR(ecc_strength, S_IRUGO, mtd_ecc_strength_show, NULL); static ssize_t mtd_bitflip_threshold_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->bitflip_threshold);} static ssize_t mtd_bitflip_threshold_store(struct device *dev,					   struct device_attribute *attr,					   const char *buf, size_t count){	struct mtd_info *mtd = dev_get_drvdata(dev);	unsigned int bitflip_threshold;	int retval; 	retval = kstrtouint(buf, 0, &bitflip_threshold);	if (retval)		return retval; 	mtd->bitflip_threshold = bitflip_threshold;	return count;}static DEVICE_ATTR(bitflip_threshold, S_IRUGO | S_IWUSR,		   mtd_bitflip_threshold_show,		   mtd_bitflip_threshold_store); static struct attribute *mtd_attrs[] = {	&dev_attr_type.attr,	&dev_attr_flags.attr,	&dev_attr_size.attr,	&dev_attr_erasesize.attr,	&dev_attr_writesize.attr,	&dev_attr_subpagesize.attr,	&dev_attr_oobsize.attr,	&dev_attr_numeraseregions.attr,	&dev_attr_name.attr,	&dev_attr_ecc_strength.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,}; static struct device_type mtd_devtype = {	.name		= "mtd",	.groups		= mtd_groups,	.release	= mtd_release,}; /** *	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. */ int add_mtd_device(struct mtd_info *mtd){	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;		}	} 	BUG_ON(mtd->writesize == 0);	mutex_lock(&mtd_table_mutex); 	do {		if (!idr_pre_get(&mtd_idr, GFP_KERNEL))			goto fail_locked;		error = idr_get_new(&mtd_idr, mtd, &i);	} while (error == -EAGAIN); 	if (error)		goto fail_locked; 	mtd->index = i;	mtd->usecount = 0; 	/* default value if not set by driver */	if (mtd->bitflip_threshold == 0)		mtd->bitflip_threshold = mtd->ecc_strength; 	if (is_power_of_2(mtd->erasesize))		mtd->erasesize_shift = ffs(mtd->erasesize) - 1;	else		mtd->erasesize_shift = 0; 	if (is_power_of_2(mtd->writesize))		mtd->writesize_shift = ffs(mtd->writesize) - 1;	else		mtd->writesize_shift = 0; 	mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;	mtd->writesize_mask = (1 << mtd->writesize_shift) - 1; 	/* Some chips always power up locked. Unlock them now */	if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) {		error = mtd_unlock(mtd, 0, mtd->size);		if (error && error != -EOPNOTSUPP)			printk(KERN_WARNING			       "%s: unlock failed, writes may not work\n",			       mtd->name);	} 	/* Caller should have set dev.parent to match the	 * physical device.	 */	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)		goto fail_added; 	if (MTD_DEVT(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	   the notifier, since we hold the mtd_table_mutex */	list_for_each_entry(not, &mtd_notifiers, list)		not->add(mtd); 	mutex_unlock(&mtd_table_mutex);	/* We _know_ we aren't being removed, because	   our caller is still holding us here. So none	   of this try_ nonsense, and no bitching about it	   either. :) */	__module_get(THIS_MODULE);	return 0; fail_added:	idr_remove(&mtd_idr, i);fail_locked:	mutex_unlock(&mtd_table_mutex);	return 1;} /** *	del_mtd_device - unregister an MTD device *	@mtd: pointer to MTD device info structure * *	Remove a device from the list of MTD devices present in the system, *	and notify each currently active MTD 'user' of its departure. *	Returns zero on success or 1 on failure, which currently will happen *	if the requested device does not appear to be present in the list. */ int del_mtd_device(struct mtd_info *mtd){	int ret;	struct mtd_notifier *not; 	mutex_lock(&mtd_table_mutex); 	if (idr_find(&mtd_idr, mtd->index) != mtd) {		ret = -ENODEV;		goto out_error;	} 	/* No need to get a refcount on the module containing		the notifier, since we hold the mtd_table_mutex */	list_for_each_entry(not, &mtd_notifiers, list)		not->remove(mtd); 	if (mtd->usecount) {		printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",		       mtd->index, mtd->name, mtd->usecount);		ret = -EBUSY;	} else {		device_unregister(&mtd->dev); 		idr_remove(&mtd_idr, mtd->index); 		module_put(THIS_MODULE);		ret = 0;	} out_error:	mutex_unlock(&mtd_table_mutex);	return ret;} /** * mtd_device_parse_register - parse partitions and register an MTD device. * * @mtd: the MTD device to register * @types: the list of MTD partition probes to try, see *         'parse_mtd_partitions()' for more information * @parser_data: MTD partition parser-specific data * @parts: fallback partition information to register, if parsing fails; *         only valid if %nr_parts > %0 * @nr_parts: the number of partitions in parts, if zero then the full *            MTD device is registered if no partition info is found * * This function aggregates MTD partitions parsing (done by * 'parse_mtd_partitions()') and MTD device and partitions registering. It * basically follows the most common pattern found in many MTD drivers: * * * It first tries to probe partitions on MTD device @mtd using parsers *   specified in @types (if @types is %NULL, then the default list of parsers *   is used, see 'parse_mtd_partitions()' for more information). If none are *   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. * * If no partitions were found this function just registers the MTD device *   @mtd and exits. * * Returns zero in case of success and a negative error code in case of failure. */int mtd_device_parse_register(struct mtd_info *mtd, const char **types,

Lines 1–500 of 1203.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t