summaryrefslogtreecommitdiffstats
path: root/linux-2.4.x/drivers/mtd/devices/ramtd.c
diff options
context:
space:
mode:
Diffstat (limited to 'linux-2.4.x/drivers/mtd/devices/ramtd.c')
-rw-r--r--linux-2.4.x/drivers/mtd/devices/ramtd.c249
1 files changed, 249 insertions, 0 deletions
diff --git a/linux-2.4.x/drivers/mtd/devices/ramtd.c b/linux-2.4.x/drivers/mtd/devices/ramtd.c
new file mode 100644
index 0000000..882ff33
--- /dev/null
+++ b/linux-2.4.x/drivers/mtd/devices/ramtd.c
@@ -0,0 +1,249 @@
+/**
+ * Simple mtd driver that dynamically allocates/frees memory
+ *
+ * $Id: ramtd.c,v 1.7 2006/03/08 17:48:28 joern Exp $
+ *
+ * Copyright (c) 2005 Joern Engel <joern@wh.fh-wedel.de>
+ * Copyright (c) 2006 iSteve <isteve@bofh.cz>
+ * - Added module parameters, immediate allocation and fixed few leaks.
+ */
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/vmalloc.h>
+
+struct ramtd {
+ struct mtd_info mtd;
+ struct list_head list;
+ u32 size;
+ void *page[];
+};
+
+static LIST_HEAD(ramtd_list);
+static DECLARE_MUTEX(ramtd_mutex);
+
+static unsigned long ramtd_size = 4*1024*1024;
+static int ramtd_now = 0;
+
+module_param(ramtd_size, ulong, 0);
+MODULE_PARM_DESC(ramtd_size, "Total device size in bytes");
+
+module_param(ramtd_now, bool, 0);
+MODULE_PARM_DESC(ramtd_now, "Allocate all memory when loaded");
+
+
+static void *get_pool_page(void)
+{
+ void *ret = (void*)__get_free_page(GFP_KERNEL);
+ return ret;
+}
+
+
+static void free_pool_page(void *page)
+{
+ free_page((unsigned long)page);
+}
+
+static void free_all_pages(struct ramtd *this)
+{
+ u32 page;
+
+ for (page = 0; page < this->mtd.size / PAGE_SIZE; page++) {
+ if (this->page[page])
+ free_pool_page(this->page[page]);
+ }
+}
+
+static int alloc_all_pages(struct ramtd *this)
+{
+ u32 page;
+
+ if (!ramtd_now)
+ return 0;
+
+ for (page = 0; page < this->mtd.size / PAGE_SIZE; page++) {
+ if (this->page[page])
+ continue;
+
+ this->page[page] = get_pool_page();
+ if (!this->page[page])
+ return -ENOMEM;
+
+ memset(this->page[page], 0xff, PAGE_SIZE);
+ }
+
+ return 0;
+}
+
+
+static int ramtd_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct ramtd *ramtd = container_of(mtd, typeof(*ramtd), mtd);
+ u32 addr = instr->addr;
+ u32 len = instr->len;
+
+ if (addr + len > mtd->size)
+ return -EINVAL;
+ if (addr % PAGE_SIZE)
+ return -EINVAL;
+ if (len % PAGE_SIZE)
+ return -EINVAL;
+
+ while (len) {
+ u32 page = addr / PAGE_SIZE;
+
+ down_interruptible(&ramtd_mutex);
+ free_pool_page(ramtd->page[page]);
+ ramtd->page[page] = NULL;
+ up(&ramtd_mutex);
+
+ addr += PAGE_SIZE;
+ len -= PAGE_SIZE;
+ }
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+
+static int ramtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct ramtd *ramtd = container_of(mtd, typeof(*ramtd), mtd);
+
+ if (from >= mtd->size)
+ return -EINVAL;
+
+ if (len > mtd->size - from)
+ len = mtd->size - from;
+
+ *retlen = 0;
+ while (len) {
+ u32 page = from / PAGE_SIZE;
+ u32 ofs = from % PAGE_SIZE;
+ u32 rlen = min_t(u32, len, PAGE_SIZE - ofs);
+
+ down_interruptible(&ramtd_mutex);
+ if (!ramtd->page[page])
+ memset(buf, 0xff, rlen);
+ else
+ memcpy(buf, ramtd->page[page] + ofs, rlen);
+ up(&ramtd_mutex);
+
+ buf += rlen;
+ from += rlen;
+ *retlen += rlen;
+ len -= rlen;
+ }
+ return 0;
+}
+
+
+static int ramtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct ramtd *ramtd = container_of(mtd, typeof(*ramtd), mtd);
+
+ if (to >= mtd->size)
+ return -EINVAL;
+
+ if (len > mtd->size - to)
+ len = mtd->size - to;
+
+ *retlen = 0;
+ while (len) {
+ u32 page = to / PAGE_SIZE;
+ u32 ofs = to % PAGE_SIZE;
+ u32 wlen = min_t(u32, len, PAGE_SIZE - ofs);
+
+ down_interruptible(&ramtd_mutex);
+ if (!ramtd->page[page]) {
+ ramtd->page[page] = get_pool_page();
+ memset(ramtd->page[page], 0xff, PAGE_SIZE);
+ }
+ if (!ramtd->page[page])
+ return -EIO;
+
+ memcpy(ramtd->page[page] + ofs, buf, wlen);
+ up(&ramtd_mutex);
+
+ buf += wlen;
+ to += wlen;
+ *retlen += wlen;
+ len -= wlen;
+ }
+ return 0;
+}
+
+
+static int register_device(const char *name, u32 size)
+{
+ struct ramtd *new;
+ u32 pages_size;
+ int err;
+
+ size = PAGE_ALIGN(size);
+ pages_size = size / PAGE_SIZE * sizeof(void*);
+ new = vmalloc(sizeof(*new) + pages_size);
+ if (!new)
+ return -ENOMEM;
+ memset(new, 0, sizeof(*new) + pages_size);
+
+ new->mtd.name = (char*)name;
+ new->mtd.size = size;
+ new->mtd.flags = MTD_CAP_RAM | MTD_ERASEABLE | MTD_VOLATILE;
+ new->mtd.owner = THIS_MODULE;
+ new->mtd.type = MTD_RAM;
+ new->mtd.erasesize = PAGE_SIZE;
+ new->mtd.write = ramtd_write;
+ new->mtd.read = ramtd_read;
+ new->mtd.erase = ramtd_erase;
+
+ if (add_mtd_device(&new->mtd)) {
+ free_pool_page(new);
+ return -EAGAIN;
+ }
+
+ err = alloc_all_pages(new);
+ if (err) {
+ free_all_pages(new);
+ vfree(new);
+ return err;
+ }
+
+ down_interruptible(&ramtd_mutex);
+ list_add(&new->list, &ramtd_list);
+ up(&ramtd_mutex);
+ return 0;
+}
+
+
+static int __init ramtd_init(void)
+{
+ return register_device("ramtd", ramtd_size); /* FIXME */
+}
+
+static void __exit ramtd_exit(void)
+{
+ struct ramtd *this, *next;
+
+ down_interruptible(&ramtd_mutex);
+ list_for_each_entry_safe(this, next, &ramtd_list, list) {
+ free_all_pages(this);
+ del_mtd_device(&this->mtd);
+ vfree(this);
+ }
+ up(&ramtd_mutex);
+}
+
+
+module_init(ramtd_init);
+module_exit(ramtd_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Joern Engel <joern@wh.fh-wedel.de>");
+MODULE_AUTHOR("iSteve <isteve@bofh.cz>");
+MODULE_DESCRIPTION("MTD using dynamic memory allocation");