Add SQUASHFS_LINEAR option to use linear addressing for SquashFS. Signed-off-by: UCHINO Satoshi <satoshi.uchino@xxxxxxxxxxxxx> CC: Atsushi Nemoto <nemoto@xxxxxxxxxxxxxxxxxx> --- fs/squashfs/Kconfig | 25 +++++++ fs/squashfs/block.c | 69 ++++++++++++++++++--- fs/squashfs/inode.c | 10 +++ fs/squashfs/lzo_wrapper.c | 10 +++- fs/squashfs/squashfs.h | 5 ++ fs/squashfs/squashfs_fs_sb.h | 2 + fs/squashfs/super.c | 144 +++++++++++++++++++++++++++++++++++++++--- fs/squashfs/xz_wrapper.c | 5 ++ fs/squashfs/zlib_wrapper.c | 5 ++ 9 files changed, 257 insertions(+), 18 deletions(-) diff --git a/fs/squashfs/Kconfig b/fs/squashfs/Kconfig index c70111e..f3a6f9c 100644 --- a/fs/squashfs/Kconfig +++ b/fs/squashfs/Kconfig @@ -121,3 +121,28 @@ config SQUASHFS_FRAGMENT_CACHE_SIZE Note there must be at least one cached fragment. Anything much more than three will probably not make much difference. + +config SQUASHFS_LINEAR + bool "Use linear addressing for SquashFS" + depends on SQUASHFS + help + This option tells the SquashFS 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 SquashFs image in memory is board + dependent. Therefore, if you say Y, you must know the proper + physical address where to store the SquashFS image and specify + it using the physaddr=0x******** mount option (for example: + "mount -t squashfs_linear -o physaddr=0x100000 none /mnt"). + + In addition, if you want to use the linear SquashFS image as + a root file system, you must also pass the command line + parameter "root=/dev/null", "rootfstype=squashfs_linear", + and "rootflags=physaddr=0x********" to the kernel + (replace 0x******** with the physical address location of + the linear SquashFs image to boot with). + + If unsure, say N. diff --git a/fs/squashfs/block.c b/fs/squashfs/block.c index 8127cce..15e5880 100644 --- a/fs/squashfs/block.c +++ b/fs/squashfs/block.c @@ -76,6 +76,27 @@ static struct buffer_head *get_block_length(struct super_block *sb, return bh; } +/* + * Return a pointer to the block in the linearly addressed squashfs image. + */ +static const void *squashfs_linear_read(struct super_block *sb, + unsigned int offset) +{ + struct squashfs_sb_info *msblk = sb->s_fs_info; + + return (__force const void *)msblk->linear_virt_addr + offset; +} + +static void get_block_length_linear(struct super_block *sb, + u64 *cur_index, int *offset, int *length) +{ + struct squashfs_sb_info *msblk = sb->s_fs_info; + const unsigned char *data = + squashfs_linear_read(sb, *cur_index * msblk->devblksize); + + *length = data[*offset] | data[*offset + 1] << 8; + *offset += 2; +} /* * Read and decompress a metadata block or datablock. Length is non-zero @@ -96,10 +117,15 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index, int bytes, compressed, b = 0, k = 0, page = 0, avail; const char *c_buffer = NULL; - bh = kcalloc(((srclength + msblk->devblksize - 1) - >> msblk->devblksize_log2) + 1, sizeof(*bh), GFP_KERNEL); - if (bh == NULL) - return -ENOMEM; + if (LINEAR(msblk)) + bh = NULL; + else { + bh = kcalloc(((srclength + msblk->devblksize - 1) + >> msblk->devblksize_log2) + 1, sizeof(*bh), + GFP_KERNEL); + if (bh == NULL) + return -ENOMEM; + } if (length) { /* @@ -118,6 +144,10 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index, (index + length) > msblk->bytes_used) goto read_failure; + if (LINEAR(msblk)) { + c_buffer = squashfs_linear_read(sb, index); + goto read_done; + } for (b = 0; bytes < length; b++, cur_index++) { bh[b] = sb_getblk(sb, cur_index); if (bh[b] == NULL) @@ -132,10 +162,16 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index, if ((index + 2) > msblk->bytes_used) goto read_failure; - bh[0] = get_block_length(sb, &cur_index, &offset, &length); - if (bh[0] == NULL) - goto read_failure; - b = 1; + if (LINEAR(msblk)) { + get_block_length_linear(sb, &cur_index, &offset, + &length); + } else { + bh[0] = get_block_length(sb, &cur_index, &offset, + &length); + if (bh[0] == NULL) + goto read_failure; + b = 1; + } bytes = msblk->devblksize - offset; compressed = SQUASHFS_COMPRESSED(length); @@ -150,6 +186,11 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index, (index + length) > msblk->bytes_used) goto block_release; + if (LINEAR(msblk)) { + c_buffer = squashfs_linear_read(sb, + cur_index * msblk->devblksize + offset); + goto read_done; + } for (; bytes < length; b++) { bh[b] = sb_getblk(sb, ++cur_index); if (bh[b] == NULL) @@ -158,6 +199,7 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index, } ll_rw_block(READ, b - 1, bh + 1); } +read_done: if (compressed) { length = squashfs_decompress(msblk, buffer, bh, b, offset, @@ -170,6 +212,15 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index, */ int i, in, pg_offset = 0; + if (LINEAR(msblk)) { + for (bytes = length; bytes; page++) { + avail = min_t(int, bytes, PAGE_CACHE_SIZE); + memcpy(buffer[page], c_buffer, avail); + bytes -= avail; + c_buffer += avail; + } + goto uncompress_done; + } for (i = 0; i < b; i++) { wait_on_buffer(bh[i]); if (!buffer_uptodate(bh[i])) @@ -196,11 +247,13 @@ int squashfs_read_data(struct super_block *sb, void **buffer, u64 index, put_bh(bh[k]); } } +uncompress_done: kfree(bh); return length; block_release: + BUG_ON(LINEAR(msblk) && b != 0); for (; k < b; k++) put_bh(bh[k]); diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c index 81afbcc..ff77612 100644 --- a/fs/squashfs/inode.c +++ b/fs/squashfs/inode.c @@ -41,6 +41,7 @@ #include <linux/fs.h> #include <linux/vfs.h> #include <linux/xattr.h> +#include <linux/backing-dev.h> #include "squashfs_fs.h" #include "squashfs_fs_sb.h" @@ -48,6 +49,10 @@ #include "squashfs.h" #include "xattr.h" +static struct backing_dev_info squashfs_backing_dev_info = { + .ra_pages = 0, /* No readahead */ +}; + /* * Initialise VFS inode with the base inode information common to all * Squashfs inode types. Sqsh_ino contains the unswapped base inode @@ -56,6 +61,9 @@ static int squashfs_new_inode(struct super_block *sb, struct inode *inode, struct squashfs_base_inode *sqsh_ino) { +#ifdef CONFIG_SQUASHFS_LINEAR + struct squashfs_sb_info *msblk = sb->s_fs_info; +#endif int err; err = squashfs_get_id(sb, le16_to_cpu(sqsh_ino->uid), &inode->i_uid); @@ -72,6 +80,8 @@ static int squashfs_new_inode(struct super_block *sb, struct inode *inode, inode->i_ctime.tv_sec = inode->i_mtime.tv_sec; inode->i_mode = le16_to_cpu(sqsh_ino->mode); inode->i_size = 0; + if (LINEAR(msblk)) + inode->i_mapping->backing_dev_info = &squashfs_backing_dev_info; return err; } diff --git a/fs/squashfs/lzo_wrapper.c b/fs/squashfs/lzo_wrapper.c index 0be87de..8b9b3b2 100644 --- a/fs/squashfs/lzo_wrapper.c +++ b/fs/squashfs/lzo_wrapper.c @@ -85,6 +85,8 @@ static int lzo_uncompress(struct squashfs_sb_info *msblk, void **buffer, mutex_lock(&msblk->read_data_mutex); + if (LINEAR(msblk)) + BUG_ON(b != 0); for (i = 0; i < b; i++) { wait_on_buffer(bh[i]); if (!buffer_uptodate(bh[i])) @@ -98,8 +100,12 @@ static int lzo_uncompress(struct squashfs_sb_info *msblk, void **buffer, put_bh(bh[i]); } - res = lzo1x_decompress_safe(stream->input, (size_t)length, - stream->output, &out_len); + if (LINEAR(msblk)) + res = lzo1x_decompress_safe(c_buffer, (size_t)length, + stream->output, &out_len); + else + res = lzo1x_decompress_safe(stream->input, (size_t)length, + stream->output, &out_len); if (res != LZO_E_OK) goto failed; diff --git a/fs/squashfs/squashfs.h b/fs/squashfs/squashfs.h index d126651..33a1533 100644 --- a/fs/squashfs/squashfs.h +++ b/fs/squashfs/squashfs.h @@ -30,6 +30,11 @@ /* block.c */ extern int squashfs_read_data(struct super_block *, void **, u64, int, u64 *, int, int); +#ifdef CONFIG_SQUASHFS_LINEAR +#define LINEAR(x) ((x)->linear_phys_addr != 0) +#else +#define LINEAR(x) 0 +#endif /* cache.c */ extern struct squashfs_cache *squashfs_cache_init(char *, int, int); diff --git a/fs/squashfs/squashfs_fs_sb.h b/fs/squashfs/squashfs_fs_sb.h index 52934a2..8cb9e41 100644 --- a/fs/squashfs/squashfs_fs_sb.h +++ b/fs/squashfs/squashfs_fs_sb.h @@ -76,5 +76,7 @@ struct squashfs_sb_info { long long bytes_used; unsigned int inodes; int xattr_ids; + unsigned long linear_phys_addr; + void __iomem *linear_virt_addr; }; #endif diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c index 29cd014..ca9b02b 100644 --- a/fs/squashfs/super.c +++ b/fs/squashfs/super.c @@ -36,6 +36,7 @@ #include <linux/module.h> #include <linux/magic.h> #include <linux/xattr.h> +#include <linux/io.h> #include "squashfs_fs.h" #include "squashfs_fs_sb.h" @@ -45,6 +46,9 @@ #include "xattr.h" static struct file_system_type squashfs_fs_type; +#ifdef CONFIG_SQUASHFS_LINEAR +static struct file_system_type squashfs_linear_fs_type; +#endif static const struct super_operations squashfs_super_ops; static const struct squashfs_decompressor *supported_squashfs_filesystem(short @@ -85,6 +89,11 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent) unsigned int fragments; u64 lookup_table_start, xattr_id_table_start, next_table; int err; +#ifdef CONFIG_SQUASHFS_LINEAR + char *p; + char *end; + char org_end; +#endif TRACE("Entered squashfs_fill_superblock\n"); @@ -95,7 +104,58 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent) } msblk = sb->s_fs_info; - msblk->devblksize = sb_min_blocksize(sb, SQUASHFS_DEVBLK_SIZE); +#ifdef CONFIG_SQUASHFS_LINEAR + /* + * The physical location of the squashfs 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. + */ + msblk->linear_phys_addr = 0; + p = strstr(data ?: "", "physaddr="); + err = -EINVAL; + if (!p) { + if (!sb->s_bdev) /* avoid crash */ + goto failed_mount; + goto read; + } + end = strchr(p + 9, ',') ?: p + strlen(p); + org_end = *end; + *end = '\0'; + err = kstrtoul(p + 9, 0, &msblk->linear_phys_addr); + *end = org_end; + if (err) { + ERROR("physical address for linear squashfs is invalid\n"); + goto failed_mount; + } + if (msblk->linear_phys_addr & (PAGE_SIZE - 1)) { + ERROR("physical address 0x%lx for" + " linear squashfs isn't aligned to a page boundary\n", + msblk->linear_phys_addr); + goto failed_mount; + } + if (msblk->linear_phys_addr == 0) { + ERROR("physical address for" + " linear squashfs image can't be 0\n"); + goto failed_mount; + } + TRACE("checking physical address 0x%lx for linear squashfs image\n", + msblk->linear_phys_addr); + + /* Map only one page for now. Will remap it when fs size is known. */ + msblk->linear_virt_addr = + ioremap(msblk->linear_phys_addr, PAGE_SIZE); + if (!msblk->linear_virt_addr) { + ERROR("ioremap of the linear squashfs image failed\n"); + goto failed_mount; + } +read: +#endif + if (LINEAR(msblk)) + msblk->devblksize = SQUASHFS_DEVBLK_SIZE; + else + msblk->devblksize = sb_min_blocksize(sb, SQUASHFS_DEVBLK_SIZE); msblk->devblksize_log2 = ffz(~msblk->devblksize); mutex_init(&msblk->read_data_mutex); @@ -122,9 +182,14 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent) /* Check it is a SQUASHFS superblock */ sb->s_magic = le32_to_cpu(sblk->s_magic); if (sb->s_magic != SQUASHFS_MAGIC) { - if (!silent) - ERROR("Can't find a SQUASHFS superblock on %s\n", - bdevname(sb->s_bdev, b)); + if (!silent) { + if (LINEAR(msblk)) + ERROR("Can't find a SQUASHFS superblock" + " on 0x%lx\n", msblk->linear_phys_addr); + else + ERROR("Can't find a SQUASHFS superblock" + " on %s\n", bdevname(sb->s_bdev, b)); + } goto failed_mount; } @@ -139,9 +204,11 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent) /* Check the filesystem does not extend beyond the end of the block device */ msblk->bytes_used = le64_to_cpu(sblk->bytes_used); - if (msblk->bytes_used < 0 || msblk->bytes_used > - i_size_read(sb->s_bdev->bd_inode)) + if (msblk->bytes_used < 0) goto failed_mount; + if (!LINEAR(msblk) && + msblk->bytes_used > i_size_read(sb->s_bdev->bd_inode)) + goto failed_mount; /* Check block size for sanity */ msblk->block_size = le32_to_cpu(sblk->block_size); @@ -177,7 +244,12 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent) msblk->inodes = le32_to_cpu(sblk->inodes); flags = le16_to_cpu(sblk->flags); - TRACE("Found valid superblock on %s\n", bdevname(sb->s_bdev, b)); + if (LINEAR(msblk)) + TRACE("Found valid superblock on 0x%lx\n", + msblk->linear_phys_addr); + else + TRACE("Found valid superblock on %s\n", + bdevname(sb->s_bdev, b)); TRACE("Inodes are %scompressed\n", SQUASHFS_UNCOMPRESSED_INODES(flags) ? "un" : ""); TRACE("Data is %scompressed\n", SQUASHFS_UNCOMPRESSED_DATA(flags) @@ -205,6 +277,28 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent) if (msblk->block_cache == NULL) goto failed_mount; + if (LINEAR(msblk)) { + int size = ALIGN(sblk->bytes_used, PAGE_CACHE_SIZE); + /* Remap the whole filesystem now */ + iounmap(msblk->linear_virt_addr); + TRACE("linear squashfs image appears to be %u KB in size\n", + size >> 10); +#if defined(CONFIG_MIPS) + msblk->linear_virt_addr = + ioremap_cachable(msblk->linear_phys_addr, size); +#elif defined(CONFIG_ARM) + msblk->linear_virt_addr = + ioremap_cached(msblk->linear_phys_addr, size); +#else + msblk->linear_virt_addr = + ioremap(msblk->linear_phys_addr, size); +#endif + if (!msblk->linear_virt_addr) { + ERROR("ioremap of the linear squashfs image failed\n"); + goto failed_mount; + } + } + /* Allocate read_page block */ msblk->read_page = squashfs_cache_init("data", 1, msblk->block_size); if (msblk->read_page == NULL) { @@ -341,6 +435,8 @@ failed_mount: kfree(msblk->fragment_index); kfree(msblk->id_table); kfree(msblk->xattr_id_table); + if (LINEAR(msblk) && msblk->linear_virt_addr) + iounmap(msblk->linear_virt_addr); kfree(sb->s_fs_info); sb->s_fs_info = NULL; kfree(sblk); @@ -351,7 +447,8 @@ failed_mount: static int squashfs_statfs(struct dentry *dentry, struct kstatfs *buf) { struct squashfs_sb_info *msblk = dentry->d_sb->s_fs_info; - u64 id = huge_encode_dev(dentry->d_sb->s_bdev->bd_dev); + u64 id = LINEAR(msblk) ? msblk->linear_phys_addr : + huge_encode_dev(dentry->d_sb->s_bdev->bd_dev); TRACE("Entered squashfs_statfs\n"); @@ -389,6 +486,8 @@ static void squashfs_put_super(struct super_block *sb) kfree(sbi->meta_index); kfree(sbi->inode_lookup_table); kfree(sbi->xattr_id_table); + if (LINEAR(sbi) && sbi->linear_virt_addr) + iounmap(sbi->linear_virt_addr); kfree(sb->s_fs_info); sb->s_fs_info = NULL; } @@ -401,6 +500,13 @@ static struct dentry *squashfs_mount(struct file_system_type *fs_type, return mount_bdev(fs_type, flags, dev_name, data, squashfs_fill_super); } +#ifdef CONFIG_SQUASHFS_LINEAR +static struct dentry *squashfs_linear_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_nodev(fs_type, flags, data, squashfs_fill_super); +} +#endif static struct kmem_cache *squashfs_inode_cachep; @@ -436,8 +542,18 @@ static int __init init_squashfs_fs(void) if (err) return err; +#ifdef CONFIG_SQUASHFS_LINEAR + err = register_filesystem(&squashfs_linear_fs_type); + if (err) { + destroy_inodecache(); + return err; + } +#endif err = register_filesystem(&squashfs_fs_type); if (err) { +#ifdef CONFIG_SQUASHFS_LINEAR + unregister_filesystem(&squashfs_linear_fs_type); +#endif destroy_inodecache(); return err; } @@ -451,6 +567,9 @@ static int __init init_squashfs_fs(void) static void __exit exit_squashfs_fs(void) { +#ifdef CONFIG_SQUASHFS_LINEAR + unregister_filesystem(&squashfs_linear_fs_type); +#endif unregister_filesystem(&squashfs_fs_type); destroy_inodecache(); } @@ -485,6 +604,15 @@ static struct file_system_type squashfs_fs_type = { .fs_flags = FS_REQUIRES_DEV }; +#ifdef CONFIG_SQUASHFS_LINEAR +static struct file_system_type squashfs_linear_fs_type = { + .owner = THIS_MODULE, + .name = "squashfs_linear", + .mount = squashfs_linear_mount, + .kill_sb = kill_anon_super, +}; +#endif + static const struct super_operations squashfs_super_ops = { .alloc_inode = squashfs_alloc_inode, .destroy_inode = squashfs_destroy_inode, diff --git a/fs/squashfs/xz_wrapper.c b/fs/squashfs/xz_wrapper.c index 4140040..d0fbedb 100644 --- a/fs/squashfs/xz_wrapper.c +++ b/fs/squashfs/xz_wrapper.c @@ -119,6 +119,11 @@ static int squashfs_xz_uncompress(struct squashfs_sb_info *msblk, void **buffer, stream->buf.out_pos = 0; stream->buf.out_size = PAGE_CACHE_SIZE; stream->buf.out = buffer[page++]; + if (LINEAR(msblk)) { + BUG_ON(b != 0); + stream->buf.in = c_buffer; + stream->buf.in_size = length; + } do { if (stream->buf.in_pos == stream->buf.in_size && k < b) { diff --git a/fs/squashfs/zlib_wrapper.c b/fs/squashfs/zlib_wrapper.c index 01cdcc6..29533c9 100644 --- a/fs/squashfs/zlib_wrapper.c +++ b/fs/squashfs/zlib_wrapper.c @@ -73,6 +73,11 @@ static int zlib_uncompress(struct squashfs_sb_info *msblk, void **buffer, stream->avail_out = 0; stream->avail_in = 0; + if (LINEAR(msblk)) { + BUG_ON(b != 0); + stream->next_in = c_buffer; + stream->avail_in = length; + } do { if (stream->avail_in == 0 && k < b) { -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html