2013-05-09 03:23:34 +04:00
|
|
|
/*
|
2013-05-09 03:25:03 +04:00
|
|
|
* Simple log-structured translation layer for FTLed flash drives
|
|
|
|
* like memory cards or USB sticks.
|
2013-05-09 03:23:34 +04:00
|
|
|
*
|
2013-05-18 03:45:11 +04:00
|
|
|
* (C) 2013 Vitaliy Filippov <vitalif at mail d0t ru>
|
2013-05-09 03:25:03 +04:00
|
|
|
* Redistributable under the terms of the GNU GPL 3.0+.
|
2013-05-09 03:23:34 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
|
|
|
|
#include <linux/kernel.h> /* printk() */
|
|
|
|
#include <linux/fs.h> /* everything... */
|
|
|
|
#include <linux/errno.h> /* error codes */
|
|
|
|
#include <linux/types.h> /* size_t */
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <linux/genhd.h>
|
2013-05-09 16:26:37 +04:00
|
|
|
#include <linux/idr.h>
|
2013-05-09 03:23:34 +04:00
|
|
|
#include <linux/blkdev.h>
|
|
|
|
#include <linux/hdreg.h>
|
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
#define KERNEL_SECTOR_SIZE 512
|
2013-05-09 03:23:34 +04:00
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
#define ERROR(fmt, args...) printk(KERN_ERR "sftl: " fmt "\n" , ## args)
|
|
|
|
#define INFO(fmt, args...) printk(KERN_INFO "sftl: " fmt "\n" , ## args)
|
2013-05-09 03:23:34 +04:00
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_AUTHOR("Vitaliy Filippov <vitalif@mail.ru>");
|
|
|
|
MODULE_DESCRIPTION("Log-structured translation layer for USB sticks and memory cards");
|
2013-05-09 16:26:37 +04:00
|
|
|
MODULE_VERSION("0.1");
|
2013-05-09 03:23:34 +04:00
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
static int major_num = 0;
|
2013-05-18 03:45:11 +04:00
|
|
|
|
|
|
|
const u32 magic = 0x4C544673; // Magic: sFTL
|
|
|
|
const int phy_sz = 512;
|
|
|
|
const int clust_sz = 4096;
|
|
|
|
const int clust_blocks = 4096/512;
|
|
|
|
const int seg_sz = 512/16; /* (32) clusters */
|
2013-05-09 16:26:37 +04:00
|
|
|
|
|
|
|
/* Mapping element */
|
|
|
|
struct sftl_map {
|
2013-05-18 03:45:11 +04:00
|
|
|
u32 magic, block, ver, checksum;
|
2013-05-09 16:26:37 +04:00
|
|
|
};
|
2013-05-09 03:25:03 +04:00
|
|
|
|
2013-05-18 03:45:11 +04:00
|
|
|
/* Trivial checksum using some prime number */
|
|
|
|
#define sftl_map_checksum(m) ((u32)((1+(m).block+((m).magic>>16))*(1+(m).ver+((m).magic&0xFFFF))*0xC4489EF5))
|
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
/* The internal representation of our device */
|
|
|
|
struct sftl_dev {
|
|
|
|
u32 size; // device size in blocks
|
2013-05-09 16:26:37 +04:00
|
|
|
u32 segs; // device size in segments
|
|
|
|
u32 reserved_segs;
|
2013-05-09 03:25:03 +04:00
|
|
|
u32 *map, *ver; // block mappings and versions
|
|
|
|
u32 nextblk; // next free block pointer
|
2013-05-18 03:45:11 +04:00
|
|
|
u32 freeclust, freesegs; // free block count, free segment count
|
2013-05-09 03:23:34 +04:00
|
|
|
struct gendisk *gd;
|
2013-05-09 03:25:03 +04:00
|
|
|
struct block_device *blkdev;
|
|
|
|
struct request_queue *queue;
|
|
|
|
spinlock_t spinlock;
|
|
|
|
struct mutex write_mutex;
|
|
|
|
struct list_head list;
|
|
|
|
};
|
|
|
|
|
2013-05-09 16:26:37 +04:00
|
|
|
/* Index allocator */
|
|
|
|
static DEFINE_SPINLOCK(sftl_index_lock);
|
|
|
|
static DEFINE_IDA(sftl_index_ida);
|
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
/* Our block device list, used in cleanup_module */
|
2013-05-09 16:26:37 +04:00
|
|
|
static LIST_HEAD(sftl_device_list);
|
2013-05-09 03:23:34 +04:00
|
|
|
|
2013-05-19 02:47:35 +04:00
|
|
|
static void sftl_complete_seg(struct bio *bio, int err)
|
2013-05-09 16:26:37 +04:00
|
|
|
{
|
2013-05-19 02:47:35 +04:00
|
|
|
bio_endio((struct bio *)bio->bi_private, err);
|
2013-05-09 03:23:34 +04:00
|
|
|
}
|
|
|
|
|
2013-05-19 02:47:35 +04:00
|
|
|
static void sftl_make_request(struct request_queue *q, struct bio *bio)
|
2013-05-09 16:26:37 +04:00
|
|
|
{
|
2013-05-19 02:47:35 +04:00
|
|
|
struct sftl_dev *sftl = (struct sftl_dev*)q->queuedata;
|
|
|
|
BUG_ON(bio->bi_vcnt > 1);
|
|
|
|
BUG_ON(bio->bi_sector % clust_blocks);
|
|
|
|
BUG_ON(bio->bi_size != clust_sz);
|
|
|
|
if (bio->bi_sector > sftl->size)
|
2013-05-09 16:26:37 +04:00
|
|
|
{
|
2013-05-19 02:47:35 +04:00
|
|
|
INFO("Beyond-end i/o (starting sector = %ld)", (long)bio->bi_sector);
|
|
|
|
bio_endio(bio, -EIO);
|
|
|
|
}
|
|
|
|
else if (!bio_rw(bio))
|
|
|
|
{
|
|
|
|
if (!sftl->ver[bio->bi_sector/clust_blocks])
|
2013-05-09 16:26:37 +04:00
|
|
|
{
|
2013-05-19 02:47:35 +04:00
|
|
|
// version=0 => unallocated cluster
|
|
|
|
zero_fill_bio(bio);
|
|
|
|
bio_endio(bio, 0);
|
2013-05-09 03:23:34 +04:00
|
|
|
}
|
2013-05-19 02:47:35 +04:00
|
|
|
else
|
2013-05-09 16:26:37 +04:00
|
|
|
{
|
2013-05-19 02:47:35 +04:00
|
|
|
struct block_device *bdev = sftl->blkdev;
|
|
|
|
struct request_queue *q = bdev_get_queue(bdev);
|
|
|
|
struct bio *bb = bio_alloc(GFP_KERNEL, 1);
|
|
|
|
u32 m;
|
|
|
|
if (IS_ERR(bb))
|
|
|
|
return;
|
|
|
|
bio_add_pc_page(q, bb, bio_page(bio), bio->bi_size, bio_offset(bio));
|
|
|
|
m = sftl->map[bio->bi_sector/clust_blocks];
|
|
|
|
bb->bi_sector = m/seg_sz * (seg_sz*clust_blocks + 1) + (m%seg_sz)*clust_blocks;
|
|
|
|
bb->bi_bdev = bdev;
|
|
|
|
bb->bi_private = bio;
|
|
|
|
bb->bi_end_io = sftl_complete_seg;
|
|
|
|
submit_bio(READ, bb);
|
|
|
|
if (!(bb->bi_flags & (1 << BIO_UPTODATE)))
|
|
|
|
{
|
|
|
|
bio_endio(bio, -EIO);
|
|
|
|
bio_put(bb);
|
|
|
|
}
|
2013-05-09 03:23:34 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-19 02:47:35 +04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
INFO("Write request (starting sector = %ld, count = %ld)", (long)bio->bi_sector, (long)bio_sectors(bio));
|
|
|
|
bio_endio(bio, -EIO);
|
|
|
|
}
|
2013-05-09 03:23:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The HDIO_GETGEO ioctl is handled in blkdev_ioctl(), which
|
|
|
|
* calls this. We need to implement getgeo, since we can't
|
|
|
|
* use tools such as fdisk to partition the drive otherwise.
|
|
|
|
*/
|
2013-05-09 16:26:37 +04:00
|
|
|
int sftl_getgeo(struct block_device * block_device, struct hd_geometry * geo)
|
|
|
|
{
|
|
|
|
geo->cylinders = (block_device->bd_block_size & ~0x3f) >> 6;
|
2013-05-09 03:23:34 +04:00
|
|
|
geo->heads = 4;
|
|
|
|
geo->sectors = 16;
|
|
|
|
geo->start = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The device operations structure.
|
|
|
|
*/
|
2013-05-09 03:25:03 +04:00
|
|
|
static struct block_device_operations sftl_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
2013-05-09 16:26:37 +04:00
|
|
|
.getgeo = sftl_getgeo
|
2013-05-09 03:23:34 +04:00
|
|
|
};
|
|
|
|
|
2013-05-11 01:58:59 +04:00
|
|
|
static int sftl_reg_major(void)
|
2013-05-09 03:25:03 +04:00
|
|
|
{
|
2013-05-11 01:58:59 +04:00
|
|
|
if (!major_num)
|
2013-05-09 03:25:03 +04:00
|
|
|
{
|
2013-05-11 01:58:59 +04:00
|
|
|
/* Register major number */
|
|
|
|
major_num = register_blkdev(major_num, "sftl");
|
|
|
|
if (major_num < 0)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "sftl: unable to get major number\n");
|
|
|
|
return -1;
|
|
|
|
}
|
2013-05-09 03:23:34 +04:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-05-11 01:58:59 +04:00
|
|
|
static int __init sftl_init(void)
|
|
|
|
{
|
|
|
|
return sftl_reg_major();
|
|
|
|
}
|
|
|
|
|
2013-05-09 16:26:37 +04:00
|
|
|
static void sftl_free_device(struct sftl_dev *dev)
|
|
|
|
{
|
|
|
|
if (!dev)
|
|
|
|
return;
|
|
|
|
if (dev->gd)
|
|
|
|
{
|
|
|
|
del_gendisk(dev->gd);
|
|
|
|
put_disk(dev->gd);
|
|
|
|
blk_cleanup_queue(dev->queue);
|
|
|
|
}
|
|
|
|
if (dev->blkdev)
|
|
|
|
{
|
|
|
|
invalidate_mapping_pages(dev->blkdev->bd_inode->i_mapping, 0, -1);
|
|
|
|
blkdev_put(dev->blkdev, FMODE_READ|FMODE_WRITE|FMODE_EXCL);
|
|
|
|
}
|
|
|
|
if (dev->map)
|
|
|
|
vfree(dev->map);
|
|
|
|
if (dev->ver)
|
|
|
|
vfree(dev->ver);
|
|
|
|
kfree(dev);
|
|
|
|
}
|
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
static void __exit sftl_exit(void)
|
2013-05-09 03:23:34 +04:00
|
|
|
{
|
2013-05-09 16:26:37 +04:00
|
|
|
struct list_head *pos, *next;
|
|
|
|
/* Remove all devices */
|
|
|
|
list_for_each_safe(pos, next, &sftl_device_list)
|
|
|
|
{
|
|
|
|
struct sftl_dev *dev = list_entry(pos, typeof(*dev), list);
|
|
|
|
sync_blockdev(dev->blkdev);
|
|
|
|
list_del(&dev->list);
|
|
|
|
sftl_free_device(dev);
|
|
|
|
INFO("%s: removed", dev->gd->disk_name);
|
|
|
|
}
|
2013-05-09 03:25:03 +04:00
|
|
|
unregister_blkdev(major_num, "sftl");
|
2013-05-09 03:23:34 +04:00
|
|
|
}
|
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
module_init(sftl_init);
|
|
|
|
module_exit(sftl_exit);
|
|
|
|
|
2013-05-14 13:56:45 +04:00
|
|
|
static void endFunc_tryKM2(struct bio *bb, int err)
|
|
|
|
{
|
|
|
|
if (bb->bi_private)
|
|
|
|
{
|
|
|
|
complete((struct completion*)(bb->bi_private));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-18 03:45:11 +04:00
|
|
|
static void sync_read(struct block_device *bdev, unsigned sector, void *buf, unsigned len)
|
|
|
|
{
|
|
|
|
struct bio *bb;
|
|
|
|
struct request_queue *q;
|
|
|
|
DECLARE_COMPLETION_ONSTACK(waithandle);
|
|
|
|
q = bdev_get_queue(bdev);
|
|
|
|
bb = bio_map_kern(q, buf, len, GFP_KERNEL);
|
|
|
|
if (IS_ERR(bb))
|
|
|
|
return;
|
|
|
|
bb->bi_sector = sector;
|
|
|
|
bb->bi_bdev = bdev;
|
|
|
|
bb->bi_private = &waithandle;
|
|
|
|
bb->bi_end_io = endFunc_tryKM2;
|
|
|
|
submit_bio(READ, bb);
|
|
|
|
if (!(bb->bi_flags & (1 << BIO_UPTODATE)))
|
|
|
|
{
|
|
|
|
bio_put(bb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
wait_for_completion(&waithandle);
|
|
|
|
bio_put(bb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void read_maps(struct sftl_dev *dev)
|
|
|
|
{
|
|
|
|
struct sftl_map *buf = kmalloc(phy_sz, GFP_KERNEL);
|
|
|
|
int i, j, seg;
|
|
|
|
INFO("reading translation maps");
|
|
|
|
for (i = 0; i < dev->segs; i++)
|
|
|
|
{
|
|
|
|
sync_read(dev->blkdev, i*(seg_sz*clust_blocks+1), buf, phy_sz);
|
|
|
|
for (seg = 1, j = 0; j < seg_sz; j++)
|
|
|
|
{
|
2013-05-19 02:47:35 +04:00
|
|
|
if (buf[j].magic == magic && buf[j].checksum == sftl_map_checksum(buf[j]) &&
|
|
|
|
dev->ver[i*seg_sz+j] < buf[j].ver)
|
2013-05-18 03:45:11 +04:00
|
|
|
{
|
|
|
|
dev->map[i*seg_sz+j] = buf[j].block;
|
|
|
|
dev->ver[i*seg_sz+j] = buf[j].ver;
|
|
|
|
seg = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dev->ver[i*seg_sz+j] = 0;
|
|
|
|
dev->freeclust++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dev->freesegs += seg;
|
|
|
|
}
|
|
|
|
kfree(buf);
|
|
|
|
}
|
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
static struct sftl_dev *add_device(char *devname)
|
|
|
|
{
|
|
|
|
const fmode_t mode = FMODE_READ | FMODE_WRITE | FMODE_EXCL;
|
|
|
|
struct block_device *bdev;
|
|
|
|
struct sftl_dev *dev;
|
2013-05-09 16:26:37 +04:00
|
|
|
int error, index;
|
2013-05-11 01:58:59 +04:00
|
|
|
uint64_t t;
|
|
|
|
|
|
|
|
if (!major_num)
|
|
|
|
sftl_reg_major();
|
2013-05-09 03:25:03 +04:00
|
|
|
|
|
|
|
if (!devname)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
dev = kzalloc(sizeof(struct sftl_dev), GFP_KERNEL);
|
|
|
|
if (!dev)
|
|
|
|
return NULL;
|
|
|
|
|
2013-05-09 16:26:37 +04:00
|
|
|
/* Get a handle on the underlying device */
|
2013-05-09 03:25:03 +04:00
|
|
|
bdev = blkdev_get_by_path(devname, mode, dev);
|
|
|
|
if (IS_ERR(bdev))
|
|
|
|
{
|
|
|
|
ERROR("cannot open device %s", devname);
|
|
|
|
goto devinit_err;
|
|
|
|
}
|
|
|
|
dev->blkdev = bdev;
|
|
|
|
|
|
|
|
/* if (MAJOR(bdev->bd_dev) == major_num)
|
|
|
|
{
|
2013-05-09 16:26:37 +04:00
|
|
|
ERROR("attempting to translate an SFTL device");
|
2013-05-09 03:25:03 +04:00
|
|
|
goto devinit_err;
|
|
|
|
}*/
|
|
|
|
|
|
|
|
mutex_init(&dev->write_mutex);
|
|
|
|
|
2013-05-11 01:58:59 +04:00
|
|
|
t = dev->blkdev->bd_inode->i_size;
|
|
|
|
do_div(t, clust_sz*seg_sz + phy_sz);
|
2013-05-18 03:45:11 +04:00
|
|
|
dev->segs = t;
|
2013-05-09 03:25:03 +04:00
|
|
|
dev->size = dev->segs * seg_sz * clust_blocks;
|
|
|
|
dev->reserved_segs = seg_sz * (seg_sz+1);
|
2013-05-18 03:45:11 +04:00
|
|
|
dev->map = vmalloc(sizeof(u32) * dev->size);
|
|
|
|
dev->ver = vmalloc(sizeof(u32) * dev->size);
|
2013-05-19 02:47:35 +04:00
|
|
|
memset(dev->ver, 0, sizeof(u32) * dev->size);
|
2013-05-09 03:25:03 +04:00
|
|
|
|
2013-05-09 16:26:37 +04:00
|
|
|
/* Get a request queue */
|
2013-05-09 03:25:03 +04:00
|
|
|
spin_lock_init(&dev->spinlock);
|
2013-05-19 02:47:35 +04:00
|
|
|
dev->queue = blk_alloc_queue(GFP_KERNEL);
|
|
|
|
if (!dev->queue)
|
2013-05-09 03:25:03 +04:00
|
|
|
goto devinit_err;
|
2013-05-19 02:47:35 +04:00
|
|
|
blk_queue_make_request(dev->queue, sftl_make_request);
|
2013-05-09 16:42:47 +04:00
|
|
|
dev->queue->queuedata = dev;
|
2013-05-19 02:47:35 +04:00
|
|
|
/* FIXME: It's OK when PAGE_SIZE==clust_sz==4096
|
|
|
|
but we should ALWAYS support bio of size==PAGE_SIZE */
|
|
|
|
blk_queue_max_hw_sectors(dev->queue, clust_blocks);
|
2013-05-09 03:25:03 +04:00
|
|
|
blk_queue_logical_block_size(dev->queue, clust_sz);
|
|
|
|
|
2013-05-09 16:26:37 +04:00
|
|
|
/* Allocate index for the new disk */
|
|
|
|
do {
|
|
|
|
if (!ida_pre_get(&sftl_index_ida, GFP_KERNEL))
|
|
|
|
goto devinit_err;
|
|
|
|
|
|
|
|
spin_lock(&sftl_index_lock);
|
|
|
|
error = ida_get_new(&sftl_index_ida, &index);
|
|
|
|
spin_unlock(&sftl_index_lock);
|
|
|
|
} while (error == -EAGAIN);
|
|
|
|
|
|
|
|
/* Initialise gendisk structure */
|
2013-05-09 03:25:03 +04:00
|
|
|
dev->gd = alloc_disk(16);
|
|
|
|
if (!dev->gd)
|
|
|
|
goto devinit_err;
|
|
|
|
dev->gd->major = major_num;
|
2013-05-09 16:26:37 +04:00
|
|
|
dev->gd->first_minor = index*16;
|
2013-05-09 03:25:03 +04:00
|
|
|
dev->gd->fops = &sftl_ops;
|
|
|
|
dev->gd->private_data = dev;
|
2013-05-09 16:42:47 +04:00
|
|
|
snprintf(dev->gd->disk_name, 32, "sftl%d", index);
|
2013-05-09 03:25:03 +04:00
|
|
|
set_capacity(dev->gd, dev->size);
|
|
|
|
dev->gd->queue = dev->queue;
|
|
|
|
|
2013-05-09 16:26:37 +04:00
|
|
|
/* Read maps from the device */
|
2013-05-18 03:45:11 +04:00
|
|
|
read_maps(dev);
|
|
|
|
|
|
|
|
/* Add disk */
|
|
|
|
add_disk(dev->gd);
|
2013-05-09 16:26:37 +04:00
|
|
|
|
2013-05-09 03:25:03 +04:00
|
|
|
list_add(&dev->list, &sftl_device_list);
|
2013-05-18 03:45:11 +04:00
|
|
|
INFO("%s: translating %s; %d sectors; %d free", dev->gd->disk_name, devname, dev->size, dev->freeclust*clust_blocks);
|
2013-05-09 03:25:03 +04:00
|
|
|
return dev;
|
|
|
|
|
|
|
|
devinit_err:
|
|
|
|
sftl_free_device(dev);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void kill_final_newline(char *str)
|
|
|
|
{
|
|
|
|
char *newline = strrchr(str, '\n');
|
|
|
|
if (newline && !newline[1])
|
|
|
|
*newline = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sftl_setup(const char *val, struct kernel_param *kp)
|
|
|
|
{
|
|
|
|
char buf[80 + 12];
|
|
|
|
char *str = buf;
|
|
|
|
char *token[2];
|
|
|
|
char *name;
|
2013-05-09 16:26:37 +04:00
|
|
|
int i;
|
2013-05-09 03:25:03 +04:00
|
|
|
|
|
|
|
if (strnlen(val, sizeof(buf)) >= sizeof(buf))
|
|
|
|
{
|
|
|
|
ERROR("parameter too long");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(str, val);
|
|
|
|
kill_final_newline(str);
|
|
|
|
|
|
|
|
for (i = 0; i < 1; i++)
|
|
|
|
token[i] = strsep(&str, ",");
|
|
|
|
|
|
|
|
if (str)
|
|
|
|
{
|
|
|
|
ERROR("too much parameters");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!token[0])
|
|
|
|
{
|
|
|
|
ERROR("underlying block device name missing");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
name = token[0];
|
|
|
|
if (strlen(name) + 1 > 80)
|
|
|
|
{
|
|
|
|
ERROR("device name too long");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
add_device(name);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
module_param_call(dev, sftl_setup, NULL, NULL, 0200);
|
2013-05-09 16:26:37 +04:00
|
|
|
MODULE_PARM_DESC(dev, "Block device to translate. \"dev=<dev>\"");
|