[PATCH 2/2] squashfs: support linear addressing

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux