genode/ports-foc/src/drivers/genode_block.c

302 lines
8.3 KiB
C

/*
* \brief Block driver to access Genode's block service
* \author Stefan Kalkowski <stefan.kalkowski@genode-labs.com>
* \date 2010-07-08
*/
/*
* Copyright (C) 2010-2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Linux includes */
#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/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <linux/semaphore.h>
#include <linux/interrupt.h>
/* Genode support library includes */
#include <genode/block.h>
#include <genode/string.h>
#include <l4/log/log.h>
enum Geometry {
KERNEL_SECTOR_SIZE = 512, /* sector size used by kernel */
GENODE_BLK_MINORS = 16 /* number of minor numbers */
};
/*
* The internal representation of our device.
*/
struct genode_blk_device {
unsigned blk_cnt; /* Total block count */
unsigned long blk_sz; /* Single block size */
spinlock_t lock; /* For mutual exclusion */
struct gendisk *gd; /* Generic disk structure */
struct request_queue *queue; /* The device request queue */
struct semaphore queue_wait; /* Used to block, when queue is full */
short stopped; /* Indicates queue availability */
unsigned irq; /* IRQ number */
l4_cap_idx_t irq_cap; /* IRQ capability slot */
unsigned idx; /* drive index */
};
enum { MAX_DISKS = 16 };
static struct genode_blk_device blk_devs[MAX_DISKS];
/*
* Handle an I/O request.
*/
static void genode_blk_request(struct request_queue *q)
{
struct request *req;
unsigned long queue_offset;
void *buf;
unsigned long long offset;
unsigned long nbytes;
short write;
struct genode_blk_device* dev;
while ((req = blk_fetch_request(q))) {
dev = req->rq_disk->private_data;
buf = 0;
offset = blk_rq_pos(req) * KERNEL_SECTOR_SIZE;
nbytes = blk_rq_bytes(req);
write = rq_data_dir(req) == WRITE;
if (req->cmd_type != REQ_TYPE_FS) {
printk(KERN_NOTICE "Skip non-fs request\n");
__blk_end_request_all(req, -EIO);
continue;
}
while (!buf) {
unsigned long flags;
if ((buf = genode_block_request(dev->idx, nbytes, req, &queue_offset)))
break;
/* stop_queue needs disabled interrupts */
local_irq_save(flags);
blk_stop_queue(q);
dev->stopped = 1;
/*
* This function is called with the request queue lock held, unlock to
* enable VCPU IRQs
*/
spin_unlock_irqrestore(q->queue_lock, flags);
/* block until new responses are available */
down(&dev->queue_wait);
spin_lock_irqsave(q->queue_lock, flags);
/* start_queue needs disabled interrupts */
blk_start_queue(q);
local_irq_restore(flags);
}
if (write) {
char *ptr = (char*) buf;
struct req_iterator iter;
struct bio_vec *bvec;
rq_for_each_segment(bvec, req, iter) {
void *buffer = page_address(bvec->bv_page) + bvec->bv_offset;
genode_memcpy((void*)ptr, buffer, bvec->bv_len);
ptr += bvec->bv_len;
}
}
genode_block_submit(dev->idx, queue_offset, nbytes, offset, write);
}
}
static void FASTCALL
genode_end_request(void *request, short write,
void *buf, unsigned long sz) {
struct request *req = (struct request*) request;
struct genode_blk_device *dev = req->rq_disk->private_data;
char *ptr = (char*) buf;
if (!write) {
struct req_iterator iter;
struct bio_vec *bvec;
rq_for_each_segment(bvec, req, iter) {
void *buffer = page_address(bvec->bv_page) + bvec->bv_offset;
genode_memcpy(buffer, (void*)ptr, bvec->bv_len);
ptr += bvec->bv_len;
}
}
__blk_end_request_all(req, 0);
if (dev->stopped) {
dev->stopped = 0;
up(&dev->queue_wait);
}
}
static int genode_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
struct genode_blk_device *dev = bdev->bd_disk->private_data;
unsigned long size = dev->blk_cnt * dev->blk_sz *
(dev->blk_sz / KERNEL_SECTOR_SIZE);
geo->cylinders = size >> 7;
geo->heads = 4;
geo->sectors = 32;
return 0;
}
/*
* The device operations structure.
*/
static struct block_device_operations genode_blk_ops = {
.owner = THIS_MODULE,
.getgeo = genode_blk_getgeo
};
static irqreturn_t event_interrupt(int irq, void *data)
{
unsigned long flags;
struct genode_blk_device *dev = (struct genode_blk_device *)data;
spin_lock_irqsave(dev->queue->queue_lock, flags);
genode_block_collect_responses(dev->idx);
spin_unlock_irqrestore(dev->queue->queue_lock, flags);
return IRQ_HANDLED;
}
static int __init genode_blk_init(void)
{
int err;
unsigned drive;
unsigned drive_cnt = (genode_block_count() > MAX_DISKS)
? MAX_DISKS : genode_block_count();
/**
* Loop through all Genode block devices and register them in Linux.
*/
for (drive = 0 ; drive < drive_cnt; drive++) {
int major_num;
int writeable = 0;
unsigned long req_queue_sz = 0;
/* Initialize device structure */
memset (&blk_devs[drive], 0, sizeof(struct genode_blk_device));
blk_devs[drive].idx = drive;
spin_lock_init(&blk_devs[drive].lock);
genode_block_geometry(drive, (unsigned long*)&blk_devs[drive].blk_cnt,
&blk_devs[drive].blk_sz, &writeable, &req_queue_sz);
/**
* Obtain an IRQ for the drive.
*/
blk_devs[drive].irq_cap = genode_block_irq_cap(drive);
if ((blk_devs[drive].irq = l4x_register_irq(blk_devs[drive].irq_cap)) < 0)
return -ENOMEM;
if ((err = request_irq(blk_devs[drive].irq, event_interrupt, 0,
"Genode block", &blk_devs[drive]))) {
printk(KERN_WARNING "%s: request_irq failed: %d\n", __func__, err);
return err;
}
/*
* Get a request queue.
*/
if(!(blk_devs[drive].queue = blk_init_queue(genode_blk_request,
&blk_devs[drive].lock)))
return -ENOMEM;
/*
* Align queue requests to hardware sector size.
*/
blk_queue_logical_block_size(blk_devs[drive].queue, blk_devs[drive].blk_sz);
/*
* Important, limit number of sectors per request,
* as Genode's block-session has a limited request-transmit-queue.
*/
blk_queue_max_hw_sectors(blk_devs[drive].queue, req_queue_sz / KERNEL_SECTOR_SIZE);
blk_devs[drive].queue->queuedata = &blk_devs[drive];
sema_init(&blk_devs[drive].queue_wait, 0);
blk_devs[drive].stopped = 0;
/*
* Register block device and gain major number.
*/
major_num = register_blkdev(0, genode_block_name(drive));
if(major_num < 1) {
printk(KERN_WARNING "genode_blk: unable to get major number\n");
return -EBUSY;
}
/*
* Allocate and setup generic disk structure.
*/
if(!(blk_devs[drive].gd = alloc_disk(GENODE_BLK_MINORS))) {
unregister_blkdev(major_num, genode_block_name(drive));
return -ENOMEM;
}
blk_devs[drive].gd->major = major_num;
blk_devs[drive].gd->first_minor = 0;
blk_devs[drive].gd->fops = &genode_blk_ops;
blk_devs[drive].gd->private_data = &blk_devs[drive];
blk_devs[drive].gd->queue = blk_devs[drive].queue;
strncpy(blk_devs[drive].gd->disk_name, genode_block_name(drive),
sizeof(blk_devs[drive].gd->disk_name));
set_capacity(blk_devs[drive].gd, blk_devs[drive].blk_cnt *
(blk_devs[drive].blk_sz / KERNEL_SECTOR_SIZE));
/* Set it read-only or writeable */
if (!writeable)
set_disk_ro(blk_devs[drive].gd, 1);
if (drive == 0)
genode_block_register_callback(genode_end_request);
/* Make the block device available to the system */
add_disk(blk_devs[drive].gd);
}
printk(KERN_NOTICE "Genode blk-file driver initialized\n");
return 0;
}
static void __exit
genode_blk_exit(void)
{
unsigned drive, drive_cnt = (genode_block_count() > MAX_DISKS)
? MAX_DISKS : genode_block_count();
for (drive = 0 ; drive < drive_cnt; drive++) {
del_gendisk(blk_devs[drive].gd);
put_disk(blk_devs[drive].gd);
unregister_blkdev(blk_devs[drive].gd->major, genode_block_name(drive));
blk_cleanup_queue(blk_devs[drive].queue);
}
}
module_init(genode_blk_init);
module_exit(genode_blk_exit);
MODULE_LICENSE("GPL");