/*
* (C)
www.starterkit.ru
* Redistributable under the terms of the GNU GPL.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <linux/list.h>
#define SECTOR_SIZE 512
#define RAM_SECTORS (32752 + 8)
#define DISK_SECTORS (8 * 1024 * 1024 * 2)
#define RSECTORS 2048
static int rdisk_major = 0;
static struct request_queue *Queue;
struct rdisk_sector {
struct list_head list;
sector_t sector;
char data[SECTOR_SIZE];
};
static struct rdisk_dev {
struct list_head head;
struct list_head pool;
struct rdisk_sector *rsectors;
spinlock_t lock;
char *ram;
struct gendisk *gd;
} Device;
void rdisk_write_sector(struct rdisk_dev *dev, sector_t sector, char *buff)
{
struct rdisk_sector *entry;
if (!list_empty(&dev->pool))
entry = list_entry(dev->pool.next, struct rdisk_sector, list);
else
entry = list_entry(dev->head.next, struct rdisk_sector, list);
entry->sector = sector;
memcpy(entry->data, buff, SECTOR_SIZE);
list_move_tail(&entry->list, &dev->head);
}
void rdisk_read_sector(struct rdisk_dev *dev, sector_t sector, char *buff)
{
struct rdisk_sector *entry;
list_for_each_entry(entry, &dev->head, list) {
if (entry->sector == sector) {
memcpy(buff, entry->data, SECTOR_SIZE);
list_move(&entry->list, &dev->pool);
return;
}
}
}
static int rdisk_transfer(struct rdisk_dev *dev, sector_t sector,
unsigned long nsect, char *buffer, int write)
{
if (sector >= RAM_SECTORS) {
unsigned long s;
if (write)
for (s = 0; s < nsect; s++)
rdisk_write_sector(dev, sector + s, buffer + SECTOR_SIZE * s);
else
for (s = 0; s < nsect; s++)
rdisk_read_sector(dev, sector + s, buffer + SECTOR_SIZE * s);
} else {
unsigned long offset = sector * SECTOR_SIZE;
unsigned long nbytes = nsect * SECTOR_SIZE;
if (write)
memcpy(dev->ram + offset, buffer, nbytes);
else
memcpy(buffer, dev->ram + offset, nbytes);
}
return 0;
}
static void rdisk_request(struct request_queue *q)
{
struct request *req;
req = blk_fetch_request(q);
while (req != NULL) {
if (req == NULL || (req->cmd_type != REQ_TYPE_FS)) {
printk (KERN_NOTICE "Skip non-CMD request\n");
__blk_end_request_all(req, -EIO);
continue;
}
if (rdisk_transfer(&Device, blk_rq_pos(req), blk_rq_cur_sectors(req),
req->buffer, rq_data_dir(req)))
__blk_end_request_cur(req, -EIO);
if ( ! __blk_end_request_cur(req, 0)) {
req = blk_fetch_request(q);
}
}
}
static struct block_device_operations rdisk_ops = {
.owner = THIS_MODULE,
};
static int __init rdisk_init(void)
{
int i;
Device.ram = vmalloc(RAM_SECTORS * SECTOR_SIZE);
if (Device.ram == NULL)
goto out;
Device.rsectors = vmalloc(RSECTORS * sizeof (struct rdisk_sector));
if (Device.rsectors == NULL)
goto out_vfree_ram;
INIT_LIST_HEAD(&Device.head);
INIT_LIST_HEAD(&Device.pool);
for (i = 0; i < RSECTORS; i++)
list_add(&Device.rsectors[i].list, &Device.pool);
spin_lock_init(&Device.lock);
Queue = blk_init_queue(rdisk_request, &Device.lock);
if (Queue == NULL)
goto out_vfree_all;
blk_queue_logical_block_size(Queue, SECTOR_SIZE);
rdisk_major = register_blkdev(rdisk_major, "rdisk");
if (rdisk_major < 0) {
printk(KERN_WARNING "rdisk: unable to get major number\n");
goto out_vfree_all;
}
Device.gd = alloc_disk(1);
if (!Device.gd)
goto out_unregister;
Device.gd->major = rdisk_major;
Device.gd->first_minor = 0;
Device.gd->fops = &rdisk_ops;
Device.gd->private_data = &Device;
strcpy(Device.gd->disk_name, "rdisk0");
set_capacity(Device.gd, DISK_SECTORS);
Device.gd->queue = Queue;
add_disk(Device.gd);
return 0;
out_unregister:
unregister_blkdev(rdisk_major, "rdisk");
out_vfree_all:
vfree(Device.rsectors);
out_vfree_ram:
vfree(Device.ram);
out:
return -ENOMEM;
}
static void __exit rdisk_exit(void)
{
del_gendisk(Device.gd);
put_disk(Device.gd);
unregister_blkdev(rdisk_major, "rdisk");
blk_cleanup_queue(Queue);
vfree(Device.rsectors);
vfree(Device.ram);
}
module_init(rdisk_init);
module_exit(rdisk_exit);
MODULE_LICENSE("GPL");