sftl/sftl.c

328 lines
7.4 KiB
C
Raw Normal View History

/*
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:25:03 +04:00
* (C) 2013 Vitaliy Filippov <vitalif at mail dot ru>
* Redistributable under the terms of the GNU GPL 3.0+.
*/
#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>
#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: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: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:25:03 +04:00
static int major_num = 0;
static int phy_sz = 512;
static int clust_sz = 4096;
2013-05-09 16:26:37 +04:00
static int clust_blocks = 4096/512;
static int seg_sz = 512/16; /* in blocks */
/* Mapping element */
struct sftl_map {
u32 magic, blk, ver, crc;
};
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
u32 freeblks, freesegs; // free block count, free segment count
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);
/*
* Handle an I/O request.
*/
2013-05-09 16:26:37 +04:00
static void sftl_transfer(struct sftl_dev *dev, sector_t sector,
unsigned long nsect, char *buffer, int write)
{
unsigned long offset = sector * clust_blocks;
unsigned long nblocks = nsect * clust_blocks;
2013-05-09 16:26:37 +04:00
if ((offset + nblocks) > dev->size)
{
2013-05-11 01:58:59 +04:00
INFO("Beyond-end write (starting sector = %ld, count = %ld)", offset, nblocks);
return;
}
2013-05-09 16:26:37 +04:00
}
2013-05-09 16:26:37 +04:00
static void sftl_request(struct request_queue *q)
{
struct request *req;
req = blk_fetch_request(q);
2013-05-09 16:26:37 +04:00
while (req != NULL)
{
// blk_fs_request() was removed in 2.6.36 - many thanks to
// Christian Paro for the heads up and fix...
//if (!blk_fs_request(req)) {
2013-05-09 16:26:37 +04:00
if (req == NULL || (req->cmd_type != REQ_TYPE_FS))
{
printk (KERN_NOTICE "Skip non-CMD request\n");
__blk_end_request_all(req, -EIO);
continue;
}
2013-05-09 16:42:47 +04:00
sftl_transfer((struct sftl_dev *)q->queuedata, blk_rq_pos(req),
blk_rq_cur_sectors(req), req->buffer, rq_data_dir(req));
2013-05-09 16:26:37 +04:00
if (!__blk_end_request_cur(req, 0))
{
req = blk_fetch_request(q);
}
}
}
/*
* 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;
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-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;
}
}
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 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:25:03 +04:00
module_init(sftl_init);
module_exit(sftl_exit);
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);
dev->segs = (uint32_t)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-09 16:26:37 +04:00
/* Get a request queue */
2013-05-09 03:25:03 +04:00
spin_lock_init(&dev->spinlock);
dev->queue = blk_init_queue(sftl_request, &dev->spinlock);
if (dev->queue == NULL)
goto devinit_err;
2013-05-09 16:42:47 +04:00
dev->queue->queuedata = dev;
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-11 01:58:59 +04:00
INFO("gd = %d %d %d", dev->gd->minors, dev->gd->major, dev->gd->first_minor);
2013-05-09 03:25:03 +04:00
add_disk(dev->gd);
2013-05-09 16:26:37 +04:00
/* Read maps from the device */
2013-05-09 03:25:03 +04:00
list_add(&dev->list, &sftl_device_list);
2013-05-11 01:58:59 +04:00
INFO("%s: translating %s; %d sectors", dev->gd->disk_name, devname, dev->size);
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>\"");