summaryrefslogtreecommitdiffstats
path: root/fs/jffs2/fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/jffs2/fs.c')
-rw-r--r--fs/jffs2/fs.c483
1 files changed, 483 insertions, 0 deletions
diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c
new file mode 100644
index 0000000000..f094291aa4
--- /dev/null
+++ b/fs/jffs2/fs.c
@@ -0,0 +1,483 @@
+/*
+ * JFFS2 -- Journalling Flash File System, Version 2.
+ *
+ * Copyright © 2001-2007 Red Hat, Inc.
+ * Copyright © 2004-2010 David Woodhouse <dwmw2@infradead.org>
+ *
+ * Created by David Woodhouse <dwmw2@infradead.org>
+ *
+ * For licensing information, see the file 'LICENCE' in this directory.
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <common.h>
+#include <crc.h>
+#include <driver.h>
+#include <init.h>
+#include <fs.h>
+#include <malloc.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/mtd/mtd.h>
+#include <linux/pagemap.h>
+#include <linux/stat.h>
+#include "compr.h"
+#include "nodelist.h"
+#include "os-linux.h"
+
+static int jffs2_flash_setup(struct jffs2_sb_info *c);
+
+/* from include/linux/fs.h */
+static inline void i_uid_write(struct inode *inode, uid_t uid)
+{
+ inode->i_uid = uid;
+}
+
+static inline void i_gid_write(struct inode *inode, gid_t gid)
+{
+ inode->i_gid = gid;
+}
+
+const struct file_operations jffs2_file_operations;
+const struct inode_operations jffs2_file_inode_operations;
+
+static int jffs2_open(struct device_d *dev, FILE *file, const char *filename)
+{
+ struct inode *inode = file->f_inode;
+ struct jffs2_file *jf;
+
+ jf = xzalloc(sizeof(*jf));
+
+ jf->inode = inode;
+ jf->buf = xmalloc(JFFS2_BLOCK_SIZE);
+ jf->offset = -1;
+
+ file->priv = jf;
+
+ return 0;
+}
+
+static int jffs2_close(struct device_d *dev, FILE *f)
+{
+ struct jffs2_file *jf = f->priv;
+
+ free(jf->buf);
+ free(jf);
+
+ return 0;
+}
+
+static int jffs2_get_block(struct jffs2_file *jf, unsigned int pos)
+{
+ struct jffs2_sb_info *c = JFFS2_SB_INFO(jf->inode->i_sb);
+ struct jffs2_inode_info *f = JFFS2_INODE_INFO(jf->inode);
+ int ret;
+
+ if (pos != jf->offset) {
+ ret = jffs2_read_inode_range(c, f, jf->buf, pos,
+ JFFS2_BLOCK_SIZE);
+ if (ret && ret != -ENOENT)
+ return ret;
+ jf->offset = pos;
+ }
+
+ return 0;
+}
+
+static int jffs2_read(struct device_d *_dev, FILE *f, void *buf,
+ size_t insize)
+{
+ struct jffs2_file *jf = f->priv;
+ unsigned int pos = f->pos;
+ unsigned int ofs;
+ unsigned int now;
+ unsigned int size = insize;
+ int ret;
+
+ /* Read till end of current block */
+ ofs = f->pos % JFFS2_BLOCK_SIZE;
+ if (ofs) {
+ ret = jffs2_get_block(jf, pos);
+ if (ret)
+ return ret;
+
+ now = min(size, JFFS2_BLOCK_SIZE - ofs);
+
+ memcpy(buf, jf->buf + ofs, now);
+ size -= now;
+ pos += now;
+ buf += now;
+ }
+
+ /* Do full blocks */
+ while (size >= JFFS2_BLOCK_SIZE) {
+ ret = jffs2_get_block(jf, pos);
+ if (ret)
+ return ret;
+
+ memcpy(buf, jf->buf, JFFS2_BLOCK_SIZE);
+ size -= JFFS2_BLOCK_SIZE;
+ pos += JFFS2_BLOCK_SIZE;
+ buf += JFFS2_BLOCK_SIZE;
+ }
+
+ /* And the rest */
+ if (size) {
+ ret = jffs2_get_block(jf, pos);
+ if (ret)
+ return ret;
+ memcpy(buf, jf->buf, size);
+ }
+
+ return insize;
+
+}
+
+struct inode *jffs2_iget(struct super_block *sb, unsigned long ino)
+{
+ struct jffs2_inode_info *f;
+ struct jffs2_sb_info *c;
+ struct jffs2_raw_inode latest_node;
+ struct inode *inode;
+ int ret;
+
+ jffs2_dbg(1, "%s(): ino == %lu\n", __func__, ino);
+
+ inode = iget_locked(sb, ino);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+ if (!(inode->i_state & I_NEW))
+ return inode;
+
+ f = JFFS2_INODE_INFO(inode);
+ c = JFFS2_SB_INFO(inode->i_sb);
+
+ jffs2_init_inode_info(f);
+ mutex_lock(&f->sem);
+
+ ret = jffs2_do_read_inode(c, f, inode->i_ino, &latest_node);
+ if (ret)
+ goto error;
+
+ inode->i_mode = jemode_to_cpu(latest_node.mode);
+ i_uid_write(inode, je16_to_cpu(latest_node.uid));
+ i_gid_write(inode, je16_to_cpu(latest_node.gid));
+ inode->i_size = je32_to_cpu(latest_node.isize);
+ inode->i_atime = ITIME(je32_to_cpu(latest_node.atime));
+ inode->i_mtime = ITIME(je32_to_cpu(latest_node.mtime));
+ inode->i_ctime = ITIME(je32_to_cpu(latest_node.ctime));
+
+ set_nlink(inode, f->inocache->pino_nlink);
+
+ inode->i_blocks = (inode->i_size + 511) >> 9;
+
+ switch (inode->i_mode & S_IFMT) {
+
+ case S_IFLNK:
+ inode->i_op = &simple_symlink_inode_operations;
+ inode->i_link = f->target;
+ break;
+
+ case S_IFDIR:
+ {
+ struct jffs2_full_dirent *fd;
+ set_nlink(inode, 2); /* parent and '.' */
+
+ for (fd=f->dents; fd; fd = fd->next) {
+ if (fd->type == DT_DIR && fd->ino)
+ inc_nlink(inode);
+ }
+ /* Root dir gets i_nlink 3 for some reason */
+ if (inode->i_ino == 1)
+ inc_nlink(inode);
+
+ inode->i_op = &jffs2_dir_inode_operations;
+ inode->i_fop = &jffs2_dir_operations;
+ break;
+ }
+ case S_IFREG:
+ inode->i_op = &jffs2_file_inode_operations;
+ inode->i_fop = &jffs2_file_operations;
+ break;
+
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFSOCK:
+ case S_IFIFO:
+ break;
+
+ default:
+ pr_warn("%s(): Bogus i_mode %o for ino %lu\n",
+ __func__, inode->i_mode, (unsigned long)inode->i_ino);
+ }
+
+ mutex_unlock(&f->sem);
+
+ jffs2_dbg(1, "jffs2_read_inode() returning\n");
+ return inode;
+
+error:
+ mutex_unlock(&f->sem);
+ iget_failed(inode);
+ return ERR_PTR(ret);
+}
+
+static int calculate_inocache_hashsize(uint32_t flash_size)
+{
+ /*
+ * Pick a inocache hash size based on the size of the medium.
+ * Count how many megabytes we're dealing with, apply a hashsize twice
+ * that size, but rounding down to the usual big powers of 2. And keep
+ * to sensible bounds.
+ */
+
+ int size_mb = flash_size / 1024 / 1024;
+ int hashsize = (size_mb * 2) & ~0x3f;
+
+ if (hashsize < INOCACHE_HASHSIZE_MIN)
+ return INOCACHE_HASHSIZE_MIN;
+ if (hashsize > INOCACHE_HASHSIZE_MAX)
+ return INOCACHE_HASHSIZE_MAX;
+
+ return hashsize;
+}
+
+static void jffs2_put_super(struct super_block *sb)
+{
+ if (sb->s_fs_info) {
+ struct jffs2_sb_info *ctx = sb->s_fs_info;
+
+ jffs2_free_ino_caches(ctx);
+ jffs2_free_raw_node_refs(ctx);
+ kfree(ctx->blocks);
+ kfree(ctx->inocache_list);
+ jffs2_flash_cleanup(ctx);
+
+ kfree(sb->s_fs_info);
+ }
+}
+
+int jffs2_do_fill_super(struct super_block *sb, int silent)
+{
+ struct jffs2_sb_info *c;
+ struct inode *root_i;
+ int ret;
+ size_t blocks;
+
+ c = JFFS2_SB_INFO(sb);
+
+#ifndef CONFIG_JFFS2_FS_WRITEBUFFER
+ if (c->mtd->type == MTD_NANDFLASH) {
+ pr_err("Cannot operate on NAND flash unless jffs2 NAND support is compiled in");
+ return -EINVAL;
+ }
+ if (c->mtd->type == MTD_DATAFLASH) {
+ pr_err("Cannot operate on DataFlash unless jffs2 DataFlash support is compiled in");
+ return -EINVAL;
+ }
+#endif
+
+ c->flash_size = c->mtd->size;
+ c->sector_size = c->mtd->erasesize;
+ blocks = c->flash_size / c->sector_size;
+
+ /*
+ * Size alignment check
+ */
+ if ((c->sector_size * blocks) != c->flash_size) {
+ c->flash_size = c->sector_size * blocks;
+ pr_info("Flash size not aligned to erasesize, reducing to %dKiB",
+ c->flash_size / 1024);
+ }
+
+ if (c->flash_size < 5*c->sector_size) {
+ pr_err("Too few erase blocks (%d)",
+ c->flash_size / c->sector_size);
+ return -EINVAL;
+ }
+
+ c->cleanmarker_size = sizeof(struct jffs2_unknown_node);
+
+ /* NAND (or other bizarre) flash... do setup accordingly */
+ ret = jffs2_flash_setup(c);
+ if (ret)
+ return ret;
+
+ c->inocache_hashsize = calculate_inocache_hashsize(c->flash_size);
+ c->inocache_list = kcalloc(c->inocache_hashsize, sizeof(struct jffs2_inode_cache *), GFP_KERNEL);
+ if (!c->inocache_list) {
+ ret = -ENOMEM;
+ goto out_wbuf;
+ }
+
+ jffs2_init_xattr_subsystem(c);
+
+ if ((ret = jffs2_do_mount_fs(c)))
+ goto out_inohash;
+
+ jffs2_dbg(1, "%s(): Getting root inode\n", __func__);
+ root_i = jffs2_iget(sb, 1);
+ if (IS_ERR(root_i)) {
+ jffs2_dbg(1, "get root inode failed\n");
+ ret = PTR_ERR(root_i);
+ goto out_root;
+ }
+
+ ret = -ENOMEM;
+
+ jffs2_dbg(1, "%s(): d_make_root()\n", __func__);
+ sb->s_root = d_make_root(root_i);
+ if (!sb->s_root)
+ goto out_root;
+
+ sb->s_maxbytes = 0xFFFFFFFF;
+ sb->s_blocksize = PAGE_SIZE;
+ sb->s_blocksize_bits = PAGE_SHIFT;
+ sb->s_magic = JFFS2_SUPER_MAGIC;
+
+ return 0;
+
+out_root:
+ jffs2_free_ino_caches(c);
+ jffs2_free_raw_node_refs(c);
+out_inohash:
+ kfree(c->inocache_list);
+out_wbuf:
+ jffs2_flash_cleanup(c);
+ return ret;
+}
+
+static int jffs2_flash_setup(struct jffs2_sb_info *c) {
+ int ret = 0;
+
+ if (jffs2_cleanmarker_oob(c)) {
+ /* NAND flash... do setup accordingly */
+ ret = jffs2_nand_flash_setup(c);
+ if (ret)
+ return ret;
+ }
+
+ /* and Dataflash */
+ if (jffs2_dataflash(c)) {
+ ret = jffs2_dataflash_setup(c);
+ if (ret)
+ return ret;
+ }
+
+ /* and an UBI volume */
+ if (jffs2_ubivol(c)) {
+ ret = jffs2_ubivol_setup(c);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+void jffs2_flash_cleanup(struct jffs2_sb_info *c) {
+
+ if (jffs2_cleanmarker_oob(c)) {
+ jffs2_nand_flash_cleanup(c);
+ }
+
+ /* and DataFlash */
+ if (jffs2_dataflash(c)) {
+ jffs2_dataflash_cleanup(c);
+ }
+
+ /* and Intel "Sibley" flash */
+ if (jffs2_nor_wbuf_flash(c)) {
+ jffs2_nor_wbuf_flash_cleanup(c);
+ }
+
+ /* and an UBI volume */
+ if (jffs2_ubivol(c)) {
+ jffs2_ubivol_cleanup(c);
+ }
+}
+
+static int jffs2_probe(struct device_d *dev)
+{
+ struct fs_device_d *fsdev;
+ struct super_block *sb;
+ struct jffs2_sb_info *ctx;
+ int ret;
+
+ fsdev = dev_to_fs_device(dev);
+ sb = &fsdev->sb;
+
+ ret = fsdev_open_cdev(fsdev);
+ if (ret)
+ goto err_out;
+
+ ctx = kzalloc(sizeof(struct jffs2_sb_info), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->mtd = fsdev->cdev->mtd;
+
+ sb->s_fs_info = ctx;
+
+ ret = jffs2_compressors_init();
+ if (ret) {
+ pr_err("error: Failed to initialise compressors\n");
+ goto err_out;
+ }
+
+ ret = jffs2_create_slab_caches();
+ if (ret) {
+ pr_err("error: Failed to initialise slab caches\n");
+ goto err_compressors;
+ }
+
+ if (jffs2_fill_super(fsdev, 0)) {
+ dev_err(dev, "no valid jffs2 found\n");
+ ret = -EINVAL;
+ goto err_slab;
+ }
+
+ return 0;
+
+err_slab:
+ jffs2_destroy_slab_caches();
+err_compressors:
+ jffs2_compressors_exit();
+err_out:
+
+ return ret;
+}
+
+static void jffs2_remove(struct device_d *dev)
+{
+ struct fs_device_d *fsdev;
+ struct super_block *sb;
+
+ fsdev = dev_to_fs_device(dev);
+ sb = &fsdev->sb;
+
+ jffs2_destroy_slab_caches();
+ jffs2_compressors_exit();
+
+ jffs2_put_super(sb);
+}
+
+
+static struct fs_driver_d jffs2_driver = {
+ .open = jffs2_open,
+ .close = jffs2_close,
+ .read = jffs2_read,
+ .type = filetype_jffs2,
+ .flags = 0,
+ .drv = {
+ .probe = jffs2_probe,
+ .remove = jffs2_remove,
+ .name = "jffs2",
+ }
+};
+
+static int jffs2_init(void)
+{
+ return register_fs_driver(&jffs2_driver);
+}
+coredevice_initcall(jffs2_init);