xref: /openbmc/linux/drivers/mtd/mtdsuper.c (revision 48440e89)
1acaebfd8SDavid Howells /* MTD-based superblock management
2acaebfd8SDavid Howells  *
3acaebfd8SDavid Howells  * Copyright © 2001-2007 Red Hat, Inc. All Rights Reserved.
4acaebfd8SDavid Howells  * Written by:  David Howells <dhowells@redhat.com>
5acaebfd8SDavid Howells  *              David Woodhouse <dwmw2@infradead.org>
6acaebfd8SDavid Howells  *
7acaebfd8SDavid Howells  * This program is free software; you can redistribute it and/or
8acaebfd8SDavid Howells  * modify it under the terms of the GNU General Public License
9acaebfd8SDavid Howells  * as published by the Free Software Foundation; either version
10acaebfd8SDavid Howells  * 2 of the License, or (at your option) any later version.
11acaebfd8SDavid Howells  */
12acaebfd8SDavid Howells 
13acaebfd8SDavid Howells #include <linux/mtd/super.h>
14acaebfd8SDavid Howells #include <linux/namei.h>
15acaebfd8SDavid Howells #include <linux/ctype.h>
16acaebfd8SDavid Howells 
17acaebfd8SDavid Howells /*
18acaebfd8SDavid Howells  * compare superblocks to see if they're equivalent
19acaebfd8SDavid Howells  * - they are if the underlying MTD device is the same
20acaebfd8SDavid Howells  */
21acaebfd8SDavid Howells static int get_sb_mtd_compare(struct super_block *sb, void *_mtd)
22acaebfd8SDavid Howells {
23acaebfd8SDavid Howells 	struct mtd_info *mtd = _mtd;
24acaebfd8SDavid Howells 
25acaebfd8SDavid Howells 	if (sb->s_mtd == mtd) {
26acaebfd8SDavid Howells 		DEBUG(2, "MTDSB: Match on device %d (\"%s\")\n",
27acaebfd8SDavid Howells 		      mtd->index, mtd->name);
28acaebfd8SDavid Howells 		return 1;
29acaebfd8SDavid Howells 	}
30acaebfd8SDavid Howells 
31acaebfd8SDavid Howells 	DEBUG(2, "MTDSB: No match, device %d (\"%s\"), device %d (\"%s\")\n",
32acaebfd8SDavid Howells 	      sb->s_mtd->index, sb->s_mtd->name, mtd->index, mtd->name);
33acaebfd8SDavid Howells 	return 0;
34acaebfd8SDavid Howells }
35acaebfd8SDavid Howells 
36acaebfd8SDavid Howells /*
37acaebfd8SDavid Howells  * mark the superblock by the MTD device it is using
38acaebfd8SDavid Howells  * - set the device number to be the correct MTD block device for pesuperstence
39acaebfd8SDavid Howells  *   of NFS exports
40acaebfd8SDavid Howells  */
41acaebfd8SDavid Howells static int get_sb_mtd_set(struct super_block *sb, void *_mtd)
42acaebfd8SDavid Howells {
43acaebfd8SDavid Howells 	struct mtd_info *mtd = _mtd;
44acaebfd8SDavid Howells 
45acaebfd8SDavid Howells 	sb->s_mtd = mtd;
46acaebfd8SDavid Howells 	sb->s_dev = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
47acaebfd8SDavid Howells 	return 0;
48acaebfd8SDavid Howells }
49acaebfd8SDavid Howells 
50acaebfd8SDavid Howells /*
51acaebfd8SDavid Howells  * get a superblock on an MTD-backed filesystem
52acaebfd8SDavid Howells  */
53acaebfd8SDavid Howells static int get_sb_mtd_aux(struct file_system_type *fs_type, int flags,
54acaebfd8SDavid Howells 			  const char *dev_name, void *data,
55acaebfd8SDavid Howells 			  struct mtd_info *mtd,
56acaebfd8SDavid Howells 			  int (*fill_super)(struct super_block *, void *, int),
57acaebfd8SDavid Howells 			  struct vfsmount *mnt)
58acaebfd8SDavid Howells {
59acaebfd8SDavid Howells 	struct super_block *sb;
60acaebfd8SDavid Howells 	int ret;
61acaebfd8SDavid Howells 
62acaebfd8SDavid Howells 	sb = sget(fs_type, get_sb_mtd_compare, get_sb_mtd_set, mtd);
63acaebfd8SDavid Howells 	if (IS_ERR(sb))
64acaebfd8SDavid Howells 		goto out_error;
65acaebfd8SDavid Howells 
66acaebfd8SDavid Howells 	if (sb->s_root)
67acaebfd8SDavid Howells 		goto already_mounted;
68acaebfd8SDavid Howells 
69acaebfd8SDavid Howells 	/* fresh new superblock */
70acaebfd8SDavid Howells 	DEBUG(1, "MTDSB: New superblock for device %d (\"%s\")\n",
71acaebfd8SDavid Howells 	      mtd->index, mtd->name);
72acaebfd8SDavid Howells 
7348440e89SDavid Howells 	sb->s_flags = flags;
7448440e89SDavid Howells 
75acaebfd8SDavid Howells 	ret = fill_super(sb, data, flags & MS_SILENT ? 1 : 0);
76acaebfd8SDavid Howells 	if (ret < 0) {
77acaebfd8SDavid Howells 		up_write(&sb->s_umount);
78acaebfd8SDavid Howells 		deactivate_super(sb);
79acaebfd8SDavid Howells 		return ret;
80acaebfd8SDavid Howells 	}
81acaebfd8SDavid Howells 
82acaebfd8SDavid Howells 	/* go */
83acaebfd8SDavid Howells 	sb->s_flags |= MS_ACTIVE;
84acaebfd8SDavid Howells 	return simple_set_mnt(mnt, sb);
85acaebfd8SDavid Howells 
86acaebfd8SDavid Howells 	/* new mountpoint for an already mounted superblock */
87acaebfd8SDavid Howells already_mounted:
88acaebfd8SDavid Howells 	DEBUG(1, "MTDSB: Device %d (\"%s\") is already mounted\n",
89acaebfd8SDavid Howells 	      mtd->index, mtd->name);
90acaebfd8SDavid Howells 	ret = simple_set_mnt(mnt, sb);
91acaebfd8SDavid Howells 	goto out_put;
92acaebfd8SDavid Howells 
93acaebfd8SDavid Howells out_error:
94acaebfd8SDavid Howells 	ret = PTR_ERR(sb);
95acaebfd8SDavid Howells out_put:
96acaebfd8SDavid Howells 	put_mtd_device(mtd);
97acaebfd8SDavid Howells 	return ret;
98acaebfd8SDavid Howells }
99acaebfd8SDavid Howells 
100acaebfd8SDavid Howells /*
101acaebfd8SDavid Howells  * get a superblock on an MTD-backed filesystem by MTD device number
102acaebfd8SDavid Howells  */
103acaebfd8SDavid Howells static int get_sb_mtd_nr(struct file_system_type *fs_type, int flags,
104acaebfd8SDavid Howells 			 const char *dev_name, void *data, int mtdnr,
105acaebfd8SDavid Howells 			 int (*fill_super)(struct super_block *, void *, int),
106acaebfd8SDavid Howells 			 struct vfsmount *mnt)
107acaebfd8SDavid Howells {
108acaebfd8SDavid Howells 	struct mtd_info *mtd;
109acaebfd8SDavid Howells 
110acaebfd8SDavid Howells 	mtd = get_mtd_device(NULL, mtdnr);
111718ea836SDavid Woodhouse 	if (IS_ERR(mtd)) {
112acaebfd8SDavid Howells 		DEBUG(0, "MTDSB: Device #%u doesn't appear to exist\n", mtdnr);
113718ea836SDavid Woodhouse 		return PTR_ERR(mtd);
114acaebfd8SDavid Howells 	}
115acaebfd8SDavid Howells 
116acaebfd8SDavid Howells 	return get_sb_mtd_aux(fs_type, flags, dev_name, data, mtd, fill_super,
117acaebfd8SDavid Howells 			      mnt);
118acaebfd8SDavid Howells }
119acaebfd8SDavid Howells 
120acaebfd8SDavid Howells /*
121acaebfd8SDavid Howells  * set up an MTD-based superblock
122acaebfd8SDavid Howells  */
123acaebfd8SDavid Howells int get_sb_mtd(struct file_system_type *fs_type, int flags,
124acaebfd8SDavid Howells 	       const char *dev_name, void *data,
125acaebfd8SDavid Howells 	       int (*fill_super)(struct super_block *, void *, int),
126acaebfd8SDavid Howells 	       struct vfsmount *mnt)
127acaebfd8SDavid Howells {
128acaebfd8SDavid Howells 	struct nameidata nd;
129acaebfd8SDavid Howells 	int mtdnr, ret;
130acaebfd8SDavid Howells 
131acaebfd8SDavid Howells 	if (!dev_name)
132acaebfd8SDavid Howells 		return -EINVAL;
133acaebfd8SDavid Howells 
134acaebfd8SDavid Howells 	DEBUG(2, "MTDSB: dev_name \"%s\"\n", dev_name);
135acaebfd8SDavid Howells 
136acaebfd8SDavid Howells 	/* the preferred way of mounting in future; especially when
137acaebfd8SDavid Howells 	 * CONFIG_BLOCK=n - we specify the underlying MTD device by number or
138acaebfd8SDavid Howells 	 * by name, so that we don't require block device support to be present
139acaebfd8SDavid Howells 	 * in the kernel. */
140acaebfd8SDavid Howells 	if (dev_name[0] == 'm' && dev_name[1] == 't' && dev_name[2] == 'd') {
141acaebfd8SDavid Howells 		if (dev_name[3] == ':') {
142acaebfd8SDavid Howells 			struct mtd_info *mtd;
143acaebfd8SDavid Howells 
144acaebfd8SDavid Howells 			/* mount by MTD device name */
145acaebfd8SDavid Howells 			DEBUG(1, "MTDSB: mtd:%%s, name \"%s\"\n",
146acaebfd8SDavid Howells 			      dev_name + 4);
147acaebfd8SDavid Howells 
148acaebfd8SDavid Howells 			for (mtdnr = 0; mtdnr < MAX_MTD_DEVICES; mtdnr++) {
149acaebfd8SDavid Howells 				mtd = get_mtd_device(NULL, mtdnr);
150718ea836SDavid Woodhouse 				if (!IS_ERR(mtd)) {
151acaebfd8SDavid Howells 					if (!strcmp(mtd->name, dev_name + 4))
152acaebfd8SDavid Howells 						return get_sb_mtd_aux(
153acaebfd8SDavid Howells 							fs_type, flags,
154acaebfd8SDavid Howells 							dev_name, data, mtd,
155acaebfd8SDavid Howells 							fill_super, mnt);
156acaebfd8SDavid Howells 
157acaebfd8SDavid Howells 					put_mtd_device(mtd);
158acaebfd8SDavid Howells 				}
159acaebfd8SDavid Howells 			}
160acaebfd8SDavid Howells 
161acaebfd8SDavid Howells 			printk(KERN_NOTICE "MTD:"
162acaebfd8SDavid Howells 			       " MTD device with name \"%s\" not found.\n",
163acaebfd8SDavid Howells 			       dev_name + 4);
164acaebfd8SDavid Howells 
165acaebfd8SDavid Howells 		} else if (isdigit(dev_name[3])) {
166acaebfd8SDavid Howells 			/* mount by MTD device number name */
167acaebfd8SDavid Howells 			char *endptr;
168acaebfd8SDavid Howells 
169acaebfd8SDavid Howells 			mtdnr = simple_strtoul(dev_name + 3, &endptr, 0);
170acaebfd8SDavid Howells 			if (!*endptr) {
171acaebfd8SDavid Howells 				/* It was a valid number */
172acaebfd8SDavid Howells 				DEBUG(1, "MTDSB: mtd%%d, mtdnr %d\n",
173acaebfd8SDavid Howells 				      mtdnr);
174acaebfd8SDavid Howells 				return get_sb_mtd_nr(fs_type, flags,
175acaebfd8SDavid Howells 						     dev_name, data,
176acaebfd8SDavid Howells 						     mtdnr, fill_super, mnt);
177acaebfd8SDavid Howells 			}
178acaebfd8SDavid Howells 		}
179acaebfd8SDavid Howells 	}
180acaebfd8SDavid Howells 
181acaebfd8SDavid Howells 	/* try the old way - the hack where we allowed users to mount
182acaebfd8SDavid Howells 	 * /dev/mtdblock$(n) but didn't actually _use_ the blockdev
183acaebfd8SDavid Howells 	 */
184acaebfd8SDavid Howells 	ret = path_lookup(dev_name, LOOKUP_FOLLOW, &nd);
185acaebfd8SDavid Howells 
186acaebfd8SDavid Howells 	DEBUG(1, "MTDSB: path_lookup() returned %d, inode %p\n",
187acaebfd8SDavid Howells 	      ret, nd.dentry ? nd.dentry->d_inode : NULL);
188acaebfd8SDavid Howells 
189acaebfd8SDavid Howells 	if (ret)
190acaebfd8SDavid Howells 		return ret;
191acaebfd8SDavid Howells 
192acaebfd8SDavid Howells 	ret = -EINVAL;
193acaebfd8SDavid Howells 
194acaebfd8SDavid Howells 	if (!S_ISBLK(nd.dentry->d_inode->i_mode))
195acaebfd8SDavid Howells 		goto out;
196acaebfd8SDavid Howells 
197acaebfd8SDavid Howells 	if (nd.mnt->mnt_flags & MNT_NODEV) {
198acaebfd8SDavid Howells 		ret = -EACCES;
199acaebfd8SDavid Howells 		goto out;
200acaebfd8SDavid Howells 	}
201acaebfd8SDavid Howells 
202acaebfd8SDavid Howells 	if (imajor(nd.dentry->d_inode) != MTD_BLOCK_MAJOR)
203acaebfd8SDavid Howells 		goto not_an_MTD_device;
204acaebfd8SDavid Howells 
205acaebfd8SDavid Howells 	mtdnr = iminor(nd.dentry->d_inode);
206acaebfd8SDavid Howells 	path_release(&nd);
207acaebfd8SDavid Howells 
208acaebfd8SDavid Howells 	return get_sb_mtd_nr(fs_type, flags, dev_name, data, mtdnr, fill_super,
209acaebfd8SDavid Howells 			     mnt);
210acaebfd8SDavid Howells 
211acaebfd8SDavid Howells not_an_MTD_device:
212acaebfd8SDavid Howells 	if (!(flags & MS_SILENT))
213acaebfd8SDavid Howells 		printk(KERN_NOTICE
214acaebfd8SDavid Howells 		       "MTD: Attempt to mount non-MTD device \"%s\"\n",
215acaebfd8SDavid Howells 		       dev_name);
216acaebfd8SDavid Howells out:
217acaebfd8SDavid Howells 	path_release(&nd);
218acaebfd8SDavid Howells 	return ret;
219acaebfd8SDavid Howells 
220acaebfd8SDavid Howells }
221acaebfd8SDavid Howells 
222acaebfd8SDavid Howells EXPORT_SYMBOL_GPL(get_sb_mtd);
223acaebfd8SDavid Howells 
224acaebfd8SDavid Howells /*
225acaebfd8SDavid Howells  * destroy an MTD-based superblock
226acaebfd8SDavid Howells  */
227acaebfd8SDavid Howells void kill_mtd_super(struct super_block *sb)
228acaebfd8SDavid Howells {
229acaebfd8SDavid Howells 	generic_shutdown_super(sb);
230acaebfd8SDavid Howells 	put_mtd_device(sb->s_mtd);
231acaebfd8SDavid Howells 	sb->s_mtd = NULL;
232acaebfd8SDavid Howells }
233acaebfd8SDavid Howells 
234acaebfd8SDavid Howells EXPORT_SYMBOL_GPL(kill_mtd_super);
235