/* * \brief Block driver to access Genode's block service * \author Stefan Kalkowski * \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 #include #include #include #include #include #include #include #include #include #include #include /* Genode support library includes */ #include #include #include 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");