diff mbox series

[v6,1/4] cramfs: direct memory access support

Message ID 20171012061613.28705-2-nicolas.pitre@linaro.org
State Accepted
Commit 99c18ce580c6cc6763e694b4ce320d7b226ab59b
Headers show
Series cramfs refresh for embedded usage | expand

Commit Message

Nicolas Pitre Oct. 12, 2017, 6:16 a.m. UTC
Small embedded systems typically execute the kernel code in place (XIP)
directly from flash to save on precious RAM usage. This adds the ability
to consume filesystem data directly from flash to the cramfs filesystem
as well. Cramfs is particularly well suited to this feature as it is
very simple and its RAM usage is already very low, and with this feature
it is possible to use it with no block device support and even lower RAM
usage.

This patch was inspired by a similar patch from Shane Nay dated 17 years
ago that used to be very popular in embedded circles but never made it
into mainline. This is a cleaned-up implementation that uses far fewer
ifdef's and gets the actual memory location for the filesystem image
via MTD at run time. In the context of small IoT deployments, this
functionality has become relevant and useful again.

Signed-off-by: Nicolas Pitre <nico@linaro.org>

---
 fs/cramfs/Kconfig |  30 +++++++-
 fs/cramfs/inode.c | 215 +++++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 202 insertions(+), 43 deletions(-)

-- 
2.9.5

Comments

Chris Brandt Oct. 12, 2017, 5:03 p.m. UTC | #1
On Thursday, October 12, 2017, Nicolas Pitre wrote:
> Small embedded systems typically execute the kernel code in place (XIP)

> directly from flash to save on precious RAM usage. This adds the ability

> to consume filesystem data directly from flash to the cramfs filesystem

> as well. Cramfs is particularly well suited to this feature as it is

> very simple and its RAM usage is already very low, and with this feature

> it is possible to use it with no block device support and even lower RAM

> usage.

> 

> This patch was inspired by a similar patch from Shane Nay dated 17 years

> ago that used to be very popular in embedded circles but never made it

> into mainline. This is a cleaned-up implementation that uses far fewer

> ifdef's and gets the actual memory location for the filesystem image

> via MTD at run time. In the context of small IoT deployments, this

> functionality has become relevant and useful again.

> 

> Signed-off-by: Nicolas Pitre <nico@linaro.org>

> ---

>  fs/cramfs/Kconfig |  30 +++++++-

>  fs/cramfs/inode.c | 215 +++++++++++++++++++++++++++++++++++++++++++------

> -----


Works!

I first applied the MTD patch series from here:

http://patchwork.ozlabs.org/project/linux-mtd/list/?series=7504

Then this v6 patch series on top of it.

I created a mtd-rom/direct-mapped partition and was able to both mount after boot, and also boot as the rootfs.

Log from booting as rootfs:

[    1.586625] cramfs: checking physical address 0x1b000000 for linear cramfs image
[    1.594512] cramfs: linear cramfs image on mtd:rootfs_xipcramfs appears to be 15744 KB in size
[    1.603619] VFS: Mounted root (cramfs filesystem) readonly on device 31:1.


$ cat /proc/self/maps
00008000-000a1000 r-xp 1b005000 1f:01 18192      /bin/busybox
000a9000-000aa000 rw-p 00099000 1f:01 18192      /bin/busybox
000aa000-000ac000 rw-p 00000000 00:00 0          [heap]
b6e07000-b6ee0000 r-xp 00000000 1f:01 766540     /lib/libc-2.18-2013.10.so
b6ee0000-b6ee8000 ---p 000d9000 1f:01 766540     /lib/libc-2.18-2013.10.so
b6ee8000-b6eea000 r--p 000d9000 1f:01 766540     /lib/libc-2.18-2013.10.so
b6eea000-b6eeb000 rw-p 000db000 1f:01 766540     /lib/libc-2.18-2013.10.so
b6eeb000-b6eee000 rw-p 00000000 00:00 0
b6eee000-b6f05000 r-xp 00000000 1f:01 670372     /lib/ld-2.18-2013.10.so
b6f08000-b6f09000 rw-p 00000000 00:00 0
b6f0a000-b6f0c000 rw-p 00000000 00:00 0
b6f0c000-b6f0d000 r--p 00016000 1f:01 670372     /lib/ld-2.18-2013.10.so
b6f0d000-b6f0e000 rw-p 00017000 1f:01 670372     /lib/ld-2.18-2013.10.so
bedb0000-bedd1000 rw-p 00000000 00:00 0          [stack]
bedf4000-bedf5000 r-xp 00000000 00:00 0          [sigpage]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

So far, so good.

Thank you!


Tested-by: Chris Brandt <chris.brandt@renesas.com>
Nicolas Pitre Oct. 13, 2017, 1:20 a.m. UTC | #2
On Thu, 12 Oct 2017, Chris Brandt wrote:

> On Thursday, October 12, 2017, Nicolas Pitre wrote:

> > Small embedded systems typically execute the kernel code in place (XIP)

> > directly from flash to save on precious RAM usage. This adds the ability

> > to consume filesystem data directly from flash to the cramfs filesystem

> > as well. Cramfs is particularly well suited to this feature as it is

> > very simple and its RAM usage is already very low, and with this feature

> > it is possible to use it with no block device support and even lower RAM

> > usage.

> > 

> 

> Works!

> 

> I first applied the MTD patch series from here:

> 

> http://patchwork.ozlabs.org/project/linux-mtd/list/?series=7504

> 

> Then this v6 patch series on top of it.

> 

> I created a mtd-rom/direct-mapped partition and was able to both mount after boot, and also boot as the rootfs.

> 

> So far, so good.

> 

> Thank you!

> 

> Tested-by: Chris Brandt <chris.brandt@renesas.com>


Great! Thanks for testing.

Hopefully this series has finally addressed all objections that were 
raised.


Nicolas
Christoph Hellwig Oct. 13, 2017, 7:30 a.m. UTC | #3
This looks much better, thanks.  I'm not a big fan of the games with
IS_ENABLED and letting the compiler optimize code away, but you're
the maintainer..

Reviewed-by: Christoph Hellwig <hch@lst.de>
Al Viro Oct. 13, 2017, 5:29 p.m. UTC | #4
On Thu, Oct 12, 2017 at 02:16:10AM -0400, Nicolas Pitre wrote:

>  static void cramfs_kill_sb(struct super_block *sb)

>  {

>  	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);

>  

> -	kill_block_super(sb);

> +	if (IS_ENABLED(CCONFIG_CRAMFS_MTD)) {

> +		if (sbi->mtd_point_size)

> +			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> +		if (sb->s_mtd)

> +			kill_mtd_super(sb);


...

> +	mtd_unpoint(sb->s_mtd, 0, PAGE_SIZE);

> +	err = mtd_point(sb->s_mtd, 0, sbi->size, &sbi->mtd_point_size,

> +			&sbi->linear_virt_addr, &sbi->linear_phys_addr);

> +	if (err || sbi->mtd_point_size != sbi->size) {


What happens if that mtd_point() fails?  Note that ->kill_sb() will be
called anyway and ->mtd_point_size is going to be non-zero here...  Do
we get the second mtd_unpoint(), or am I misreading that code?

This logics does look fishy, but I'm not familiar enough with mtd guts
to tell if that's OK...

Rules regarding ->kill_sb(): any struct super_block instance that
got out of sget() and its ilk will have ->kill_sb() called.  In case of
mtd, it's simply "if that thing got past setting ->s_mtd, it will be
passed to ->kill_sb()".

Note, BTW, that you *must* have generic_shutdown_super() called once on
every reachable path in ->kill_sb().  AFAICS your patch is correct in
that area (all instances with that ->s_type are created either in
mount_bdev() or in mount_mtd(); the former will have non-NULL ->s_bdev,
the latter - non-NULL ->s_mtd), but that's one thing to watch out when
doing any modifications.
Nicolas Pitre Oct. 13, 2017, 5:39 p.m. UTC | #5
On Fri, 13 Oct 2017, Al Viro wrote:

> On Thu, Oct 12, 2017 at 02:16:10AM -0400, Nicolas Pitre wrote:

> 

> >  static void cramfs_kill_sb(struct super_block *sb)

> >  {

> >  	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);

> >  

> > -	kill_block_super(sb);

> > +	if (IS_ENABLED(CCONFIG_CRAMFS_MTD)) {

> > +		if (sbi->mtd_point_size)

> > +			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> > +		if (sb->s_mtd)

> > +			kill_mtd_super(sb);

> 

> ...

> 

> > +	mtd_unpoint(sb->s_mtd, 0, PAGE_SIZE);

> > +	err = mtd_point(sb->s_mtd, 0, sbi->size, &sbi->mtd_point_size,

> > +			&sbi->linear_virt_addr, &sbi->linear_phys_addr);

> > +	if (err || sbi->mtd_point_size != sbi->size) {

> 

> What happens if that mtd_point() fails?  Note that ->kill_sb() will be

> called anyway and ->mtd_point_size is going to be non-zero here...


mtd_point() always clears sbi->mtd_point_size first thing upon entry 
even before it has a chance to fail. So it it fails then 
sbi->mtd_point_size will be zero and ->kill_sb() will skip the unpoint 
call.


Nicolas
Al Viro Oct. 13, 2017, 5:52 p.m. UTC | #6
On Fri, Oct 13, 2017 at 01:39:13PM -0400, Nicolas Pitre wrote:
> On Fri, 13 Oct 2017, Al Viro wrote:

> 

> > On Thu, Oct 12, 2017 at 02:16:10AM -0400, Nicolas Pitre wrote:

> > 

> > >  static void cramfs_kill_sb(struct super_block *sb)

> > >  {

> > >  	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);

> > >  

> > > -	kill_block_super(sb);

> > > +	if (IS_ENABLED(CCONFIG_CRAMFS_MTD)) {

> > > +		if (sbi->mtd_point_size)

> > > +			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> > > +		if (sb->s_mtd)

> > > +			kill_mtd_super(sb);

> > 

> > ...

> > 

> > > +	mtd_unpoint(sb->s_mtd, 0, PAGE_SIZE);

> > > +	err = mtd_point(sb->s_mtd, 0, sbi->size, &sbi->mtd_point_size,

> > > +			&sbi->linear_virt_addr, &sbi->linear_phys_addr);

> > > +	if (err || sbi->mtd_point_size != sbi->size) {

> > 

> > What happens if that mtd_point() fails?  Note that ->kill_sb() will be

> > called anyway and ->mtd_point_size is going to be non-zero here...

> 

> mtd_point() always clears sbi->mtd_point_size first thing upon entry 

> even before it has a chance to fail. So it it fails then 

> sbi->mtd_point_size will be zero and ->kill_sb() will skip the unpoint 

> call.


OK...  I wonder if it should simply define stubs for kill_mtd_super(),
mtd_unpoint() and kill_block_super() in !CONFIG_MTD and !CONFIG_BLOCK
cases.  mount_mtd() and mount_bdev() as well - e.g.  mount_bdev()
returning ERR_PTR(-ENODEV) and kill_block_super() being simply BUG()
in !CONFIG_BLOCK case.  Then cramfs_kill_sb() would be
	if (sb->s_mtd) {
		if (sbi->mtd_point_size)
			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);
		kill_mtd_super(sb);
	} else {
		kill_block_super(sb);
	}
	kfree(sbi);

Wait.  Looking at that code... what happens if you hit this failure
exit:
        sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);
        if (!sbi)
                return -ENOMEM;

Current cramfs_kill_sb() will do kill_block_super() and kfree(NULL), which
works nicely, but you are dereferencing that sucker, not just passing it
to kfree().  IOW, that if (sbi->....) ought to be if (sbi && sbi->...)
Nicolas Pitre Oct. 13, 2017, 6:18 p.m. UTC | #7
On Fri, 13 Oct 2017, Al Viro wrote:

> On Fri, Oct 13, 2017 at 01:39:13PM -0400, Nicolas Pitre wrote:

> > On Fri, 13 Oct 2017, Al Viro wrote:

> > 

> > > On Thu, Oct 12, 2017 at 02:16:10AM -0400, Nicolas Pitre wrote:

> > > 

> > > >  static void cramfs_kill_sb(struct super_block *sb)

> > > >  {

> > > >  	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);

> > > >  

> > > > -	kill_block_super(sb);

> > > > +	if (IS_ENABLED(CCONFIG_CRAMFS_MTD)) {

> > > > +		if (sbi->mtd_point_size)

> > > > +			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> > > > +		if (sb->s_mtd)

> > > > +			kill_mtd_super(sb);

> > > 

> > > ...

> > > 

> > > > +	mtd_unpoint(sb->s_mtd, 0, PAGE_SIZE);

> > > > +	err = mtd_point(sb->s_mtd, 0, sbi->size, &sbi->mtd_point_size,

> > > > +			&sbi->linear_virt_addr, &sbi->linear_phys_addr);

> > > > +	if (err || sbi->mtd_point_size != sbi->size) {

> > > 

> > > What happens if that mtd_point() fails?  Note that ->kill_sb() will be

> > > called anyway and ->mtd_point_size is going to be non-zero here...

> > 

> > mtd_point() always clears sbi->mtd_point_size first thing upon entry 

> > even before it has a chance to fail. So it it fails then 

> > sbi->mtd_point_size will be zero and ->kill_sb() will skip the unpoint 

> > call.

> 

> OK...  I wonder if it should simply define stubs for kill_mtd_super(),

> mtd_unpoint() and kill_block_super() in !CONFIG_MTD and !CONFIG_BLOCK

> cases.  mount_mtd() and mount_bdev() as well - e.g.  mount_bdev()

> returning ERR_PTR(-ENODEV) and kill_block_super() being simply BUG()

> in !CONFIG_BLOCK case.  Then cramfs_kill_sb() would be

> 	if (sb->s_mtd) {

> 		if (sbi->mtd_point_size)

> 			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> 		kill_mtd_super(sb);

> 	} else {

> 		kill_block_super(sb);

> 	}

> 	kfree(sbi);


What I really like about IS_ENABLED() usage is the immediate build 
coverage without having to run all config combinations. The compiler 
will discard unneeded code and avoid pesky unused variable warnings that 
require ugly #ifdefs otherwise.

> Wait.  Looking at that code... what happens if you hit this failure

> exit:

>         sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);

>         if (!sbi)

>                 return -ENOMEM;

> 

> Current cramfs_kill_sb() will do kill_block_super() and kfree(NULL), which

> works nicely, but you are dereferencing that sucker, not just passing it

> to kfree().  IOW, that if (sbi->....) ought to be if (sbi && sbi->...)


Right, good catch.
Fixed in my tree now.


Nicolas
Nicolas Pitre Oct. 13, 2017, 8:09 p.m. UTC | #8
On Fri, 13 Oct 2017, Al Viro wrote:

> OK...  I wonder if it should simply define stubs for kill_mtd_super(),

> mtd_unpoint() and kill_block_super() in !CONFIG_MTD and !CONFIG_BLOCK

> cases.  mount_mtd() and mount_bdev() as well - e.g.  mount_bdev()

> returning ERR_PTR(-ENODEV) and kill_block_super() being simply BUG()

> in !CONFIG_BLOCK case.  Then cramfs_kill_sb() would be

> 	if (sb->s_mtd) {

> 		if (sbi->mtd_point_size)

> 			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> 		kill_mtd_super(sb);

> 	} else {

> 		kill_block_super(sb);

> 	}

> 	kfree(sbi);


Well... Stubs have to be named differently or they conflict with 
existing declarations. At that point that makes for more lines of code 
compared to the current patch and the naming indirection makes it less 
obvious when reading the code. Alternatively I could add those stubs in 
the corresponding header files and #ifdef the existing declarations 
away. That might look somewhat less cluttered in the main code but it 
also hides what is actually going on and left me unconvinced. And I'm 
not sure this is worth it in the end given this is not a common 
occurrence in the kernel either.

Still, I've rearanged it slightly and fixed the null deref you spotted 
earlier. Latest patch below:

----- >8
Subject: [PATCH] cramfs: direct memory access support

Small embedded systems typically execute the kernel code in place (XIP)
directly from flash to save on precious RAM usage. This patch adds to
cramfs the ability to consume filesystem data directly from flash as
well. Cramfs is particularly well suited to this feature as it is very
simple with low RAM usage, and with this feature it is possible to use
it with no block device support and consequently even lower RAM usage.

This patch was inspired by a similar patch from Shane Nay dated 17 years
ago that used to be very popular in embedded circles but never made it
into mainline. This is a cleaned-up implementation that uses far fewer
ifdef's and gets the actual memory location for the filesystem image
via MTD at run time. In the context of small IoT deployments, this
functionality has become relevant and useful again.

Signed-off-by: Nicolas Pitre <nico@linaro.org>

Tested-by: Chris Brandt <chris.brandt@renesas.com>

Reviewed-by: Christoph Hellwig <hch@lst.de>


diff --git a/fs/cramfs/Kconfig b/fs/cramfs/Kconfig
index 11b29d491b..ef86b06bc0 100644
--- a/fs/cramfs/Kconfig
+++ b/fs/cramfs/Kconfig
@@ -1,6 +1,5 @@
 config CRAMFS
 	tristate "Compressed ROM file system support (cramfs) (OBSOLETE)"
-	depends on BLOCK
 	select ZLIB_INFLATE
 	help
 	  Saying Y here includes support for CramFs (Compressed ROM File
@@ -20,3 +19,32 @@ config CRAMFS
 	  in terms of performance and features.
 
 	  If unsure, say N.
+
+config CRAMFS_BLOCKDEV
+	bool "Support CramFs image over a regular block device" if EXPERT
+	depends on CRAMFS && BLOCK
+	default y
+	help
+	  This option allows the CramFs driver to load data from a regular
+	  block device such a disk partition or a ramdisk.
+
+config CRAMFS_MTD
+	bool "Support CramFs image directly mapped in physical memory"
+	depends on CRAMFS && MTD
+	default y if !CRAMFS_BLOCKDEV
+	help
+	  This option allows the CramFs driver to load data directly from
+	  a linear adressed memory range (usually non volatile memory
+	  like flash) instead of going through the block device layer.
+	  This saves some memory since no intermediate buffering is
+	  necessary.
+
+	  The location of the CramFs image is determined by a
+	  MTD device capable of direct memory mapping e.g. from
+	  the 'physmap' map driver or a resulting MTD partition.
+	  For example, this would mount the cramfs image stored in
+	  the MTD partition named "xip_fs" on the /mnt mountpoint:
+
+	  mount -t cramfs mtd:xip_fs /mnt
+
+	  If unsure, say N.
diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c
index 7919967488..bcdccb7a82 100644
--- a/fs/cramfs/inode.c
+++ b/fs/cramfs/inode.c
@@ -19,6 +19,8 @@
 #include <linux/init.h>
 #include <linux/string.h>
 #include <linux/blkdev.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/super.h>
 #include <linux/slab.h>
 #include <linux/vfs.h>
 #include <linux/mutex.h>
@@ -36,6 +38,9 @@ struct cramfs_sb_info {
 	unsigned long blocks;
 	unsigned long files;
 	unsigned long flags;
+	void *linear_virt_addr;
+	resource_size_t linear_phys_addr;
+	size_t mtd_point_size;
 };
 
 static inline struct cramfs_sb_info *CRAMFS_SB(struct super_block *sb)
@@ -140,6 +145,9 @@ static struct inode *get_cramfs_inode(struct super_block *sb,
  * BLKS_PER_BUF*PAGE_SIZE, so that the caller doesn't need to
  * worry about end-of-buffer issues even when decompressing a full
  * page cache.
+ *
+ * Note: This is all optimized away at compile time when
+ *       CONFIG_CRAMFS_BLOCKDEV=n.
  */
 #define READ_BUFFERS (2)
 /* NEXT_BUFFER(): Loop over [0..(READ_BUFFERS-1)]. */
@@ -160,10 +168,10 @@ static struct super_block *buffer_dev[READ_BUFFERS];
 static int next_buffer;
 
 /*
- * Returns a pointer to a buffer containing at least LEN bytes of
- * filesystem starting at byte offset OFFSET into the filesystem.
+ * Populate our block cache and return a pointer to it.
  */
-static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned int len)
+static void *cramfs_blkdev_read(struct super_block *sb, unsigned int offset,
+				unsigned int len)
 {
 	struct address_space *mapping = sb->s_bdev->bd_inode->i_mapping;
 	struct page *pages[BLKS_PER_BUF];
@@ -239,11 +247,49 @@ static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned i
 	return read_buffers[buffer] + offset;
 }
 
+/*
+ * Return a pointer to the linearly addressed cramfs image in memory.
+ */
+static void *cramfs_direct_read(struct super_block *sb, unsigned int offset,
+				unsigned int len)
+{
+	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
+
+	if (!len)
+		return NULL;
+	if (len > sbi->size || offset > sbi->size - len)
+		return page_address(ZERO_PAGE(0));
+	return sbi->linear_virt_addr + offset;
+}
+
+/*
+ * Returns a pointer to a buffer containing at least LEN bytes of
+ * filesystem starting at byte offset OFFSET into the filesystem.
+ */
+static void *cramfs_read(struct super_block *sb, unsigned int offset,
+			 unsigned int len)
+{
+	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
+
+	if (IS_ENABLED(CONFIG_CRAMFS_MTD) && sbi->linear_virt_addr)
+		return cramfs_direct_read(sb, offset, len);
+	else if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV))
+		return cramfs_blkdev_read(sb, offset, len);
+	else
+		return NULL;
+}
+
 static void cramfs_kill_sb(struct super_block *sb)
 {
 	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
 
-	kill_block_super(sb);
+	if (IS_ENABLED(CCONFIG_CRAMFS_MTD) && sb->s_mtd) {
+		if (sbi && sbi->mtd_point_size)
+			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);
+		kill_mtd_super(sb);
+	} else if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV) && sb->s_bdev) {
+		kill_block_super(sb);
+	}
 	kfree(sbi);
 }
 
@@ -254,34 +300,24 @@ static int cramfs_remount(struct super_block *sb, int *flags, char *data)
 	return 0;
 }
 
-static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
+static int cramfs_read_super(struct super_block *sb,
+			     struct cramfs_super *super, int silent)
 {
-	int i;
-	struct cramfs_super super;
+	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
 	unsigned long root_offset;
-	struct cramfs_sb_info *sbi;
-	struct inode *root;
-
-	sb->s_flags |= MS_RDONLY;
-
-	sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);
-	if (!sbi)
-		return -ENOMEM;
-	sb->s_fs_info = sbi;
 
-	/* Invalidate the read buffers on mount: think disk change.. */
-	mutex_lock(&read_mutex);
-	for (i = 0; i < READ_BUFFERS; i++)
-		buffer_blocknr[i] = -1;
+	/* We don't know the real size yet */
+	sbi->size = PAGE_SIZE;
 
 	/* Read the first block and get the superblock from it */
-	memcpy(&super, cramfs_read(sb, 0, sizeof(super)), sizeof(super));
+	mutex_lock(&read_mutex);
+	memcpy(super, cramfs_read(sb, 0, sizeof(*super)), sizeof(*super));
 	mutex_unlock(&read_mutex);
 
 	/* Do sanity checks on the superblock */
-	if (super.magic != CRAMFS_MAGIC) {
+	if (super->magic != CRAMFS_MAGIC) {
 		/* check for wrong endianness */
-		if (super.magic == CRAMFS_MAGIC_WEND) {
+		if (super->magic == CRAMFS_MAGIC_WEND) {
 			if (!silent)
 				pr_err("wrong endianness\n");
 			return -EINVAL;
@@ -289,10 +325,12 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 
 		/* check at 512 byte offset */
 		mutex_lock(&read_mutex);
-		memcpy(&super, cramfs_read(sb, 512, sizeof(super)), sizeof(super));
+		memcpy(super,
+		       cramfs_read(sb, 512, sizeof(*super)),
+		       sizeof(*super));
 		mutex_unlock(&read_mutex);
-		if (super.magic != CRAMFS_MAGIC) {
-			if (super.magic == CRAMFS_MAGIC_WEND && !silent)
+		if (super->magic != CRAMFS_MAGIC) {
+			if (super->magic == CRAMFS_MAGIC_WEND && !silent)
 				pr_err("wrong endianness\n");
 			else if (!silent)
 				pr_err("wrong magic\n");
@@ -301,34 +339,34 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 	}
 
 	/* get feature flags first */
-	if (super.flags & ~CRAMFS_SUPPORTED_FLAGS) {
+	if (super->flags & ~CRAMFS_SUPPORTED_FLAGS) {
 		pr_err("unsupported filesystem features\n");
 		return -EINVAL;
 	}
 
 	/* Check that the root inode is in a sane state */
-	if (!S_ISDIR(super.root.mode)) {
+	if (!S_ISDIR(super->root.mode)) {
 		pr_err("root is not a directory\n");
 		return -EINVAL;
 	}
 	/* correct strange, hard-coded permissions of mkcramfs */
-	super.root.mode |= (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+	super->root.mode |= 0555;
 
-	root_offset = super.root.offset << 2;
-	if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) {
-		sbi->size = super.size;
-		sbi->blocks = super.fsid.blocks;
-		sbi->files = super.fsid.files;
+	root_offset = super->root.offset << 2;
+	if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) {
+		sbi->size = super->size;
+		sbi->blocks = super->fsid.blocks;
+		sbi->files = super->fsid.files;
 	} else {
 		sbi->size = 1<<28;
 		sbi->blocks = 0;
 		sbi->files = 0;
 	}
-	sbi->magic = super.magic;
-	sbi->flags = super.flags;
+	sbi->magic = super->magic;
+	sbi->flags = super->flags;
 	if (root_offset == 0)
 		pr_info("empty filesystem");
-	else if (!(super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) &&
+	else if (!(super->flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) &&
 		 ((root_offset != sizeof(struct cramfs_super)) &&
 		  (root_offset != 512 + sizeof(struct cramfs_super))))
 	{
@@ -336,9 +374,18 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 		return -EINVAL;
 	}
 
+	return 0;
+}
+
+static int cramfs_finalize_super(struct super_block *sb,
+				 struct cramfs_inode *cramfs_root)
+{
+	struct inode *root;
+
 	/* Set it all up.. */
+	sb->s_flags |= MS_RDONLY;
 	sb->s_op = &cramfs_ops;
-	root = get_cramfs_inode(sb, &super.root, 0);
+	root = get_cramfs_inode(sb, cramfs_root, 0);
 	if (IS_ERR(root))
 		return PTR_ERR(root);
 	sb->s_root = d_make_root(root);
@@ -347,10 +394,79 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 	return 0;
 }
 
+static int cramfs_blkdev_fill_super(struct super_block *sb, void *data,
+				    int silent)
+{
+	struct cramfs_sb_info *sbi;
+	struct cramfs_super super;
+	int i, err;
+
+	sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+	sb->s_fs_info = sbi;
+
+	/* Invalidate the read buffers on mount: think disk change.. */
+	for (i = 0; i < READ_BUFFERS; i++)
+		buffer_blocknr[i] = -1;
+
+	err = cramfs_read_super(sb, &super, silent);
+	if (err)
+		return err;
+	return cramfs_finalize_super(sb, &super.root);
+}
+
+static int cramfs_mtd_fill_super(struct super_block *sb, void *data,
+				 int silent)
+{
+	struct cramfs_sb_info *sbi;
+	struct cramfs_super super;
+	int err;
+
+	sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+	sb->s_fs_info = sbi;
+
+	/* Map only one page for now.  Will remap it when fs size is known. */
+	err = mtd_point(sb->s_mtd, 0, PAGE_SIZE, &sbi->mtd_point_size,
+			&sbi->linear_virt_addr, &sbi->linear_phys_addr);
+	if (err || sbi->mtd_point_size != PAGE_SIZE) {
+		pr_err("unable to get direct memory access to mtd:%s\n",
+		       sb->s_mtd->name);
+		return err ? : -ENODATA;
+	}
+
+	pr_info("checking physical address %pap for linear cramfs image\n",
+		&sbi->linear_phys_addr);
+	err = cramfs_read_super(sb, &super, silent);
+	if (err)
+		return err;
+
+	/* Remap the whole filesystem now */
+	pr_info("linear cramfs image on mtd:%s appears to be %lu KB in size\n",
+		sb->s_mtd->name, sbi->size/1024);
+	mtd_unpoint(sb->s_mtd, 0, PAGE_SIZE);
+	err = mtd_point(sb->s_mtd, 0, sbi->size, &sbi->mtd_point_size,
+			&sbi->linear_virt_addr, &sbi->linear_phys_addr);
+	if (err || sbi->mtd_point_size != sbi->size) {
+		pr_err("unable to get direct memory access to mtd:%s\n",
+		       sb->s_mtd->name);
+		return err ? : -ENODATA;
+	}
+
+	return cramfs_finalize_super(sb, &super.root);
+}
+
 static int cramfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
 	struct super_block *sb = dentry->d_sb;
-	u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
+	u64 id = 0;
+
+	if (sb->s_bdev)
+		id = huge_encode_dev(sb->s_bdev->bd_dev);
+	else if (sb->s_dev)
+		id = huge_encode_dev(sb->s_dev);
 
 	buf->f_type = CRAMFS_MAGIC;
 	buf->f_bsize = PAGE_SIZE;
@@ -573,10 +689,22 @@ static const struct super_operations cramfs_ops = {
 	.statfs		= cramfs_statfs,
 };
 
-static struct dentry *cramfs_mount(struct file_system_type *fs_type,
-	int flags, const char *dev_name, void *data)
+static struct dentry *cramfs_mount(struct file_system_type *fs_type, int flags,
+				   const char *dev_name, void *data)
 {
-	return mount_bdev(fs_type, flags, dev_name, data, cramfs_fill_super);
+	struct dentry *ret = ERR_PTR(-ENOPROTOOPT);
+
+	if (IS_ENABLED(CONFIG_CRAMFS_MTD)) {
+		ret = mount_mtd(fs_type, flags, dev_name, data,
+				cramfs_mtd_fill_super);
+		if (!IS_ERR(ret))
+			return ret;
+	}
+	if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV)) {
+		ret = mount_bdev(fs_type, flags, dev_name, data,
+				 cramfs_blkdev_fill_super);
+	}
+	return ret;
 }
 
 static struct file_system_type cramfs_fs_type = {
Al Viro Oct. 14, 2017, 12:31 a.m. UTC | #9
On Fri, Oct 13, 2017 at 04:09:23PM -0400, Nicolas Pitre wrote:
> On Fri, 13 Oct 2017, Al Viro wrote:

> 

> > OK...  I wonder if it should simply define stubs for kill_mtd_super(),

> > mtd_unpoint() and kill_block_super() in !CONFIG_MTD and !CONFIG_BLOCK

> > cases.  mount_mtd() and mount_bdev() as well - e.g.  mount_bdev()

> > returning ERR_PTR(-ENODEV) and kill_block_super() being simply BUG()

> > in !CONFIG_BLOCK case.  Then cramfs_kill_sb() would be

> > 	if (sb->s_mtd) {

> > 		if (sbi->mtd_point_size)

> > 			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> > 		kill_mtd_super(sb);

> > 	} else {

> > 		kill_block_super(sb);

> > 	}

> > 	kfree(sbi);

> 

> Well... Stubs have to be named differently or they conflict with 

> existing declarations. At that point that makes for more lines of code 

> compared to the current patch and the naming indirection makes it less 

> obvious when reading the code. Alternatively I could add those stubs in 

> the corresponding header files and #ifdef the existing declarations 

> away. That might look somewhat less cluttered in the main code but it 

> also hides what is actually going on and left me unconvinced. And I'm 

> not sure this is worth it in the end given this is not a common 

> occurrence in the kernel either.


What I mean is this (completely untested) for CONFIG_BLOCK side of things,
with something similar for CONFIG_MTD one:

Provide definitions of mount_bdev/kill_block_super() in case !CONFIG_BLOCK

mount_bdev() and kill_block_super() are defined only when CONFIG_BLOCK is
defined; however, their declarations in fs.h are unconditional.  We could
make these conditional upon CONFIG_BLOCK as well, but it's easy to provide
inline definitions for !CONFIG_BLOCK case - mount_bdev() should fail with
ENODEV, while kill_block_super() can be simply BUG(); there should be no
superblock instances with non-NULL ->s_bdev on such configs.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

---
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 339e73742e73..e773c1c51aad 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2094,9 +2094,18 @@ struct file_system_type {
 extern struct dentry *mount_ns(struct file_system_type *fs_type,
 	int flags, void *data, void *ns, struct user_namespace *user_ns,
 	int (*fill_super)(struct super_block *, void *, int));
+#ifdef CONFIG_BLOCK
 extern struct dentry *mount_bdev(struct file_system_type *fs_type,
 	int flags, const char *dev_name, void *data,
 	int (*fill_super)(struct super_block *, void *, int));
+#else
+static inline struct dentry *mount_bdev(struct file_system_type *fs_type,
+	int flags, const char *dev_name, void *data,
+	int (*fill_super)(struct super_block *, void *, int))
+{
+	return ERR_PTR(-ENODEV);
+}
+#endif
 extern struct dentry *mount_single(struct file_system_type *fs_type,
 	int flags, void *data,
 	int (*fill_super)(struct super_block *, void *, int));
@@ -2105,7 +2114,14 @@ extern struct dentry *mount_nodev(struct file_system_type *fs_type,
 	int (*fill_super)(struct super_block *, void *, int));
 extern struct dentry *mount_subtree(struct vfsmount *mnt, const char *path);
 void generic_shutdown_super(struct super_block *sb);
+#ifdef CONFIG_BLOCK
 void kill_block_super(struct super_block *sb);
+#else
+static inline void kill_block_super(struct super_block *sb)
+{
+	BUG();
+}
+#endif
 void kill_anon_super(struct super_block *sb);
 void kill_litter_super(struct super_block *sb);
 void deactivate_super(struct super_block *sb);
Nicolas Pitre Oct. 14, 2017, 2:25 a.m. UTC | #10
On Sat, 14 Oct 2017, Al Viro wrote:

> On Fri, Oct 13, 2017 at 04:09:23PM -0400, Nicolas Pitre wrote:

> > On Fri, 13 Oct 2017, Al Viro wrote:

> > 

> > > OK...  I wonder if it should simply define stubs for kill_mtd_super(),

> > > mtd_unpoint() and kill_block_super() in !CONFIG_MTD and !CONFIG_BLOCK

> > > cases.  mount_mtd() and mount_bdev() as well - e.g.  mount_bdev()

> > > returning ERR_PTR(-ENODEV) and kill_block_super() being simply BUG()

> > > in !CONFIG_BLOCK case.  Then cramfs_kill_sb() would be

> > > 	if (sb->s_mtd) {

> > > 		if (sbi->mtd_point_size)

> > > 			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> > > 		kill_mtd_super(sb);

> > > 	} else {

> > > 		kill_block_super(sb);

> > > 	}

> > > 	kfree(sbi);

> > 

> > Well... Stubs have to be named differently or they conflict with 

> > existing declarations. At that point that makes for more lines of code 

> > compared to the current patch and the naming indirection makes it less 

> > obvious when reading the code. Alternatively I could add those stubs in 

> > the corresponding header files and #ifdef the existing declarations 

> > away. That might look somewhat less cluttered in the main code but it 

> > also hides what is actually going on and left me unconvinced. And I'm 

> > not sure this is worth it in the end given this is not a common 

> > occurrence in the kernel either.

> 

> What I mean is this (completely untested) for CONFIG_BLOCK side of things,

> with something similar for CONFIG_MTD one:

> 

> Provide definitions of mount_bdev/kill_block_super() in case !CONFIG_BLOCK


Yes, that's what I thought you meant, which corresponds to the second 
part of my comment above. And as I said I'm not convinced this hiding of 
kernel config effects is better for understanding what is actually going 
on locally, and my own preference is how things are right now.

But if you confirm you really want things that other way then I'll 
oblige and repost.


Nicolas
Nicolas Pitre Oct. 14, 2017, 2:37 a.m. UTC | #11
On Fri, 13 Oct 2017, Nicolas Pitre wrote:

> On Sat, 14 Oct 2017, Al Viro wrote:

> 

> > On Fri, Oct 13, 2017 at 04:09:23PM -0400, Nicolas Pitre wrote:

> > > On Fri, 13 Oct 2017, Al Viro wrote:

> > > 

> > > > OK...  I wonder if it should simply define stubs for kill_mtd_super(),

> > > > mtd_unpoint() and kill_block_super() in !CONFIG_MTD and !CONFIG_BLOCK

> > > > cases.  mount_mtd() and mount_bdev() as well - e.g.  mount_bdev()

> > > > returning ERR_PTR(-ENODEV) and kill_block_super() being simply BUG()

> > > > in !CONFIG_BLOCK case.  Then cramfs_kill_sb() would be

> > > > 	if (sb->s_mtd) {

> > > > 		if (sbi->mtd_point_size)

> > > > 			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);

> > > > 		kill_mtd_super(sb);

> > > > 	} else {

> > > > 		kill_block_super(sb);

> > > > 	}

> > > > 	kfree(sbi);

> > > 

> > > Well... Stubs have to be named differently or they conflict with 

> > > existing declarations. At that point that makes for more lines of code 

> > > compared to the current patch and the naming indirection makes it less 

> > > obvious when reading the code. Alternatively I could add those stubs in 

> > > the corresponding header files and #ifdef the existing declarations 

> > > away. That might look somewhat less cluttered in the main code but it 

> > > also hides what is actually going on and left me unconvinced. And I'm 

> > > not sure this is worth it in the end given this is not a common 

> > > occurrence in the kernel either.

> > 

> > What I mean is this (completely untested) for CONFIG_BLOCK side of things,

> > with something similar for CONFIG_MTD one:

> > 

> > Provide definitions of mount_bdev/kill_block_super() in case !CONFIG_BLOCK

> 

> Yes, that's what I thought you meant, which corresponds to the second 

> part of my comment above. And as I said I'm not convinced this hiding of 

> kernel config effects is better for understanding what is actually going 

> on locally, and my own preference is how things are right now.


Another case that your suggestion doesn't cover well is the ability to 
still have block device support in the kernel for other filesystems but 
_without_ block device support in the cramfs case. In other words, 
having CONFIG_BLOCK=y and CONFIG_CRAMFS_BLOCKDEV=n. This is a common 
case to have embedded devices with the root filesystem in flash while 
still needing access to a FAT filesystem on SD cards. Your stubs are 
conditional on CONFIG_BLOCK but that is not sufficient in this example.


Nicolas
diff mbox series

Patch

diff --git a/fs/cramfs/Kconfig b/fs/cramfs/Kconfig
index 11b29d491b..ef86b06bc0 100644
--- a/fs/cramfs/Kconfig
+++ b/fs/cramfs/Kconfig
@@ -1,6 +1,5 @@ 
 config CRAMFS
 	tristate "Compressed ROM file system support (cramfs) (OBSOLETE)"
-	depends on BLOCK
 	select ZLIB_INFLATE
 	help
 	  Saying Y here includes support for CramFs (Compressed ROM File
@@ -20,3 +19,32 @@  config CRAMFS
 	  in terms of performance and features.
 
 	  If unsure, say N.
+
+config CRAMFS_BLOCKDEV
+	bool "Support CramFs image over a regular block device" if EXPERT
+	depends on CRAMFS && BLOCK
+	default y
+	help
+	  This option allows the CramFs driver to load data from a regular
+	  block device such a disk partition or a ramdisk.
+
+config CRAMFS_MTD
+	bool "Support CramFs image directly mapped in physical memory"
+	depends on CRAMFS && MTD
+	default y if !CRAMFS_BLOCKDEV
+	help
+	  This option allows the CramFs driver to load data directly from
+	  a linear adressed memory range (usually non volatile memory
+	  like flash) instead of going through the block device layer.
+	  This saves some memory since no intermediate buffering is
+	  necessary.
+
+	  The location of the CramFs image is determined by a
+	  MTD device capable of direct memory mapping e.g. from
+	  the 'physmap' map driver or a resulting MTD partition.
+	  For example, this would mount the cramfs image stored in
+	  the MTD partition named "xip_fs" on the /mnt mountpoint:
+
+	  mount -t cramfs mtd:xip_fs /mnt
+
+	  If unsure, say N.
diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c
index 7919967488..321a1fe17e 100644
--- a/fs/cramfs/inode.c
+++ b/fs/cramfs/inode.c
@@ -19,6 +19,8 @@ 
 #include <linux/init.h>
 #include <linux/string.h>
 #include <linux/blkdev.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/super.h>
 #include <linux/slab.h>
 #include <linux/vfs.h>
 #include <linux/mutex.h>
@@ -36,6 +38,9 @@  struct cramfs_sb_info {
 	unsigned long blocks;
 	unsigned long files;
 	unsigned long flags;
+	void *linear_virt_addr;
+	resource_size_t linear_phys_addr;
+	size_t mtd_point_size;
 };
 
 static inline struct cramfs_sb_info *CRAMFS_SB(struct super_block *sb)
@@ -140,6 +145,9 @@  static struct inode *get_cramfs_inode(struct super_block *sb,
  * BLKS_PER_BUF*PAGE_SIZE, so that the caller doesn't need to
  * worry about end-of-buffer issues even when decompressing a full
  * page cache.
+ *
+ * Note: This is all optimized away at compile time when
+ *       CONFIG_CRAMFS_BLOCKDEV=n.
  */
 #define READ_BUFFERS (2)
 /* NEXT_BUFFER(): Loop over [0..(READ_BUFFERS-1)]. */
@@ -160,10 +168,10 @@  static struct super_block *buffer_dev[READ_BUFFERS];
 static int next_buffer;
 
 /*
- * Returns a pointer to a buffer containing at least LEN bytes of
- * filesystem starting at byte offset OFFSET into the filesystem.
+ * Populate our block cache and return a pointer from it.
  */
-static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned int len)
+static void *cramfs_blkdev_read(struct super_block *sb, unsigned int offset,
+				unsigned int len)
 {
 	struct address_space *mapping = sb->s_bdev->bd_inode->i_mapping;
 	struct page *pages[BLKS_PER_BUF];
@@ -239,11 +247,52 @@  static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned i
 	return read_buffers[buffer] + offset;
 }
 
+/*
+ * Return a pointer to the linearly addressed cramfs image in memory.
+ */
+static void *cramfs_direct_read(struct super_block *sb, unsigned int offset,
+				unsigned int len)
+{
+	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
+
+	if (!len)
+		return NULL;
+	if (len > sbi->size || offset > sbi->size - len)
+		return page_address(ZERO_PAGE(0));
+	return sbi->linear_virt_addr + offset;
+}
+
+/*
+ * Returns a pointer to a buffer containing at least LEN bytes of
+ * filesystem starting at byte offset OFFSET into the filesystem.
+ */
+static void *cramfs_read(struct super_block *sb, unsigned int offset,
+			 unsigned int len)
+{
+	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
+
+	if (IS_ENABLED(CONFIG_CRAMFS_MTD) && sbi->linear_virt_addr)
+		return cramfs_direct_read(sb, offset, len);
+	else if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV))
+		return cramfs_blkdev_read(sb, offset, len);
+	else
+		return NULL;
+}
+
 static void cramfs_kill_sb(struct super_block *sb)
 {
 	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
 
-	kill_block_super(sb);
+	if (IS_ENABLED(CCONFIG_CRAMFS_MTD)) {
+		if (sbi->mtd_point_size)
+			mtd_unpoint(sb->s_mtd, 0, sbi->mtd_point_size);
+		if (sb->s_mtd)
+			kill_mtd_super(sb);
+	}
+	if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV)) {
+		if (sb->s_bdev)
+			kill_block_super(sb);
+	}
 	kfree(sbi);
 }
 
@@ -254,34 +303,24 @@  static int cramfs_remount(struct super_block *sb, int *flags, char *data)
 	return 0;
 }
 
-static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
+static int cramfs_read_super(struct super_block *sb,
+			     struct cramfs_super *super, int silent)
 {
-	int i;
-	struct cramfs_super super;
+	struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
 	unsigned long root_offset;
-	struct cramfs_sb_info *sbi;
-	struct inode *root;
-
-	sb->s_flags |= MS_RDONLY;
-
-	sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);
-	if (!sbi)
-		return -ENOMEM;
-	sb->s_fs_info = sbi;
 
-	/* Invalidate the read buffers on mount: think disk change.. */
-	mutex_lock(&read_mutex);
-	for (i = 0; i < READ_BUFFERS; i++)
-		buffer_blocknr[i] = -1;
+	/* We don't know the real size yet */
+	sbi->size = PAGE_SIZE;
 
 	/* Read the first block and get the superblock from it */
-	memcpy(&super, cramfs_read(sb, 0, sizeof(super)), sizeof(super));
+	mutex_lock(&read_mutex);
+	memcpy(super, cramfs_read(sb, 0, sizeof(*super)), sizeof(*super));
 	mutex_unlock(&read_mutex);
 
 	/* Do sanity checks on the superblock */
-	if (super.magic != CRAMFS_MAGIC) {
+	if (super->magic != CRAMFS_MAGIC) {
 		/* check for wrong endianness */
-		if (super.magic == CRAMFS_MAGIC_WEND) {
+		if (super->magic == CRAMFS_MAGIC_WEND) {
 			if (!silent)
 				pr_err("wrong endianness\n");
 			return -EINVAL;
@@ -289,10 +328,12 @@  static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 
 		/* check at 512 byte offset */
 		mutex_lock(&read_mutex);
-		memcpy(&super, cramfs_read(sb, 512, sizeof(super)), sizeof(super));
+		memcpy(super,
+		       cramfs_read(sb, 512, sizeof(*super)),
+		       sizeof(*super));
 		mutex_unlock(&read_mutex);
-		if (super.magic != CRAMFS_MAGIC) {
-			if (super.magic == CRAMFS_MAGIC_WEND && !silent)
+		if (super->magic != CRAMFS_MAGIC) {
+			if (super->magic == CRAMFS_MAGIC_WEND && !silent)
 				pr_err("wrong endianness\n");
 			else if (!silent)
 				pr_err("wrong magic\n");
@@ -301,34 +342,34 @@  static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 	}
 
 	/* get feature flags first */
-	if (super.flags & ~CRAMFS_SUPPORTED_FLAGS) {
+	if (super->flags & ~CRAMFS_SUPPORTED_FLAGS) {
 		pr_err("unsupported filesystem features\n");
 		return -EINVAL;
 	}
 
 	/* Check that the root inode is in a sane state */
-	if (!S_ISDIR(super.root.mode)) {
+	if (!S_ISDIR(super->root.mode)) {
 		pr_err("root is not a directory\n");
 		return -EINVAL;
 	}
 	/* correct strange, hard-coded permissions of mkcramfs */
-	super.root.mode |= (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+	super->root.mode |= 0555;
 
-	root_offset = super.root.offset << 2;
-	if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) {
-		sbi->size = super.size;
-		sbi->blocks = super.fsid.blocks;
-		sbi->files = super.fsid.files;
+	root_offset = super->root.offset << 2;
+	if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) {
+		sbi->size = super->size;
+		sbi->blocks = super->fsid.blocks;
+		sbi->files = super->fsid.files;
 	} else {
 		sbi->size = 1<<28;
 		sbi->blocks = 0;
 		sbi->files = 0;
 	}
-	sbi->magic = super.magic;
-	sbi->flags = super.flags;
+	sbi->magic = super->magic;
+	sbi->flags = super->flags;
 	if (root_offset == 0)
 		pr_info("empty filesystem");
-	else if (!(super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) &&
+	else if (!(super->flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) &&
 		 ((root_offset != sizeof(struct cramfs_super)) &&
 		  (root_offset != 512 + sizeof(struct cramfs_super))))
 	{
@@ -336,9 +377,18 @@  static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 		return -EINVAL;
 	}
 
+	return 0;
+}
+
+static int cramfs_finalize_super(struct super_block *sb,
+				 struct cramfs_inode *cramfs_root)
+{
+	struct inode *root;
+
 	/* Set it all up.. */
+	sb->s_flags |= MS_RDONLY;
 	sb->s_op = &cramfs_ops;
-	root = get_cramfs_inode(sb, &super.root, 0);
+	root = get_cramfs_inode(sb, cramfs_root, 0);
 	if (IS_ERR(root))
 		return PTR_ERR(root);
 	sb->s_root = d_make_root(root);
@@ -347,10 +397,79 @@  static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 	return 0;
 }
 
+static int cramfs_blkdev_fill_super(struct super_block *sb, void *data,
+				    int silent)
+{
+	struct cramfs_sb_info *sbi;
+	struct cramfs_super super;
+	int i, err;
+
+	sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+	sb->s_fs_info = sbi;
+
+	/* Invalidate the read buffers on mount: think disk change.. */
+	for (i = 0; i < READ_BUFFERS; i++)
+		buffer_blocknr[i] = -1;
+
+	err = cramfs_read_super(sb, &super, silent);
+	if (err)
+		return err;
+	return cramfs_finalize_super(sb, &super.root);
+}
+
+static int cramfs_mtd_fill_super(struct super_block *sb, void *data,
+				 int silent)
+{
+	struct cramfs_sb_info *sbi;
+	struct cramfs_super super;
+	int err;
+
+	sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+	sb->s_fs_info = sbi;
+
+	/* Map only one page for now.  Will remap it when fs size is known. */
+	err = mtd_point(sb->s_mtd, 0, PAGE_SIZE, &sbi->mtd_point_size,
+			&sbi->linear_virt_addr, &sbi->linear_phys_addr);
+	if (err || sbi->mtd_point_size != PAGE_SIZE) {
+		pr_err("unable to get direct memory access to mtd:%s\n",
+		       sb->s_mtd->name);
+		return err ? : -ENODATA;
+	}
+
+	pr_info("checking physical address %pap for linear cramfs image\n",
+		&sbi->linear_phys_addr);
+	err = cramfs_read_super(sb, &super, silent);
+	if (err)
+		return err;
+
+	/* Remap the whole filesystem now */
+	pr_info("linear cramfs image on mtd:%s appears to be %lu KB in size\n",
+		sb->s_mtd->name, sbi->size/1024);
+	mtd_unpoint(sb->s_mtd, 0, PAGE_SIZE);
+	err = mtd_point(sb->s_mtd, 0, sbi->size, &sbi->mtd_point_size,
+			&sbi->linear_virt_addr, &sbi->linear_phys_addr);
+	if (err || sbi->mtd_point_size != sbi->size) {
+		pr_err("unable to get direct memory access to mtd:%s\n",
+		       sb->s_mtd->name);
+		return err ? : -ENODATA;
+	}
+
+	return cramfs_finalize_super(sb, &super.root);
+}
+
 static int cramfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
 	struct super_block *sb = dentry->d_sb;
-	u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
+	u64 id = 0;
+
+	if (sb->s_bdev)
+		id = huge_encode_dev(sb->s_bdev->bd_dev);
+	else if (sb->s_dev)
+		id = huge_encode_dev(sb->s_dev);
 
 	buf->f_type = CRAMFS_MAGIC;
 	buf->f_bsize = PAGE_SIZE;
@@ -573,10 +692,22 @@  static const struct super_operations cramfs_ops = {
 	.statfs		= cramfs_statfs,
 };
 
-static struct dentry *cramfs_mount(struct file_system_type *fs_type,
-	int flags, const char *dev_name, void *data)
+static struct dentry *cramfs_mount(struct file_system_type *fs_type, int flags,
+				   const char *dev_name, void *data)
 {
-	return mount_bdev(fs_type, flags, dev_name, data, cramfs_fill_super);
+	struct dentry *ret = ERR_PTR(-ENOPROTOOPT);
+
+	if (IS_ENABLED(CONFIG_CRAMFS_MTD)) {
+		ret = mount_mtd(fs_type, flags, dev_name, data,
+				cramfs_mtd_fill_super);
+		if (!IS_ERR(ret))
+			return ret;
+	}
+	if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV)) {
+		ret = mount_bdev(fs_type, flags, dev_name, data,
+				 cramfs_blkdev_fill_super);
+	}
+	return ret;
 }
 
 static struct file_system_type cramfs_fs_type = {