/*
* A RAM Disk device driver for the Linux Kernel which allocates a chunk of
* memory and presents it as a block device.
*
-
* Also uses file I/O functions based on some other drivers
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/timer.h>
#include <linux/types.h> /* size_t */
#include <linux/vmalloc.h> /* vmalloc() */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/hdreg.h> /* HDIO_GETGEO */
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h> /* invalidate_bdev */
#include <linux/bio.h>
#include <linux/crypto.h>
MODULE_LICENSE("GPL");
#define PREFIX KERN_DEBUG "##NEWMOD: "
/* minor number and partition management */
#define NEWMOD_MINORS 16
#define MINOR_SHIFT 4
#define DEVNUM(kdevnum) (MINOR(kdev_t_to_nr(kdevnum)) >> MINOR_SHIFT
/* Default crypto key */
#define DEFAULT_KEY "1234567890123456"
#define DEFAULT_KEY_LEN 16
/* Data file for loading/unloading data */
#define DATA_FILE "/newmod_data"
/* device major number */
static int newmod_major = 0;
module_param(newmod_major, int, 0);
/* sector size (in bytes) */
static int logical_block_size = 512;
module_param(logical_block_size, int, 0);
/* number of sectors (1024 * 512 = 524288 = ~524 kB*/
static int nsectors = 1024;
module_param(nsectors, int, 0);
/* We can tweak our hardware sector size, but the kernel talks to us
* in terms of small sectors, always. */
#define KERNEL_SECTOR_SIZE 512
/* Our request queue */
static struct request_queue *Queue;
/* the device data */
static struct newmod_device {
unsigned long size; /* device size in sectors */
u8 *data; /* the data array */
spinlock_t lock; /* for mutual exclusion */
struct gendisk *gd; /* the gendisk structure */
} Device;
/******************************** CRITICAL NOTE ********************************
*
* The crypto key must be 16 bytes long. Any shorter and crypto_cipher_setkey
* Will return error. Keylen must be 16 bytes.
*
******************************************************************************/
struct crypto_cipher *tfm;
static char *key = DEFAULT_KEY;
module_param(key, charp, 0);
static int keylen = DEFAULT_KEY_LEN;
module_param(keylen, int, 0);
/* Handle an I/O request */
static void newmod_transfer(struct newmod_device *dev, sector_t sector,
unsigned long nsect, char *buffer, int write) {
/* nsect is the number of sectors we're reading/writing,
* sector is the sector we're starting from. */
unsigned long offset = sector * logical_block_size;
unsigned long nbytes = nsect * logical_block_size;
int i, err;
/* check that we're reading within bound */
if ((offset + nbytes) > dev->size) {
printk(PREFIX "Transfer: too far. offset: %ld. nbytes: %ld.\n",
offset, nbytes);
return;
}
/* returns an error code or 0 on success */
err = crypto_cipher_setkey(tfm, key, keylen);
printk(PREFIX "setting key returned <%d>\n", err);
/*
* If we're writing, copy data from buffer into device data.
* If we're reading, copy device data into buffer.
*
* To use the crypto function calls, just copy one block at
* a time into the dextination from the source, until the
* number of transfered bytes has reached the desired amount
*/
if (write) {
printk(PREFIX "Transfer: Writing %lu bytes.\n", nbytes);
// memcpy(dev->data + offset, buffer, nbytes);
for (i = 0; i < nbytes; i += crypto_cipher_blocksize(tfm)) {
/* Using tfm, encrypt data from source and put it in
* destination. The crypto function encrypts data
* in blocks that aren't 1 byte each.
* crypto_cipher_encrypt_one returns nothing. */
crypto_cipher_encrypt_one(
tfm, /* crypto info */
dev->data + offset + i, /* destination */
buffer + i /* source */
);
}
} else {
printk(PREFIX "Transfer: Reading %lu bytes.\n", nbytes);
// memcpy(buffer, dev->data + offset, nbytes);
for (i = 0; i < nbytes; i += crypto_cipher_blocksize(tfm)) {
crypto_cipher_decrypt_one(
tfm, /* crypto data */
buffer + i, /* destination */
dev->data + offset + i /* source */
);
}
}
printk(PREFIX "Transfer: done.\n");
/* debug code */
/* {
unsigned long len;
u8 *dst;
u8 *src;
len = nbytes;
if (write) {
dst = dev->data + offset;
src = buffer;
} else {
dst = buffer;
src = dev->data + offset;
}
src = dev->data + offset;
dst = buffer;
printk("Transfer: dev->data data:\n");
len = nbytes;
while(len--)
printk("%u", (unsigned)*src++);
printk("Transfer: buffer data:\n");
len = nbytes;
while(len--)
printk("%u", (unsigned)*dst++);
printk("transferred.\n"
}
*/
/* print the data that was transferred, and from where */
// printk(PREFIX "Data in device:\n");
// for (i = 0; i < nbytes; i++)
// printk(PREFIX " Byte %d: %c\n", i, (unsigned char)(dev->data + offset + i));
// printk(PREFIX "Data in buffer:\n");
// for (i = 0; i < nbytes; i++)
// printk(PREFIX " Byte %d: %c\n", i, (unsigned char)(buffer + i));
}
/* handle a request */
static void newmod_request(struct request_queue *q) {
struct request *req;
printk(PREFIX "request: Handling requests\n");
/* fetch the request at the top of queue */
req = blk_fetch_request(q);
/* iterate through every request in the queue */
while (req != NULL) {
/* if we have no request or a request of the wrong type,
* skip it and continue */
if ((req == NULL) || (req->cmd_type != REQ_TYPE_FS)) {
printk(PREFIX "request: Received non-fs request\n");
__blk_end_request_all(req, -EIO);
req = blk_fetch_request(q);
continue;
}
/* call my data transfer function */
newmod_transfer(&Device, /* my device data */
blk_rq_pos(req), /* request sector */
blk_rq_cur_sectors(req),/* number of sectors */
req->buffer, /* buffer to fill */
rq_data_dir(req)); /* read/write? */
printk(PREFIX "request: did a transfer.\n");
/* End current request, then fetch the next one.
* __blk_end_request_cur returns 0 on success */
if (!__blk_end_request_cur(req, 0))
req = blk_fetch_request(q);
}
printk(PREFIX "request: finished all the requests.\n");
}
/*
* 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.
*/
int newmod_getgeo(struct block_device * block_device,
struct hd_geometry * geo) {
long size;
printk(PREFIX "getgeo: Making stuff up.\n");
/* make stuff up */
size = Device.size * (logical_block_size * KERNEL_SECTOR_SIZE);
geo->cylinders = (size & ~0x3f) >> 6;
geo->heads = 4;
geo->sectors = 16;
geo->start = 0;
return 0;
}
/* define the file operations */
static struct block_device_operations newmod_ops = {
.owner = THIS_MODULE,
.getgeo = newmod_getgeo
};
/* Initialize the device and announce it to the kernel */
static int __init newmod_init(void)
{
/* variables for file I/O */
mm_segment_t oldfs;
struct file *filp = NULL;
unsigned long long offset = 0;
ssize_t read_size;
int ret;
printk(PREFIX "init: start\n");
/* register the block device with the value given */
newmod_major = register_blkdev(newmod_major, "newmod");
/* register_blkdev() returns negative on failure, whether the input is
* zero or a requested major number. */
if (newmod_major < 0) {
printk(PREFIX "INIT: failed to register blockdev\n");
return -EBUSY;
}
/* setup the device. There is only one. */
memset(&Device, 0, sizeof(struct newmod_device));
Device.size = nsectors * logical_block_size;
Device.data = vmalloc(Device.size);
memset(Device.data, 0, Device.size);
/* check allocation */
if (Device.data == NULL) {
printk(PREFIX "Init: Failed to allocate Device.data\n");
unregister_blkdev(newmod_major, "newmod");
return -ENOMEM;
}
printk(PREFIX "Init: device size is %ld.\n", Device.size);
/* Now copy the device data from a file. If it doesn't exist, create
* it. */
oldfs = get_fs();
set_fs(get_ds());
filp = filp_open(DATA_FILE, O_RDONLY | O_CREAT, S_IRWXUGO);
printk(PREFIX "init: attempted to open <%s>.\n", DATA_FILE);
/* IS_ERR checks nor NULL and also error codes. If we failed to read
* the data from the previous run of this module, we can just blow it
* all away and start over with fresh data. It's not that bad */
if (IS_ERR(filp)) {
printk(PREFIX "Init: Failed to open file <%s> for reading.\n",
DATA_FILE);
set_fs(oldfs);
} else {
/* read as many bytes from the file as will fit in our data buffer */
read_size = vfs_read(filp, Device.data, Device.size, &offset);
printk(PREFIX "Init: Read from file returned %d. offset: %llu.\n",
read_size, offset);
/* close the file after we're done */
set_fs(oldfs);
ret = filp_close(filp, 0);
printk(PREFIX "Init: Closed file, returned %d.\n", ret);
}
/* initialize spinlock. Can't find the documentation. */
spin_lock_init(&Device.lock);
/* Prepare the request queue for use with this block device. The