Index: linux-2.6.25-source/drivers/block/cobd.c =================================================================== --- /dev/null +++ linux-2.6.25-source/drivers/block/cobd.c @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2003 Dan Aloni + * + * Cooperative Linux Block Device implementation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static int const hardsect_size = 512; +static int const hardsect_size_shift = 9; +static int const cobd_max = CO_MODULE_MAX_COBD; +static spinlock_t cobd_lock = SPIN_LOCK_UNLOCKED; + +struct cobd_device { + int unit; + int refcount; + struct block_device *device; +}; + +static struct gendisk **cobd_disks; +static struct cobd_device cobd_devs[CO_MODULE_MAX_COBD]; + +static int cobd_request(struct cobd_device *cobd, co_block_request_type_t type, co_block_request_t *out_request) +{ + co_block_request_t *request; + unsigned long flags; + long rc; + + co_passage_page_assert_valid(); + + co_passage_page_acquire(&flags); + co_passage_page->operation = CO_OPERATION_DEVICE; + co_passage_page->params[0] = CO_DEVICE_BLOCK; + co_passage_page->params[1] = cobd->unit; + request = (co_block_request_t *)&co_passage_page->params[2]; + request->type = type; + request->rc = -1; + co_switch_wrapper(); + rc = request->rc; + *out_request = *request; + co_passage_page_release(flags); + + return rc; +} + +static int cobd_stat(struct cobd_device *cobd, co_block_request_t *out_request) +{ + return cobd_request(cobd, CO_BLOCK_STAT, out_request); +} + +static int cobd_get_alias(struct cobd_device *cobd, co_block_request_t *out_request) +{ + return cobd_request(cobd, CO_BLOCK_GET_ALIAS, out_request); +} + +static int cobd_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, unsigned long arg) +{ + return -ENOTTY; /* unknown command */ +} + +static int cobd_open(struct inode *inode, struct file *file) +{ + struct cobd_device *cobd = (struct cobd_device *)(inode->i_bdev->bd_disk->private_data); + co_block_request_t *co_request; + co_block_request_t stat_request; + unsigned long flags; + int result; + + if (cobd->device && cobd->device != inode->i_bdev) + return -EBUSY; + + if (cobd->refcount == 0) { + if (cobd_stat(cobd, &stat_request)) { + return -ENODEV; + } + } + + result = 0; + + co_passage_page_assert_valid(); + + co_passage_page_acquire(&flags); + co_passage_page->operation = CO_OPERATION_DEVICE; + co_passage_page->params[0] = CO_DEVICE_BLOCK; + co_passage_page->params[1] = cobd->unit; + co_request = (co_block_request_t *)&co_passage_page->params[2]; + co_request->type = CO_BLOCK_OPEN; + co_switch_wrapper(); + if (co_request->rc) + result = -EIO; + else + cobd->refcount++; + co_passage_page_release(flags); + + if (result) + return result; + + if (cobd->refcount == 1) { + set_capacity(inode->i_bdev->bd_disk, stat_request.disk_size >> hardsect_size_shift); + cobd->device = inode->i_bdev; + } + + return 0; +} + +static int cobd_release(struct inode *inode, struct file *file) +{ + struct cobd_device *cobd = (struct cobd_device *)(inode->i_bdev->bd_disk->private_data); + co_block_request_t *co_request; + unsigned long flags; + int ret = 0; + + co_passage_page_assert_valid(); + + co_passage_page_acquire(&flags); + co_passage_page->operation = CO_OPERATION_DEVICE; + co_passage_page->params[0] = CO_DEVICE_BLOCK; + co_passage_page->params[1] = cobd->unit; + co_request = (co_block_request_t *)&co_passage_page->params[2]; + co_request->type = CO_BLOCK_CLOSE; + co_switch_wrapper(); + if (co_request->rc) + ret = -EIO; + cobd->refcount--; + co_passage_page_release(flags); + + if (cobd->refcount == 0) + cobd->device = NULL; + + return ret; +} + +/* + * Handle an I/O request. + */ +static int cobd_transfer(struct cobd_device *cobd, struct request *req, int *async) +{ + co_block_request_t *co_request; + unsigned long flags; + int ret; + + co_passage_page_assert_valid(); + + co_passage_page_acquire(&flags); + co_passage_page->operation = CO_OPERATION_DEVICE; + co_passage_page->params[0] = CO_DEVICE_BLOCK; + co_passage_page->params[1] = cobd->unit; + co_request = (co_block_request_t *)&co_passage_page->params[2]; + co_request->type = (rq_data_dir(req) == READ) ? CO_BLOCK_READ : CO_BLOCK_WRITE; + co_request->irq_request = req; + co_request->offset = ((unsigned long long)(req->sector)) << hardsect_size_shift; + co_request->size = req->current_nr_sectors << hardsect_size_shift; + co_request->address = req->buffer; + co_request->rc = 0; + co_request->async = 0; + co_switch_wrapper(); + *async = co_request->async; + ret = co_request->rc; + + co_passage_page_release(flags); + return ret; +} + +static void do_cobd_request(request_queue_t *q) +{ + struct request *req; + struct cobd_device *cobd; + + while ((req = elv_next_request(q)) != NULL) { + int ret; + int async; + + if (!blk_fs_request(req)) { + end_request(req, 0); + continue; + } + cobd = (struct cobd_device *)(req->rq_disk->private_data); + + ret = cobd_transfer(cobd, req, &async); + + /* + * OK: ret == 0 --> uptodate = 1 + * FAIL: ret == -1 --> uptodate = 0 + */ + if (ret == CO_BLOCK_REQUEST_RETCODE_OK) { + if (async) + break; /* wait for interrupt */ + end_request(req, 1); + } else { + end_request(req, 0); + } + } +} + +static irqreturn_t cobd_interrupt(int irq, void *dev_id) +{ + co_message_node_t *input; + + while (co_get_message(&input, CO_DEVICE_BLOCK)) { + co_linux_message_t *message; + co_block_intr_t *intr; + struct request *req; + + message = (co_linux_message_t *)&input->msg.data; + if (message->unit >= CO_MODULE_MAX_COBD) { + printk("cobd interrupt: buggy unit reception: %x\n", message->unit); + goto goto_next_message; + } + + BUG_ON(message->size != sizeof(co_block_intr_t)); + intr = (co_block_intr_t *)message->data; + req = intr->irq_request; + BUG_ON(!req); + + spin_lock(&cobd_lock); + end_request(req, intr->uptodate); + do_cobd_request(req->q); + spin_unlock(&cobd_lock); + +goto_next_message: + co_free_message(input); + } + + return IRQ_HANDLED; +} + +static struct block_device_operations cobd_fops = { + .owner = THIS_MODULE, + .open = cobd_open, + .release = cobd_release, + .ioctl = cobd_ioctl, +}; + +static int __init cobd_drives_init(void) +{ + int result, i; + + if (request_irq(BLOCKDEV_IRQ, &cobd_interrupt, 0, "cobd", NULL)) { + printk("cobd: unable to get IRQ%d\n", BLOCKDEV_IRQ); + return -EBUSY; + } + + if (register_blkdev(COLINUX_MAJOR, "cobd")) { + printk(KERN_WARNING "Unable to get major number %d for cobd device\n", COLINUX_MAJOR); + result = -EIO; + goto fail_irq; + } + + result = -ENOMEM; /* for the possible errors */ + + cobd_disks = kmalloc(cobd_max * sizeof(struct gendisk *), GFP_KERNEL); + if (!cobd_disks) + goto fail_malloc; + + for (i=0; i < cobd_max; i++) { + cobd_disks[i] = alloc_disk(1); + if (!cobd_disks[i]) + goto fail_malloc3; + } + + for (i=0; i < cobd_max; i++) { + struct cobd_device *cobd = &cobd_devs[i]; + struct gendisk *disk = cobd_disks[i]; + + disk->queue = blk_init_queue(do_cobd_request, &cobd_lock); + if (!disk->queue) + goto fail_malloc4; + + blk_queue_hardsect_size(disk->queue, hardsect_size); + + cobd->unit = i; + disk->major = COLINUX_MAJOR; + disk->first_minor = i; + disk->fops = &cobd_fops; + sprintf(disk->disk_name, "cobd%d", i); + disk->private_data = cobd; + } + + for (i=0; i < cobd_max; i++) + add_disk(cobd_disks[i]); + + printk(KERN_INFO "cobd: loaded (max %d devices)\n", cobd_max); + return 0; + +/* error path */ +fail_malloc4: + while (i--) + blk_cleanup_queue(cobd_disks[i]->queue); + i = cobd_max; + +fail_malloc3: + while (i--) + if (cobd_disks[i] != NULL) + put_disk(cobd_disks[i]); + + kfree(cobd_disks); + +fail_malloc: + if (unregister_blkdev(COLINUX_MAJOR, "cobd")) + printk(KERN_WARNING "cobd: cannot unregister blkdev\n"); + +fail_irq: + free_irq(BLOCKDEV_IRQ, NULL); + return result; +} + +struct cobd_alias_major { + const char *name; + int registered; + int number; +}; + +struct cobd_alias { + const char *name; + struct cobd_alias_major *major; + int minor_start; + int minor_count; + struct gendisk **gendisk; +}; + +struct cobd_alias_major cobd_aliases_major_ide0 = { + .name = "ide0", + .number = IDE0_MAJOR, +}; + +struct cobd_alias_major cobd_aliases_major_ide1 = { + .name = "ide1", + .number = IDE1_MAJOR, +}; + +struct cobd_alias_major cobd_aliases_major_ide2 = { + .name = "ide2", + .number = IDE2_MAJOR, +}; + +struct cobd_alias_major cobd_aliases_major_ide3 = { + .name = "ide3", + .number = IDE3_MAJOR, +}; + +struct cobd_alias_major cobd_aliases_major_sd = { + .name = "sd", + .number = SCSI_DISK0_MAJOR, +}; + +struct cobd_alias cobd_aliases[] = { + {"hda", &cobd_aliases_major_ide0, 0x00, 21, }, + {"hdb", &cobd_aliases_major_ide0, 0x40, 21, }, + {"hdc", &cobd_aliases_major_ide1, 0x00, 21, }, + {"hdd", &cobd_aliases_major_ide1, 0x40, 21, }, + {"hde", &cobd_aliases_major_ide2, 0x00, 21, }, + {"hdf", &cobd_aliases_major_ide2, 0x40, 21, }, + {"hdg", &cobd_aliases_major_ide3, 0x00, 21, }, + {"hdh", &cobd_aliases_major_ide3, 0x40, 21, }, + {"sda", &cobd_aliases_major_sd, 0x00, 0x10, }, + {"sdb", &cobd_aliases_major_sd, 0x10, 0x10, }, + {"sdc", &cobd_aliases_major_sd, 0x20, 0x10, }, + {"sdd", &cobd_aliases_major_sd, 0x30, 0x10, }, + {"sde", &cobd_aliases_major_sd, 0x40, 0x10, }, + {"sdf", &cobd_aliases_major_sd, 0x50, 0x10, }, + {"sdg", &cobd_aliases_major_sd, 0x60, 0x10, }, + {"sdh", &cobd_aliases_major_sd, 0x70, 0x10, }, + {"sdi", &cobd_aliases_major_sd, 0x80, 0x10, }, + {"sdj", &cobd_aliases_major_sd, 0x90, 0x10, }, + {"sdk", &cobd_aliases_major_sd, 0xa0, 0x10, }, + {"sdl", &cobd_aliases_major_sd, 0xb0, 0x10, }, + {"sdm", &cobd_aliases_major_sd, 0xc0, 0x10, }, + {"sdn", &cobd_aliases_major_sd, 0xd0, 0x10, }, + {"sdo", &cobd_aliases_major_sd, 0xe0, 0x10, }, + {"sdp", &cobd_aliases_major_sd, 0xf0, 0x10, }, + {NULL, }, +}; + +static int __init skip_atoi(const char **s) +{ + /* lib/spprintf.h */ + + int i=0; + + while (isdigit(**s)) + i = i*10 + *((*s)++) - '0'; + + return i; +} + +static int __init cobd_spawn_alias(struct cobd_alias *alias, + const char *alias_name_requested, + int cobd_unit) +{ + const char *index_str_start = &alias_name_requested[strlen(alias->name)]; + const char *index_str_end = index_str_start; + struct cobd_device *cobd; + struct gendisk *disk; + + int index = skip_atoi(&index_str_end); + + if (!((index >= 0) && (index <= alias->minor_count))) { + printk(KERN_WARNING "index out of bounds for alias %s (1 - %d)\n", + alias_name_requested, alias->minor_count); + return -1; + } + + if (alias->gendisk == NULL) { + static struct gendisk **gendisks; + + gendisks = kzalloc(alias->minor_count * sizeof(struct gendisk *), GFP_KERNEL); + if (!gendisks) { + printk(KERN_WARNING "cannot allocate gendisk array for %s\n", alias->name); + return -ENOMEM; + } + + if (!alias->major->registered) { + if (register_blkdev(alias->major->number, alias->major->name)) { + printk(KERN_WARNING "unable to get major number %d for cobd alias device %s\n", + alias->major->number, alias_name_requested); + kfree(gendisks); + return -EIO; + } + + alias->major->registered = 1; + } + + alias->gendisk = gendisks; + } + + if (alias->gendisk[index] != NULL) { + printk(KERN_WARNING "alias %s already used\n", alias_name_requested); + return -1; + } + + disk = alloc_disk(1); + if (!disk) { + printk(KERN_WARNING "cannot allocate disk for alias %s\n", alias_name_requested); + return -1; + } + + disk->queue = blk_init_queue(do_cobd_request, &cobd_lock); + if (!disk->queue) { + printk(KERN_WARNING "cannot allocate init queue for alias %s\n", alias_name_requested); + put_disk(disk); + return -1; + } + + cobd = &cobd_devs[cobd_unit]; + blk_queue_hardsect_size(disk->queue, hardsect_size); + disk->major = alias->major->number; + disk->first_minor = alias->minor_start + index; + disk->fops = &cobd_fops; + if (index) + sprintf(disk->disk_name, "%s%d", alias->name, index); + else + sprintf(disk->disk_name, "%s", alias->name); + disk->private_data = cobd; + add_disk(disk); + alias->gendisk[index] = disk; + + printk("cobd alias cobd%d -> %s created\n", cobd_unit, alias_name_requested); + + return 0; +} + +static void __init cobd_aliases_init(void) +{ + int unit; + co_block_request_t request; + + for (unit=0; unit < cobd_max; unit++) { + struct cobd_alias *alias = cobd_aliases; + int result = cobd_get_alias(&cobd_devs[unit], &request); + if (result) + continue; + + printk("alias for cobd%d is %s\n", unit, request.alias); + + while (alias->name) { + const char *match = (strstr(request.alias, alias->name)); + if (match == request.alias) { + cobd_spawn_alias(alias, request.alias, unit); + break; + } + alias++; + } + + if (alias->name == NULL) + printk("alias %s is unknown (see cobd_aliases in cobd.c)\n", request.alias); + } +} + +static void cobd_drives_exit(void) +{ + int i; + + for (i = 0; i < cobd_max; i++) { + blk_cleanup_queue(cobd_disks[i]->queue); + del_gendisk(cobd_disks[i]); + put_disk(cobd_disks[i]); + } + + if (unregister_blkdev(COLINUX_MAJOR, "cobd")) + printk(KERN_WARNING "cobd: cannot unregister blkdev\n"); + + free_irq(BLOCKDEV_IRQ, NULL); + kfree(cobd_disks); +} + +static void cobd_aliases_exit(void) +{ + struct cobd_alias *alias = &cobd_aliases[0]; + while (alias->name != NULL) { + int index; + if (alias->gendisk == NULL) { + alias++; + continue; + } + + for (index=0; index < alias->minor_count; index++) { + struct gendisk *disk = alias->gendisk[index]; + if (!disk) + return; + + blk_cleanup_queue(disk->queue); + del_gendisk(disk); + put_disk(disk); + } + + if (!alias->major->registered) { + unregister_blkdev(alias->major->number, alias->major->name); + alias->major->registered = 0; + } + kfree(alias->gendisk); + + alias++; + } +} + +static int __init cobd_init(void) +{ + int result = cobd_drives_init(); + if (result) + return result; + + cobd_aliases_init(); + + return result; +} + +static void cobd_exit(void) +{ + cobd_aliases_exit(); + cobd_drives_exit(); +} + +module_init(cobd_init); +module_exit(cobd_exit); + +