Bugzilla 776 - FS-001 XIP from cramfs - Community patch
authorQuinn Jensen <quinn.jensen@freescale.com>
Thu, 25 Oct 2007 03:24:48 +0000 (21:24 -0600)
committerQuinn Jensen <quinn.jensen@freescale.com>
Thu, 25 Oct 2007 03:24:48 +0000 (21:24 -0600)
Patch for Bugzilla 776 - FS-001 XIP from cramfs
Origin: http://lwn.net/Articles/235532/
Description: Venerable cramfs fs Linear XIP patch originally from
MontaVista, used in the embedded Linux community for years, updated for
2.6.21. Tested on several systems with NOR Flash. PXA270, TI OMAP2430,
ARM Versatile and Freescale iMX31ADS.
Portions Copyright Shane Nay and Robert Leslie <rob@mars.org>

http://www.bitshrine.org/gpp/linux-2.6.22-mx-Bugzilla-776-FS-001-XIP-from-cramfs-Commun.patch

fs/Kconfig
fs/cramfs/inode.c
include/linux/cramfs_fs_sb.h
include/linux/mm.h
init/do_mounts.c
mm/memory.c

index c8f494364884cb68c8f7d1dd2c07007f4d403e02..e786b380e5aaecb2072c14d5e9b7ade46eb09727 100644 (file)
@@ -1371,6 +1371,51 @@ config CRAMFS
 
          If unsure, say N.
 
+config CRAMFS_LINEAR
+       bool "Use linear addressing for CramFs"
+       depends on CRAMFS
+       help
+         This option tells 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.
+
+         This is also a prerequisite for XIP of binaries stored on the
+         filesystem.
+
+         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 -o physaddr=0x100000 none /mnt").
+
+         If unsure, say N.
+
+config CRAMFS_LINEAR_XIP
+       bool "Support XIP on linear CramFs"
+       depends on CRAMFS_LINEAR
+       help
+         You must say Y to this option if you want to be able to run
+         applications directly from non-volatile memory.  XIP
+         applications are marked by setting the sticky bit (ie, "chmod
+         +t <app name>").  A cramfs file system then needs to be
+         created using mkcramfs (with XIP cramfs support in
+         it). Applications marked for XIP execution will not be
+         compressed since they have to run directly from flash.
+
+config ROOT_CRAMFS_LINEAR
+       bool "Root file system on linear CramFs"
+       depends on CRAMFS_LINEAR
+       help
+         Say Y if you have enabled linear CramFs, and you want to be
+         able to use the linear CramFs image as a root file system.  To
+         actually have the kernel mount this CramFs image as a root
+         file system, you must also pass the command line parameter
+         "root=/dev/null rootflags=physaddr=0x********" to the kernel
+         (replace 0x******** with the physical address location of the
+         linear CramFs image to boot with).
+
 config VXFS_FS
        tristate "FreeVxFS file system support (VERITAS VxFS(TM) compatible)"
        depends on BLOCK
index 3d194a2be3f511e869a541d9b782d6cbe5dddaec..f12a7e2b97fab5eb33b265e6c830c4641e994fdb 100644 (file)
  * The actual compression is based on zlib, see the other files.
  */
 
+/* Linear Addressing code
+ *
+ * Copyright (C) 2000 Shane Nay.
+ *
+ * Allows you to have a linearly addressed cramfs filesystem.
+ * Saves the need for buffer, and the munging of the buffer.
+ * Savings a bit over 32k with default PAGE_SIZE, BUFFER_SIZE
+ * etc.  Usefull on embedded platform with ROM :-).
+ *
+ * Downsides- Currently linear addressed cramfs partitions
+ * don't co-exist with block cramfs partitions.
+ *
+ */
+
+/*
+ * 28-Dec-2000: XIP mode for linear cramfs
+ * Copyright (C) 2000 Robert Leslie <rob@mars.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
 #include <linux/module.h>
 #include <linux/fs.h>
 #include <linux/pagemap.h>
 #include <asm/semaphore.h>
 
 #include <asm/uaccess.h>
+#include <asm/tlbflush.h>
 
-static const struct super_operations cramfs_ops;
-static const struct inode_operations cramfs_dir_inode_operations;
+static struct super_operations cramfs_ops;
+static struct inode_operations cramfs_dir_inode_operations;
 static const struct file_operations cramfs_directory_operations;
 static const struct address_space_operations cramfs_aops;
 
@@ -99,6 +133,67 @@ static int cramfs_iget5_set(struct inode *inode, void *opaque)
        return 0;
 }
 
+ #ifdef CONFIG_CRAMFS_LINEAR_XIP
+ static int cramfs_mmap(struct file *file, struct vm_area_struct *vma)
+ {
+       unsigned long address, length;
+       struct inode *inode = file->f_dentry->d_inode;
+       struct super_block *sb = inode->i_sb;
+       struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
+       /* this is only used in the case of read-only maps for XIP */
+       if (vma->vm_flags & VM_WRITE)
+               return generic_file_mmap(file, vma);
+       if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE))
+               return -EINVAL;
+       address  = PAGE_ALIGN(sbi->linear_phys_addr + OFFSET(inode));
+       address += vma->vm_pgoff << PAGE_SHIFT;
+       length = vma->vm_end - vma->vm_start;
+       if (length > inode->i_size)
+               length = inode->i_size;
+       length = PAGE_ALIGN(length);
+       /*
+        * Don't dump addresses that are not real memory to a core file.
+        */
+       vma->vm_flags |= (VM_IO | VM_XIP);
+       flush_tlb_page(vma, address);
+       if (remap_pfn_range(vma, vma->vm_start, address >> PAGE_SHIFT, length,
+                            vma->vm_page_prot))
+               return -EAGAIN;
+ #ifdef DEBUG_CRAMFS_XIP
+       printk("cramfs_mmap: mapped %s at 0x%08lx, length %lu to vma 0x%08lx"
+               ", page_prot 0x%08lx\n",
+               file->f_dentry->d_name.name, address, length,
+               vma->vm_start, pgprot_val(vma->vm_page_prot));
+ #endif
+       return 0;
+ }
+ static struct file_operations cramfs_linear_xip_fops = {
+   aio_read:   generic_file_aio_read,
+       read:   do_sync_read,
+       mmap:   cramfs_mmap,
+ };
+ #define CRAMFS_INODE_IS_XIP(x) ((x)->i_mode & S_ISVTX)
+ #endif
+ #ifdef CONFIG_CRAMFS_LINEAR
+ static struct backing_dev_info cramfs_backing_dev_info = {
+       .ra_pages       = 0,    /* No readahead */
+ };
+ #endif
+
 static struct inode *get_cramfs_inode(struct super_block *sb,
                                struct cramfs_inode * cramfs_inode)
 {
@@ -106,11 +201,32 @@ static struct inode *get_cramfs_inode(struct super_block *sb,
                                            cramfs_iget5_test, cramfs_iget5_set,
                                            cramfs_inode);
        if (inode && (inode->i_state & I_NEW)) {
+#ifdef CONFIG_CRAMFS_LINEAR
+                inode->i_mapping->backing_dev_info = &cramfs_backing_dev_info;
+#endif
+
+#ifdef CONFIG_CRAMFS_LINEAR_XIP
+               if(S_ISREG(inode->i_mode)) 
+                       inode->i_fop = CRAMFS_INODE_IS_XIP(inode) ? &cramfs_linear_xip_fops : &generic_ro_fops;
+#endif          
                unlock_new_inode(inode);
        }
        return inode;
 }
 
+#ifdef CONFIG_CRAMFS_LINEAR
+/*
+ * Return a pointer to the block in the linearly addressed cramfs image.
+ */
+static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned int len)
+{
+       struct cramfs_sb_info *sbi = CRAMFS_SB(sb);
+
+       if (!len)
+               return NULL;
+       return (void*)(sbi->linear_virt_addr + offset);
+}
+#else /* Not linear addressing - aka regular block mode. */
 /*
  * We have our own block cache: don't fill up the buffer cache
  * with the rom-image, because the way the filesystem is set
@@ -219,6 +335,7 @@ static void *cramfs_read(struct super_block *sb, unsigned int offset, unsigned i
        }
        return read_buffers[buffer] + offset;
 }
+#endif /* CONFIG_CRAMFS_LINEAR */
 
 static void cramfs_put_super(struct super_block *sb)
 {
@@ -234,7 +351,11 @@ static int cramfs_remount(struct super_block *sb, int *flags, char *data)
 
 static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
 {
+#ifndef CONFIG_CRAMFS_LINEAR
        int i;
+#else
+       char *p;
+#endif
        struct cramfs_super super;
        unsigned long root_offset;
        struct cramfs_sb_info *sbi;
@@ -247,11 +368,48 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
                return -ENOMEM;
        sb->s_fs_info = sbi;
 
+#ifndef CONFIG_CRAMFS_LINEAR
        /* Invalidate the read buffers on mount: think disk change.. */
        mutex_lock(&read_mutex);
        for (i = 0; i < READ_BUFFERS; i++)
                buffer_blocknr[i] = -1;
 
+#else /* CONFIG_CRAMFS_LINEAR */
+        /*
+         * 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="))) {
+                printk(KERN_ERR "cramfs: unknown physical address for linear cramfs image\n");
+                goto out;
+        }
+        sbi->linear_phys_addr = simple_strtoul(p + 9, NULL, 0);
+        if (sbi->linear_phys_addr & (PAGE_SIZE-1)) {
+                printk(KERN_ERR "cramfs: physical address 0x%lx for linear cramfs isn't aligned to a page boundary\n",
+                       sbi->linear_phys_addr);
+                goto out;
+        }
+        if (sbi->linear_phys_addr == 0) { 
+                printk(KERN_ERR "cramfs: physical address for linear cramfs image can't be 0\n");
+                goto out;
+        }
+        printk(KERN_INFO "cramfs: checking physical address 0x%lx for linear cramfs image\n",
+               sbi->linear_phys_addr);
+
+        /* Map only one page for now.  Will remap it when fs size is known. */
+        sbi->linear_virt_addr =
+                ioremap(sbi->linear_phys_addr, PAGE_SIZE);
+        if (!sbi->linear_virt_addr) {
+                printk(KERN_ERR "cramfs: ioremap of the linear cramfs image failed\n");
+                goto out;
+        }
+
+        mutex_lock(&read_mutex);
+#endif /* CONFIG_CRAMFS_LINEAR */
+
        /* Read the first block and get the superblock from it */
        memcpy(&super, cramfs_read(sb, 0, sizeof(super)), sizeof(super));
        mutex_unlock(&read_mutex);
@@ -312,8 +470,27 @@ static int cramfs_fill_super(struct super_block *sb, void *data, int silent)
                iput(root);
                goto out;
        }
+
+#ifdef CONFIG_CRAMFS_LINEAR
+       /* Remap the whole filesystem now */
+       iounmap(sbi->linear_virt_addr);
+       printk(KERN_INFO "cramfs: linear cramfs image appears to be %lu KB in size\n",
+              sbi->size/1024);
+
+       sbi->linear_virt_addr =
+               ioremap_cached(sbi->linear_phys_addr, sbi->size);
+
+       if (!sbi->linear_virt_addr) {
+               printk(KERN_ERR "cramfs: ioremap of the linear cramfs image failed\n");
+               goto out;
+       }
+#endif /* CONFIG_CRAMFS_LINEAR */
        return 0;
 out:
+#ifdef CONFIG_CRAMFS_LINEAR
+       if (sbi->linear_virt_addr)
+               iounmap(sbi->linear_virt_addr);
+#endif /* CONFIG_CRAMFS_LINEAR */
        kfree(sbi);
        sb->s_fs_info = NULL;
        return -EINVAL;
@@ -473,6 +650,20 @@ static int cramfs_readpage(struct file *file, struct page * page)
                u32 blkptr_offset = OFFSET(inode) + page->index*4;
                u32 start_offset, compr_len;
 
+#ifdef CONFIG_CRAMFS_LINEAR_XIP
+               if(CRAMFS_INODE_IS_XIP(inode)) {
+                       blkptr_offset = 
+                               PAGE_ALIGN(OFFSET(inode)) + 
+                               page->index * PAGE_CACHE_SIZE;
+                       mutex_lock(&read_mutex);
+                       memcpy(page_address(page),
+                               cramfs_read(sb, blkptr_offset, PAGE_CACHE_SIZE),
+                               PAGE_CACHE_SIZE);
+                       mutex_unlock(&read_mutex);
+                       bytes_filled = PAGE_CACHE_SIZE;
+                       pgdata = kmap(page);
+               } else {
+#endif /* CONFIG_CRAMFS_LINEAR_XIP */
                start_offset = OFFSET(inode) + maxblock*4;
                mutex_lock(&read_mutex);
                if (page->index)
@@ -492,6 +683,9 @@ static int cramfs_readpage(struct file *file, struct page * page)
                                 compr_len);
                        mutex_unlock(&read_mutex);
                }
+#ifdef CONFIG_CRAMFS_LINEAR_XIP
+               }
+#endif /* CONFIG_CRAMFS_LINEAR_XIP */
        } else
                pgdata = kmap(page);
        memset(pgdata + bytes_filled, 0, PAGE_CACHE_SIZE - bytes_filled);
@@ -519,11 +713,11 @@ static const struct file_operations cramfs_directory_operations = {
        .readdir        = cramfs_readdir,
 };
 
-static const struct inode_operations cramfs_dir_inode_operations = {
+static struct inode_operations cramfs_dir_inode_operations = {
        .lookup         = cramfs_lookup,
 };
 
-static const struct super_operations cramfs_ops = {
+static struct super_operations cramfs_ops = {
        .put_super      = cramfs_put_super,
        .remount_fs     = cramfs_remount,
        .statfs         = cramfs_statfs,
@@ -532,8 +726,12 @@ static const struct super_operations cramfs_ops = {
 static int cramfs_get_sb(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data, struct vfsmount *mnt)
 {
-       return get_sb_bdev(fs_type, flags, dev_name, data, cramfs_fill_super,
-                          mnt);
+
+#ifdef CONFIG_CRAMFS_LINEAR
+       return get_sb_nodev(fs_type, flags, data, cramfs_fill_super,mnt);
+#else
+       return get_sb_bdev(fs_type, flags, dev_name, data, cramfs_fill_super, mnt);
+#endif
 }
 
 static struct file_system_type cramfs_fs_type = {
@@ -541,7 +739,9 @@ static struct file_system_type cramfs_fs_type = {
        .name           = "cramfs",
        .get_sb         = cramfs_get_sb,
        .kill_sb        = kill_block_super,
+#ifndef CONFIG_CRAMFS_LINEAR
        .fs_flags       = FS_REQUIRES_DEV,
+#endif /* CONFIG_CRAMFS_LINEAR */
 };
 
 static int __init init_cramfs_fs(void)
index 8390693568fd9aa333decc319fd6e69f5cf35816..e3ff06de7f9dddecaa23f1db164eb7bad69a0fae 100644 (file)
@@ -10,6 +10,10 @@ struct cramfs_sb_info {
                        unsigned long blocks;
                        unsigned long files;
                        unsigned long flags;
+#ifdef CONFIG_CRAMFS_LINEAR
+                       unsigned long linear_phys_addr;
+                       char *        linear_virt_addr;
+#endif /* CONFIG_CRAMFS_LINEAR */
 };
 
 static inline struct cramfs_sb_info *CRAMFS_SB(struct super_block *sb)
index 1c1207472bb45657553ff349cf081c44840b310c..4f2b42c926649af09d0343fbc5f0df649916118f 100644 (file)
@@ -164,6 +164,7 @@ extern unsigned int kobjsize(const void *objp);
 #define VM_DONTEXPAND  0x00040000      /* Cannot expand with mremap() */
 #define VM_RESERVED    0x00080000      /* Count as reserved_vm like IO */
 #define VM_ACCOUNT     0x00100000      /* Is a VM accounted object */
+#define VM_XIP          0x00200000      /* Execute In Place from ROM/flash */
 #define VM_HUGETLB     0x00400000      /* Huge TLB Page VM */
 #define VM_NONLINEAR   0x00800000      /* Is non-linear (remap_file_pages) */
 #define VM_MAPPED_COPY 0x01000000      /* T if mapped copy of data (nommu mmap) */
index 4efa1e5385e3a1b110b29b46ceccedf36a8c4138..cf317e04bbcf1800d309dc3890316925ea53fd85 100644 (file)
@@ -360,6 +360,16 @@ static int __init mount_nfs_root(void)
 }
 #endif
 
+#ifdef CONFIG_ROOT_CRAMFS_LINEAR
+static int __init mount_cramfs_linear_root(void)
+{
+       create_dev("/dev/root", ROOT_DEV);
+       if (do_mount_root("/dev/root","cramfs",root_mountflags,root_mount_data) == 0)
+               return 1;
+       return 0;
+}
+#endif
+
 #if defined(CONFIG_BLK_DEV_RAM) || defined(CONFIG_BLK_DEV_FD)
 void __init change_floppy(char *fmt, ...)
 {
@@ -392,6 +402,13 @@ void __init change_floppy(char *fmt, ...)
 
 void __init mount_root(void)
 {
+#ifdef CONFIG_ROOT_CRAMFS_LINEAR
+        if (ROOT_DEV == MKDEV(0, 0)) {
+               if (mount_cramfs_linear_root())
+                       return;
+               printk (KERN_ERR "VFS: Unable to mount linear cramfs root.\n");
+       }
+#endif
 #ifdef CONFIG_ROOT_NFS
        if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {
                if (mount_nfs_root())
index f64cbf9baa3633fc1755f675ec2b46c920c4ecef..1b17fde23249b6f82fc32b6b634eaa03a49682b5 100644 (file)
@@ -1035,7 +1035,8 @@ int get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
                        continue;
                }
 
-               if (!vma || (vma->vm_flags & (VM_IO | VM_PFNMAP))
+               if (!vma || ((vma->vm_flags & (VM_IO | VM_PFNMAP))
+                               && !(vma->vm_flags & VM_XIP))
                                || !(vm_flags & vma->vm_flags))
                        return i ? : -EFAULT;
 
@@ -1580,6 +1581,21 @@ static inline pte_t maybe_mkwrite(pte_t pte, struct vm_area_struct *vma)
        return pte;
 }
 
+/*
+ * We hold the mm semaphore for reading and vma->vm_mm->page_table_lock
+ */
+static inline void break_cow(struct vm_area_struct * vma, struct page * new_page, unsigned long address,
+                pte_t *page_table)
+{
+        pte_t entry;
+
+        entry = maybe_mkwrite(pte_mkdirty(mk_pte(new_page, vma->vm_page_prot)),
+                              vma);
+        ptep_establish(vma, address, page_table, entry);
+        update_mmu_cache(vma, address, entry);
+        lazy_mmu_prot_update(entry);
+}
+
 static inline void cow_user_page(struct page *dst, struct page *src, unsigned long va, struct vm_area_struct *vma)
 {
        /*
@@ -1631,10 +1647,53 @@ static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,
                spinlock_t *ptl, pte_t orig_pte)
 {
        struct page *old_page, *new_page;
+       unsigned long pfn = pte_pfn(orig_pte);
        pte_t entry;
        int reuse = 0, ret = VM_FAULT_MINOR;
        struct page *dirty_page = NULL;
 
+       if (unlikely(!pfn_valid(pfn))) {
+                if ((vma->vm_flags & VM_XIP) && pte_present(orig_pte) && 
+                       pte_read(orig_pte)) {
+                        /*
+                         * Handle COW of XIP memory.
+                         * Note that the source memory actually isn't a ram
+                         * page so no struct page is associated to the source
+                         * pte.
+                         */
+                        char *dst;
+                        int ret;
+
+                        spin_unlock(&mm->page_table_lock);
+                        new_page = alloc_page(GFP_HIGHUSER);
+                        if (!new_page)
+                                return VM_FAULT_OOM;
+
+                        /* copy XIP data to memory */
+
+                        dst = kmap_atomic(new_page, KM_USER0);
+                        ret = copy_from_user(dst, (void*)address, PAGE_SIZE);
+                        kunmap_atomic(dst, KM_USER0);
+
+                        /* make sure pte didn't change while we dropped the
+                           lock */
+                        spin_lock(&mm->page_table_lock);
+                        if (!ret && pte_same(*page_table, orig_pte)) {
+                               inc_mm_counter(mm, file_rss);
+                                break_cow(vma, new_page, address, page_table);
+                                lru_cache_add(new_page);
+                                page_add_file_rmap(new_page);
+                                spin_unlock(&mm->page_table_lock);
+                                return VM_FAULT_MINOR;  /* Minor fault */
+                        }
+
+                        /* pte changed: back off */
+                        spin_unlock(&mm->page_table_lock);
+                        page_cache_release(new_page);
+                        return ret ? VM_FAULT_OOM : VM_FAULT_MINOR;
+               }
+       }
+
        old_page = vm_normal_page(vma, address, orig_pte);
        if (!old_page)
                goto gotten;