From patchwork Fri Oct 6 02:45:27 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Pitre X-Patchwork-Id: 115017 Delivered-To: patch@linaro.org Received: by 10.140.22.163 with SMTP id 32csp1270214qgn; Thu, 5 Oct 2017 19:45:48 -0700 (PDT) X-Google-Smtp-Source: AOwi7QBZh0Zu+v7RNbHIV5RpZQ/KquXXKcQ3Uq0dfUMjMJs1TtbYLzHEP45RriisNis61PyDHWd9 X-Received: by 10.84.132.66 with SMTP id 60mr624535ple.158.1507257948669; Thu, 05 Oct 2017 19:45:48 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1507257948; cv=none; d=google.com; s=arc-20160816; b=Ro/p8rrjtE+RgyE9bJJ0zlbasvY4ELGZOoeOBi7JjA8a/9pZGke+MWR6RvQ0mIq6rp rZEl2gc7X5G1YH/B4JRYByTqranqmqm0YxvhOvpbLtGiWPqVoVB7r0GIbmWzoGn7k0Ay 9LuT6ovWWpnEiO8JKINvMqGq38Lt6rw40yOEIYq+047r3Csv9HekkzbkKHhlx6w94fOL 8a8hyovzEusU3dP0aZa9+J40xLf+/vWe/1eNdj7oya9Yd1niEDOlWLJ2z6thseahMrQO 7dBVj1UnnQqtqHkcfSntq5YDrsXGJcqVr7b6ewVtJrw64Uf37WuU6ok/w930LrealAd9 3NqQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=wq9imYYv7HEa3iggrjrETTEpEdLRmrjQxd3WJYv5Kzs=; b=kfDuqJ7euPpNs0fi7VGHrBuw8VVAlWL5bRE8R1Ha+z7eZhy73cyypHXSacARDe6YWI 7HPPGaoQpEPs49d7Y7PxWnzq0z8P6/PHjoDGmVv8m4ZzJkGQ515HPFleMh8+Pv1ONkD7 dNlwwffXinKwQwaF6kAnU0RarfUq8y8CgeMcTKtaohsSo4MpS/TRAA5CfGL6wF36ubCX qHI/7rMzMS/ZoaqzCqlorwS6cEN23Fmma2thvVjxJCHxeI/pArWDkq3kRfTwA/L2fkDR +TuEX4+tBxinU2FCNOJJ/SS0quubs7j/n3V4Z5UVMINEuxOhUgdYRt85WZmoIkK4wm79 P1pg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@pobox.com header.s=sasl header.b=W0ZLKbcK; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id r17si321827pgu.619.2017.10.05.19.45.48; Thu, 05 Oct 2017 19:45:48 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@pobox.com header.s=sasl header.b=W0ZLKbcK; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751875AbdJFCpq (ORCPT + 26 others); Thu, 5 Oct 2017 22:45:46 -0400 Received: from pb-smtp1.pobox.com ([64.147.108.70]:64872 "EHLO sasl.smtp.pobox.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751459AbdJFCpj (ORCPT ); Thu, 5 Oct 2017 22:45:39 -0400 Received: from sasl.smtp.pobox.com (unknown [127.0.0.1]) by pb-smtp1.pobox.com (Postfix) with ESMTP id A555499B0F; Thu, 5 Oct 2017 22:45:38 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=pobox.com; h=from:to:cc :subject:date:message-id:in-reply-to:references; s=sasl; bh=7smc Tj/fOQHhcJ/AnguMvYxs3l0=; b=W0ZLKbcKkcyM3zJCFpeVWqAjlZ15WWJEzxi9 hYkjUTbYESmrV95CvA8+cdqzau86gNbRgJ56V2Elb13ZQUU1AV6eqx0ehWc6bnin d5SLEFH+hrcsJvMUj/DOZWj/yP2DYzHs5oxq8Cufes5LUJFwiL8W4L+CXErXXTp9 ceW0WNU= Received: from pb-smtp1.nyi.icgroup.com (unknown [127.0.0.1]) by pb-smtp1.pobox.com (Postfix) with ESMTP id 9BB5299B0E; Thu, 5 Oct 2017 22:45:38 -0400 (EDT) Received: from yoda.home (unknown [137.175.234.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by pb-smtp1.pobox.com (Postfix) with ESMTPSA id 19AAB99B09; Thu, 5 Oct 2017 22:45:38 -0400 (EDT) Received: from xanadu.home (xanadu.home [192.168.2.2]) by yoda.home (Postfix) with ESMTP id 72D472DA05B4; Thu, 5 Oct 2017 22:45:37 -0400 (EDT) From: Nicolas Pitre To: Alexander Viro , Christoph Hellwig Cc: linux-fsdevel@vger.kernel.org, linux-mm@kvack.org, linux-embedded@vger.kernel.org, linux-kernel@vger.kernel.org, Chris Brandt Subject: [PATCH v5 1/5] cramfs: direct memory access support Date: Thu, 5 Oct 2017 22:45:27 -0400 Message-Id: <20171006024531.8885-2-nicolas.pitre@linaro.org> X-Mailer: git-send-email 2.9.5 In-Reply-To: <20171006024531.8885-1-nicolas.pitre@linaro.org> References: <20171006024531.8885-1-nicolas.pitre@linaro.org> X-Pobox-Relay-ID: 6F5EB990-AA40-11E7-977A-8EF31968708C-78420484!pb-smtp1.pobox.com Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 memory address at run time when both methods are configured in. In the context of small IoT deployments, this functionality has become relevant and useful again. To distinguish between both access types, the cramfs_physmem filesystem type must be specified when using a memory accessible cramfs image, and the physaddr argument must provide the actual filesystem image's physical memory location. Signed-off-by: Nicolas Pitre Tested-by: Chris Brandt --- fs/cramfs/Kconfig | 29 +++++- fs/cramfs/inode.c | 270 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 247 insertions(+), 52 deletions(-) -- 2.9.5 diff --git a/fs/cramfs/Kconfig b/fs/cramfs/Kconfig index 11b29d491b..5b4e0b7e13 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,31 @@ 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_PHYSMEM + bool "Support CramFs image directly mapped in physical memory" + depends on CRAMFS + 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 filesystem type for this feature is "cramfs_physmem". + The location of the CramFs image in memory is board + dependent. Therefore, if you say Y, you must know the proper + physical address where to store the CramFs image and specify + it using the physaddr=0x******** mount option (for example: + "mount -t cramfs_physmem -o physaddr=0x100000 none /mnt"). + + If unsure, say N. diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c index 7919967488..89bce89c05 100644 --- a/fs/cramfs/inode.c +++ b/fs/cramfs/inode.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "internal.h" @@ -36,6 +37,8 @@ struct cramfs_sb_info { unsigned long blocks; unsigned long files; unsigned long flags; + void *linear_virt_addr; + phys_addr_t linear_phys_addr; }; static inline struct cramfs_sb_info *CRAMFS_SB(struct super_block *sb) @@ -140,6 +143,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 +166,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,7 +245,39 @@ static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned i return read_buffers[buffer] + offset; } -static void cramfs_kill_sb(struct super_block *sb) +/* + * 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_PHYSMEM) && 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_blkdev_kill_sb(struct super_block *sb) { struct cramfs_sb_info *sbi = CRAMFS_SB(sb); @@ -247,6 +285,16 @@ static void cramfs_kill_sb(struct super_block *sb) kfree(sbi); } +static void cramfs_physmem_kill_sb(struct super_block *sb) +{ + struct cramfs_sb_info *sbi = CRAMFS_SB(sb); + + if (sbi->linear_virt_addr) + memunmap(sbi->linear_virt_addr); + kill_anon_super(sb); + kfree(sbi); +} + static int cramfs_remount(struct super_block *sb, int *flags, char *data) { sync_filesystem(sb); @@ -254,34 +302,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 +327,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 +341,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 +376,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,6 +396,95 @@ 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_physmem_fill_super(struct super_block *sb, void *data, + int silent) +{ + struct cramfs_sb_info *sbi; + struct cramfs_super super; + char *p; + int err; + + sbi = kzalloc(sizeof(struct cramfs_sb_info), GFP_KERNEL); + if (!sbi) + return -ENOMEM; + sb->s_fs_info = sbi; + + /* + * The physical location of the cramfs image is specified as + * a mount parameter. This parameter is mandatory for obvious + * reasons. Some validation is made on the phys address but this + * is not exhaustive and we count on the fact that someone using + * this feature is supposed to know what he/she's doing. + */ + if (!data || !(p = strstr((char *)data, "physaddr="))) { + pr_err("unknown physical address for linear cramfs image\n"); + return -EINVAL; + } + sbi->linear_phys_addr = memparse(p + 9, NULL); + if (!sbi->linear_phys_addr) { + pr_err("bad value for cramfs image physical address\n"); + return -EINVAL; + } + if (!PAGE_ALIGNED(sbi->linear_phys_addr)) { + pr_err("physical address %pap isn't page aligned\n", + &sbi->linear_phys_addr); + return -EINVAL; + } + + /* + * Map only one page for now. Will remap it when fs size is known. + * Although we'll only read from it, we want the CPU cache to + * kick in for the higher throughput it provides, hence MEMREMAP_WB. + */ + pr_info("checking physical address %pap for linear cramfs image\n", + &sbi->linear_phys_addr); + sbi->linear_virt_addr = memremap(sbi->linear_phys_addr, PAGE_SIZE, + MEMREMAP_WB); + if (!sbi->linear_virt_addr) { + pr_err("memremap of the linear cramfs image failed\n"); + return -ENOMEM; + } + + err = cramfs_read_super(sb, &super, silent); + if (err) + return err; + + /* Remap the whole filesystem now */ + pr_info("linear cramfs image appears to be %lu KB in size\n", + sbi->size/1024); + memunmap(sbi->linear_virt_addr); + sbi->linear_virt_addr = memremap(sbi->linear_phys_addr, sbi->size, + MEMREMAP_WB); + if (!sbi->linear_virt_addr) { + pr_err("ioremap of the linear cramfs image failed\n"); + return -ENOMEM; + } + + return cramfs_finalize_super(sb, &super.root); +} + static int cramfs_statfs(struct dentry *dentry, struct kstatfs *buf) { struct super_block *sb = dentry->d_sb; @@ -573,38 +711,68 @@ 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_blkdev_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_blkdev_fill_super); +} + +static struct dentry *cramfs_physmem_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); + return mount_nodev(fs_type, flags, data, cramfs_physmem_fill_super); } static struct file_system_type cramfs_fs_type = { .owner = THIS_MODULE, .name = "cramfs", - .mount = cramfs_mount, - .kill_sb = cramfs_kill_sb, + .mount = cramfs_blkdev_mount, + .kill_sb = cramfs_blkdev_kill_sb, .fs_flags = FS_REQUIRES_DEV, }; + +static struct file_system_type cramfs_physmem_fs_type = { + .owner = THIS_MODULE, + .name = "cramfs_physmem", + .mount = cramfs_physmem_mount, + .kill_sb = cramfs_physmem_kill_sb, +}; + +#ifdef CONFIG_CRAMFS_BLOCKDEV MODULE_ALIAS_FS("cramfs"); +#endif +#ifdef CONFIG_CRAMFS_PHYSMEM +MODULE_ALIAS_FS("cramfs_physmem"); +#endif static int __init init_cramfs_fs(void) { int rv; - rv = cramfs_uncompress_init(); - if (rv < 0) - return rv; - rv = register_filesystem(&cramfs_fs_type); - if (rv < 0) - cramfs_uncompress_exit(); - return rv; + if ((rv = cramfs_uncompress_init()) < 0) + goto err0; + if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV) && + (rv = register_filesystem(&cramfs_fs_type)) < 0) + goto err1; + if (IS_ENABLED(CONFIG_CRAMFS_PHYSMEM) && + (rv = register_filesystem(&cramfs_physmem_fs_type)) < 0) + goto err2; + return 0; + +err2: if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV)) + unregister_filesystem(&cramfs_fs_type); +err1: cramfs_uncompress_exit(); +err0: return rv; } static void __exit exit_cramfs_fs(void) { cramfs_uncompress_exit(); - unregister_filesystem(&cramfs_fs_type); + if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV)) + unregister_filesystem(&cramfs_fs_type); + if (IS_ENABLED(CONFIG_CRAMFS_PHYSMEM)) + unregister_filesystem(&cramfs_physmem_fs_type); } module_init(init_cramfs_fs)