/* * Direct UBI block device access * * Author: dmitry pervushin * * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Embedded Alley Solutions, Inc. All Rights Reserved. * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html * * Based on mtdblock by: * (C) 2000-2003 Nicolas Pitre * (C) 1999-2003 David Woodhouse */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mtdcore.h" static LIST_HEAD(ubiblk_devices); /** * ubiblk_dev - the structure representing translation layer * * @m: interface to mtd_blktrans * @ubi_num: UBI device number * @ubi_vol: UBI volume ID * @usecount: reference count * * @cache_mutex: protects access to cache_data * @cache_data: content of the cached LEB * @cache_offset: offset of cached data on UBI volume, in bytes * @cache_size: cache size in bytes, usually equal to LEB size */ struct ubiblk_dev { struct mtd_blktrans_dev m; int ubi_num; int ubi_vol; int usecount; struct ubi_volume_desc *ubi; struct mutex cache_mutex; unsigned char *cache_data; unsigned long cache_offset; unsigned int cache_size; enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; struct list_head list; struct work_struct unbind; }; static int ubiblock_open(struct mtd_blktrans_dev *mbd); static int ubiblock_release(struct mtd_blktrans_dev *mbd); static int ubiblock_flush(struct mtd_blktrans_dev *mbd); static int ubiblock_readsect(struct mtd_blktrans_dev *mbd, unsigned long block, char *buf); static int ubiblock_writesect(struct mtd_blktrans_dev *mbd, unsigned long block, char *buf); static int ubiblock_getgeo(struct mtd_blktrans_dev *mbd, struct hd_geometry *geo); static void *ubiblk_add(int ubi_num, int ubi_vol_id); static void *ubiblk_add_locked(int ubi_num, int ubi_vol_id); static int ubiblk_del(struct ubiblk_dev *u); static int ubiblk_del_locked(struct ubiblk_dev *u); /* * These two routines are just to satify mtd_blkdev's requirements */ static void ubiblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) { return; } static void ubiblock_remove_dev(struct mtd_blktrans_dev *mbd) { return; } static struct mtd_blktrans_ops ubiblock_tr = { .name = "ubiblk", .major = 0, /* assign dynamically */ .part_bits = 3, /* allow up to 8 parts */ .blksize = 512, .open = ubiblock_open, .release = ubiblock_release, .flush = ubiblock_flush, .readsect = ubiblock_readsect, .writesect = ubiblock_writesect, .getgeo = ubiblock_getgeo, .add_mtd = ubiblock_add_mtd, .remove_dev = ubiblock_remove_dev, .owner = THIS_MODULE, }; static int ubiblock_getgeo(struct mtd_blktrans_dev *bdev, struct hd_geometry *geo) { return -ENOTTY; } /** * ubiblk_write_cached_data - flush the cache to the UBI volume */ static int ubiblk_write_cached_data(struct ubiblk_dev *u) { int ret; if (u->cache_state != STATE_DIRTY) return 0; pr_debug("%s: volume %d:%d, writing at %lx of size %x\n", __func__, u->ubi_num, u->ubi_vol, u->cache_offset, u->cache_size); ret = ubi_write(u->ubi, u->cache_offset / u->cache_size, u->cache_data, 0, u->cache_size); pr_debug("leb_write status %d\n", ret); u->cache_state = STATE_EMPTY; return ret; } /** * ubiblk_do_cached_write - cached write the data to the UBI volume * * @u: ubiblk_dev * @pos: offset on the block device * @len: buffer length * @buf: buffer itself * * if buffer contains one or more whole sectors (=LEBs), write them to the * volume. Otherwise, fill the cache that will be flushed later */ static int ubiblk_do_cached_write(struct ubiblk_dev *u, unsigned long pos, int len, const char *buf) { unsigned int sect_size = u->cache_size, size, offset; unsigned long sect_start; int ret = 0; int leb; pr_debug("%s: volume %d:%d, writing at pos %lx of size %x\n", __func__, u->ubi_num, u->ubi_vol, pos, len); while (len > 0) { leb = pos / sect_size; sect_start = leb * sect_size; offset = pos - sect_start; size = sect_size - offset; if (size > len) size = len; if (size == sect_size) { /* * We are covering a whole sector. Thus there is no * need to bother with the cache while it may still be * useful for other partial writes. */ ret = ubi_leb_change(u->ubi, leb, buf, size, UBI_UNKNOWN); if (ret) goto out; } else { /* Partial sector: need to use the cache */ if (u->cache_state == STATE_DIRTY && u->cache_offset != sect_start) { ret = ubiblk_write_cached_data(u); if (ret) goto out; } if (u->cache_state == STATE_EMPTY || u->cache_offset != sect_start) { /* fill the cache with the current sector */ u->cache_state = STATE_EMPTY; ret = ubi_leb_read(u->ubi, leb, u->cache_data, 0, u->cache_size, 0); if (ret) return ret; ret = ubi_leb_unmap(u->ubi, leb); if (ret) return ret; ret = ubi_leb_unmap(u->ubi, leb); if (ret) return ret; u->cache_offset = sect_start; u->cache_state = STATE_CLEAN; } /* write data to our local cache */ memcpy(u->cache_data + offset, buf, size); u->cache_state = STATE_DIRTY; } buf += size; pos += size; len -= size; } out: return ret; } /** * ubiblk_do_cached_read - cached read the data from ubi volume * * @u: ubiblk_dev * @pos: offset on the block device * @len: buffer length * @buf: preallocated buffer * * Cached LEB will be used if possible; otherwise data will be using * ubi_leb_read */ static int ubiblk_do_cached_read(struct ubiblk_dev *u, unsigned long pos, int len, char *buf) { unsigned int sect_size = u->cache_size; int err = 0; unsigned long sect_start; unsigned offset, size; int leb; pr_debug("%s: read at 0x%lx, size 0x%x\n", __func__, pos, len); while (len > 0) { leb = pos/sect_size; sect_start = leb*sect_size; offset = pos - sect_start; size = sect_size - offset; if (size > len) size = len; /* * Check if the requested data is already cached * Read the requested amount of data from our internal * cache if it contains what we want, otherwise we read * the data directly from flash. */ if (u->cache_state != STATE_EMPTY && u->cache_offset == sect_start) { pr_debug("%s: cached, returning back from cache\n", __func__); memcpy(buf, u->cache_data + offset, size); err = 0; } else { pr_debug("%s: pos = %ld, reading leb = %d\n", __func__, pos, leb); err = ubi_leb_read(u->ubi, leb, buf, offset, size, 0); if (err) goto out; } buf += size; pos += size; len -= size; } out: return err; } /** * ubiblock_writesect - write the sector * * Allocate the cache, if necessary and perform actual write using * ubiblk_do_cached_write */ static int ubiblock_writesect(struct mtd_blktrans_dev *mbd, unsigned long block, char *buf) { struct ubiblk_dev *u = container_of(mbd, struct ubiblk_dev, m); if (unlikely(!u->cache_data)) { u->cache_data = vmalloc(u->cache_size); if (!u->cache_data) return -EAGAIN; } return ubiblk_do_cached_write(u, block<<9, 512, buf); } /** * ubiblk_readsect - read the sector * * Allocate the cache, if necessary, and perform actual read using * ubiblk_do_cached_read */ static int ubiblock_readsect(struct mtd_blktrans_dev *mbd, unsigned long block, char *buf) { struct ubiblk_dev *u = container_of(mbd, struct ubiblk_dev, m); if (unlikely(!u->cache_data)) { u->cache_data = vmalloc(u->cache_size); if (!u->cache_data) return -EAGAIN; } return ubiblk_do_cached_read(u, block<<9, 512, buf); } static int ubiblk_flush_locked(struct ubiblk_dev *u) { ubiblk_write_cached_data(u); ubi_sync(u->ubi_num); return 0; } static int ubiblock_flush(struct mtd_blktrans_dev *mbd) { struct ubiblk_dev *u = container_of(mbd, struct ubiblk_dev, m); mutex_lock(&u->cache_mutex); ubiblk_flush_locked(u); mutex_unlock(&u->cache_mutex); return 0; } static int ubiblock_open(struct mtd_blktrans_dev *mbd) { struct ubiblk_dev *u = container_of(mbd, struct ubiblk_dev, m); if (u->usecount == 0) { u->ubi = ubi_open_volume(u->ubi_num, u->ubi_vol, UBI_READWRITE); if (IS_ERR(u->ubi)) return PTR_ERR(u->ubi); } u->usecount++; return 0; } static int ubiblock_release(struct mtd_blktrans_dev *mbd) { struct ubiblk_dev *u = container_of(mbd, struct ubiblk_dev, m); if (--u->usecount == 0) { mutex_lock(&u->cache_mutex); ubiblk_flush_locked(u); vfree(u->cache_data); u->cache_data = NULL; mutex_unlock(&u->cache_mutex); ubi_close_volume(u->ubi); u->ubi = NULL; } return 0; } /* * sysfs routines. The ubiblk creates two entries under /sys/block/ubiblkX: * - volume, R/O, which is read like "ubi0:volume_name" * - unbind, W/O; when user writes something here, the block device is * removed * * unbind schedules a work item to perform real unbind, because sysfs entry * handler cannot delete itself :) */ ssize_t volume_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gendisk *gd = dev_to_disk(dev); struct mtd_blktrans_dev *m = gd->private_data; struct ubiblk_dev *u = container_of(m, struct ubiblk_dev, m); return sprintf(buf, "%d:%d\n", u->ubi_num, u->ubi_vol); } static void ubiblk_unbind(struct work_struct *ws) { struct ubiblk_dev *u = container_of(ws, struct ubiblk_dev, unbind); ubiblk_del(u); } ssize_t unbind_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct gendisk *gd = dev_to_disk(dev); struct mtd_blktrans_dev *m = gd->private_data; struct ubiblk_dev *u = container_of(m, struct ubiblk_dev, m); INIT_WORK(&u->unbind, ubiblk_unbind); schedule_work(&u->unbind); return count; } DEVICE_ATTR(unbind, 0644, NULL, unbind_store); DEVICE_ATTR(volume, 0644, volume_show, NULL); static int ubiblk_sysfs(struct gendisk *hd, int add) { int r = 0; if (add) { r = device_create_file(disk_to_dev(hd), &dev_attr_unbind); if (r < 0) goto out; r = device_create_file(disk_to_dev(hd), &dev_attr_volume); if (r < 0) goto out1; return 0; } device_remove_file(disk_to_dev(hd), &dev_attr_unbind); out1: device_remove_file(disk_to_dev(hd), &dev_attr_volume); out: return r; } /** * add the FTL by registering it with mtd_blkdevs */ static void *ubiblk_add(int ubi_num, int ubi_vol_id) { void *p; mutex_lock(&mtd_table_mutex); p = ubiblk_add_locked(ubi_num, ubi_vol_id); mutex_unlock(&mtd_table_mutex); return p; } static void *ubiblk_add_locked(int ubi_num, int ubi_vol_id) { struct ubiblk_dev *u = kzalloc(sizeof(*u), GFP_KERNEL); struct ubi_volume_info uvi; struct ubi_volume_desc *ubi; if (!u) { u = ERR_PTR(-ENOMEM); goto out; } ubi = ubi_open_volume(ubi_num, ubi_vol_id, UBI_READONLY); if (IS_ERR(u->ubi)) { pr_err("cannot open the volume\n"); u = (void *)u->ubi; goto out; } ubi_get_volume_info(ubi, &uvi); ubi_close_volume(ubi); pr_debug("adding volume of size %d (used_size %lld), LEB size %d\n", uvi.size, uvi.used_bytes, uvi.usable_leb_size); u->m.mtd = NULL; u->m.devnum = -1; u->m.size = uvi.used_bytes >> 9; u->m.tr = &ubiblock_tr; u->ubi_num = ubi_num; u->ubi_vol = ubi_vol_id; mutex_init(&u->cache_mutex); u->cache_state = STATE_EMPTY; u->cache_size = uvi.usable_leb_size; u->cache_data = NULL; u->usecount = 0; INIT_LIST_HEAD(&u->list); list_add_tail(&u->list, &ubiblk_devices); add_mtd_blktrans_dev(&u->m); ubiblk_sysfs(u->m.blkcore_priv, true); out: return u; } static int ubiblk_del(struct ubiblk_dev *u) { int r; mutex_lock(&mtd_table_mutex); r = ubiblk_del_locked(u); mutex_unlock(&mtd_table_mutex); return r; } static int ubiblk_del_locked(struct ubiblk_dev *u) { if (u->usecount != 0) return -EBUSY; ubiblk_sysfs(u->m.blkcore_priv, false); del_mtd_blktrans_dev(&u->m); list_del(&u->list); BUG_ON(u->cache_data != NULL); /* who did not free the cache ?! */ kfree(u); return 0; } static struct ubiblk_dev *ubiblk_find(int num, int vol) { struct ubiblk_dev *pos; list_for_each_entry(pos, &ubiblk_devices, list) if (pos->ubi_num == num && pos->ubi_vol == vol) return pos; return NULL; } static int ubiblock_notification(struct notifier_block *blk, unsigned long type, void *v) { struct ubi_notification *nt = v; struct ubiblk_dev *u; switch (type) { case UBI_VOLUME_ADDED: ubiblk_add(nt->vi.ubi_num, nt->vi.vol_id); break; case UBI_VOLUME_REMOVED: u = ubiblk_find(nt->vi.ubi_num, nt->vi.vol_id); if (u) ubiblk_del(u); break; case UBI_VOLUME_RENAMED: case UBI_VOLUME_RESIZED: break; } return NOTIFY_OK; } static struct notifier_block ubiblock_nb = { .notifier_call = ubiblock_notification, }; static int __init ubiblock_init(void) { int r; r = register_mtd_blktrans(&ubiblock_tr); if (r) goto out; r = ubi_register_volume_notifier(&ubiblock_nb, 0); if (r) goto out_unreg; return 0; out_unreg: deregister_mtd_blktrans(&ubiblock_tr); out: return 0; } static void __exit ubiblock_exit(void) { ubi_unregister_volume_notifier(&ubiblock_nb); deregister_mtd_blktrans(&ubiblock_tr); } module_init(ubiblock_init); module_exit(ubiblock_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Caching block device emulation access to UBI devices"); MODULE_AUTHOR("dmitry pervushin ");