* Modifications by Mark A. Williamson are (c) Intel Research Cambridge
* Copyright (c) 2004-2005, Christian Limpach
*
- * This file may be distributed separately from the Linux kernel, or
- * incorporated into other software packages, subject to the following license:
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation; or, when distributed
+ * separately from the Linux kernel or incorporated into other
+ * software packages, subject to the following license:
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this source file (the "Software"), to deal in the Software without
#include <linux/blkdev.h>
#include <linux/list.h>
+#define BLKIF_MAJOR(dev) ((dev)>>8)
+#define BLKIF_MINOR(dev) ((dev) & 0xff)
+
/*
* For convenience we distinguish between ide, scsi and 'other' (i.e.,
* potentially combinations of the two) in the naming scheme and in a few other
#define NUM_SCSI_MAJORS 9
#define NUM_VBD_MAJORS 1
-struct lvdisk
-{
- blkif_sector_t capacity; /* 0: Size in terms of 512-byte sectors. */
- blkif_vdev_t device; /* 8: Device number (opaque 16 bit value). */
- u16 info;
- struct list_head list;
-};
-
static struct xlbd_type_info xlbd_ide_type = {
- .partn_shift = 6,
- .partn_per_major = 2,
- .devname = "ide",
- .diskname = "hd",
+ .partn_shift = 6,
+ .disks_per_major = 2,
+ .devname = "ide",
+ .diskname = "hd",
};
static struct xlbd_type_info xlbd_scsi_type = {
- .partn_shift = 4,
- .partn_per_major = 16,
- .devname = "sd",
- .diskname = "sd",
+ .partn_shift = 4,
+ .disks_per_major = 16,
+ .devname = "sd",
+ .diskname = "sd",
};
static struct xlbd_type_info xlbd_vbd_type = {
- .partn_shift = 4,
- .partn_per_major = 16,
- .devname = "xvd",
- .diskname = "xvd",
+ .partn_shift = 4,
+ .disks_per_major = 16,
+ .devname = "xvd",
+ .diskname = "xvd",
};
static struct xlbd_major_info *major_info[NUM_IDE_MAJORS + NUM_SCSI_MAJORS +
- NUM_VBD_MAJORS];
+ NUM_VBD_MAJORS];
-#define XLBD_MAJOR_IDE_START 0
-#define XLBD_MAJOR_SCSI_START (NUM_IDE_MAJORS)
-#define XLBD_MAJOR_VBD_START (NUM_IDE_MAJORS + NUM_SCSI_MAJORS)
+#define XLBD_MAJOR_IDE_START 0
+#define XLBD_MAJOR_SCSI_START (NUM_IDE_MAJORS)
+#define XLBD_MAJOR_VBD_START (NUM_IDE_MAJORS + NUM_SCSI_MAJORS)
-#define XLBD_MAJOR_IDE_RANGE XLBD_MAJOR_IDE_START ... XLBD_MAJOR_SCSI_START - 1
-#define XLBD_MAJOR_SCSI_RANGE XLBD_MAJOR_SCSI_START ... XLBD_MAJOR_VBD_START - 1
-#define XLBD_MAJOR_VBD_RANGE XLBD_MAJOR_VBD_START ... XLBD_MAJOR_VBD_START + NUM_VBD_MAJORS - 1
+#define XLBD_MAJOR_IDE_RANGE XLBD_MAJOR_IDE_START ... XLBD_MAJOR_SCSI_START - 1
+#define XLBD_MAJOR_SCSI_RANGE XLBD_MAJOR_SCSI_START ... XLBD_MAJOR_VBD_START - 1
+#define XLBD_MAJOR_VBD_RANGE XLBD_MAJOR_VBD_START ... XLBD_MAJOR_VBD_START + NUM_VBD_MAJORS - 1
/* Information about our VBDs. */
#define MAX_VBDS 64
-struct list_head vbds_list;
+static LIST_HEAD(vbds_list);
-struct request_queue *xlbd_blk_queue = NULL;
-
-#define MAJOR_XEN(dev) ((dev)>>8)
-#define MINOR_XEN(dev) ((dev) & 0xff)
-
-static struct block_device_operations xlvbd_block_fops =
+static struct block_device_operations xlvbd_block_fops =
{
- .owner = THIS_MODULE,
- .open = blkif_open,
- .release = blkif_release,
- .ioctl = blkif_ioctl,
+ .owner = THIS_MODULE,
+ .open = blkif_open,
+ .release = blkif_release,
+ .ioctl = blkif_ioctl,
+ .getgeo = blkif_getgeo
};
-spinlock_t blkif_io_lock = SPIN_LOCK_UNLOCKED;
+DEFINE_SPINLOCK(blkif_io_lock);
-static struct lvdisk * xlvbd_device_alloc(void)
+static struct xlbd_major_info *
+xlbd_alloc_major_info(int major, int minor, int index)
{
- struct lvdisk *ret;
-
- ret = kmalloc(sizeof(struct lvdisk), GFP_KERNEL);
- if ( ret ) {
- memset(ret, '\0', sizeof(struct lvdisk));
- INIT_LIST_HEAD(&ret->list);
- }
- return ret;
+ struct xlbd_major_info *ptr;
+
+ ptr = kzalloc(sizeof(struct xlbd_major_info), GFP_KERNEL);
+ if (ptr == NULL)
+ return NULL;
+
+ ptr->major = major;
+
+ switch (index) {
+ case XLBD_MAJOR_IDE_RANGE:
+ ptr->type = &xlbd_ide_type;
+ ptr->index = index - XLBD_MAJOR_IDE_START;
+ break;
+ case XLBD_MAJOR_SCSI_RANGE:
+ ptr->type = &xlbd_scsi_type;
+ ptr->index = index - XLBD_MAJOR_SCSI_START;
+ break;
+ case XLBD_MAJOR_VBD_RANGE:
+ ptr->type = &xlbd_vbd_type;
+ ptr->index = index - XLBD_MAJOR_VBD_START;
+ break;
+ }
+
+ printk("Registering block device major %i\n", ptr->major);
+ if (register_blkdev(ptr->major, ptr->type->devname)) {
+ WPRINTK("can't get major %d with name %s\n",
+ ptr->major, ptr->type->devname);
+ kfree(ptr);
+ return NULL;
+ }
+
+ devfs_mk_dir(ptr->type->devname);
+ major_info[index] = ptr;
+ return ptr;
}
-static void xlvbd_device_free(struct lvdisk *disk)
+static struct xlbd_major_info *
+xlbd_get_major_info(int vdevice)
{
- list_del(&disk->list);
- kfree(disk);
+ struct xlbd_major_info *mi;
+ int major, minor, index;
+
+ major = BLKIF_MAJOR(vdevice);
+ minor = BLKIF_MINOR(vdevice);
+
+ switch (major) {
+ case IDE0_MAJOR: index = 0; break;
+ case IDE1_MAJOR: index = 1; break;
+ case IDE2_MAJOR: index = 2; break;
+ case IDE3_MAJOR: index = 3; break;
+ case IDE4_MAJOR: index = 4; break;
+ case IDE5_MAJOR: index = 5; break;
+ case IDE6_MAJOR: index = 6; break;
+ case IDE7_MAJOR: index = 7; break;
+ case IDE8_MAJOR: index = 8; break;
+ case IDE9_MAJOR: index = 9; break;
+ case SCSI_DISK0_MAJOR: index = 10; break;
+ case SCSI_DISK1_MAJOR ... SCSI_DISK7_MAJOR:
+ index = 11 + major - SCSI_DISK1_MAJOR;
+ break;
+ case SCSI_CDROM_MAJOR: index = 18; break;
+ default: index = 19; break;
+ }
+
+ mi = ((major_info[index] != NULL) ? major_info[index] :
+ xlbd_alloc_major_info(major, minor, index));
+ if (mi)
+ mi->usage++;
+ return mi;
}
-static vdisk_t * xlvbd_probe(int *ret)
+static void
+xlbd_put_major_info(struct xlbd_major_info *mi)
{
- blkif_response_t rsp;
- blkif_request_t req;
- vdisk_t *disk_info = NULL;
- unsigned long buf;
- int nr;
-
- buf = __get_free_page(GFP_KERNEL);
- if ( !buf )
- goto out;
-
- memset(&req, 0, sizeof(req));
- req.operation = BLKIF_OP_PROBE;
- req.nr_segments = 1;
-#ifdef CONFIG_XEN_BLKDEV_GRANT
- blkif_control_probe_send(&req, &rsp,
- (unsigned long)(virt_to_machine(buf)));
-#else
- req.frame_and_sects[0] = virt_to_machine(buf) | 7;
-
- blkif_control_send(&req, &rsp);
-#endif
- if ( rsp.status <= 0 ) {
- printk(KERN_ALERT "Could not probe disks (%d)\n", rsp.status);
- goto out;
- }
- nr = rsp.status;
- if ( nr > MAX_VBDS )
- nr = MAX_VBDS;
-
- disk_info = kmalloc(nr * sizeof(vdisk_t), GFP_KERNEL);
- if ( disk_info )
- memcpy(disk_info, (void *) buf, nr * sizeof(vdisk_t));
- if ( ret )
- *ret = nr;
-out:
- free_page(buf);
- return disk_info;
+ mi->usage--;
+ /* XXX: release major if 0 */
}
-static struct xlbd_major_info *xlbd_alloc_major_info(int major, int minor, int index)
+static int
+xlvbd_init_blk_queue(struct gendisk *gd, u16 sector_size)
{
- struct xlbd_major_info *ptr;
-
- ptr = kmalloc(sizeof(struct xlbd_major_info), GFP_KERNEL);
- if ( !ptr )
- return NULL;
-
- memset(ptr, 0, sizeof(struct xlbd_major_info));
-
- ptr->major = major;
-
- switch (index) {
- case XLBD_MAJOR_IDE_RANGE:
- ptr->type = &xlbd_ide_type;
- ptr->index = index - XLBD_MAJOR_IDE_START;
- break;
- case XLBD_MAJOR_SCSI_RANGE:
- ptr->type = &xlbd_scsi_type;
- ptr->index = index - XLBD_MAJOR_SCSI_START;
- break;
- case XLBD_MAJOR_VBD_RANGE:
- ptr->type = &xlbd_vbd_type;
- ptr->index = index - XLBD_MAJOR_VBD_START;
- break;
- }
-
- if ( register_blkdev(ptr->major, ptr->type->devname) ) {
- printk(KERN_ALERT "XL VBD: can't get major %d with name %s\n",
- ptr->major, ptr->type->devname);
- kfree(ptr);
- return NULL;
- }
-
- devfs_mk_dir(ptr->type->devname);
- major_info[index] = ptr;
- return ptr;
-}
+ request_queue_t *rq;
-static struct xlbd_major_info *xlbd_get_major_info(int device)
-{
- int major, minor, index;
-
- major = MAJOR_XEN(device);
- minor = MINOR_XEN(device);
-
- switch (major) {
- case IDE0_MAJOR: index = 0; break;
- case IDE1_MAJOR: index = 1; break;
- case IDE2_MAJOR: index = 2; break;
- case IDE3_MAJOR: index = 3; break;
- case IDE4_MAJOR: index = 4; break;
- case IDE5_MAJOR: index = 5; break;
- case IDE6_MAJOR: index = 6; break;
- case IDE7_MAJOR: index = 7; break;
- case IDE8_MAJOR: index = 8; break;
- case IDE9_MAJOR: index = 9; break;
- case SCSI_DISK0_MAJOR: index = 10; break;
- case SCSI_DISK1_MAJOR ... SCSI_DISK7_MAJOR:
- index = 11 + major - SCSI_DISK1_MAJOR;
- break;
- case SCSI_CDROM_MAJOR: index = 18; break;
- default: index = 19; break;
- }
-
- return major_info[index]
- ? major_info[index]
- : xlbd_alloc_major_info(major, minor, index);
-}
+ rq = blk_init_queue(do_blkif_request, &blkif_io_lock);
+ if (rq == NULL)
+ return -1;
-static int xlvbd_blk_queue_alloc(struct xlbd_type_info *type)
-{
- xlbd_blk_queue = blk_init_queue(do_blkif_request, &blkif_io_lock);
- if ( !xlbd_blk_queue )
- return -1;
-
- elevator_init(xlbd_blk_queue, "noop");
-
- /*
- * Turn off barking 'headactive' mode. We dequeue
- * buffer heads as soon as we pass them to back-end
- * driver.
- */
- blk_queue_headactive(xlbd_blk_queue, 0);
-
- /* Hard sector size and max sectors impersonate the equiv. hardware. */
- blk_queue_hardsect_size(xlbd_blk_queue, 512);
- blk_queue_max_sectors(xlbd_blk_queue, 512);
-
- /* Each segment in a request is up to an aligned page in size. */
- blk_queue_segment_boundary(xlbd_blk_queue, PAGE_SIZE - 1);
- blk_queue_max_segment_size(xlbd_blk_queue, PAGE_SIZE);
-
- /* Ensure a merged request will fit in a single I/O ring slot. */
- blk_queue_max_phys_segments(xlbd_blk_queue, BLKIF_MAX_SEGMENTS_PER_REQUEST);
- blk_queue_max_hw_segments(xlbd_blk_queue, BLKIF_MAX_SEGMENTS_PER_REQUEST);
-
- /* Make sure buffer addresses are sector-aligned. */
- blk_queue_dma_alignment(xlbd_blk_queue, 511);
- return 0;
-}
+ elevator_init(rq, "noop");
-struct gendisk *xlvbd_alloc_gendisk(struct xlbd_major_info *mi, int minor,
- vdisk_t *disk)
-{
- struct gendisk *gd;
- struct xlbd_disk_info *di;
- int nb_minors;
-
- di = kmalloc(sizeof(struct xlbd_disk_info), GFP_KERNEL);
- if ( !di )
- goto out;
- di->mi = mi;
- di->xd_device = disk->device;
-
- nb_minors = ((minor & ((1 << mi->type->partn_shift) - 1)) == 0)
- ? mi->type->partn_per_major
- : 1;
-
- gd = alloc_disk(nb_minors);
- if ( !gd )
- goto out;
-
- if ( nb_minors > 1 )
- sprintf(gd->disk_name, "%s%c", mi->type->diskname,
- 'a' + mi->index * mi->type->partn_per_major +
- (minor >> mi->type->partn_shift));
- else
- sprintf(gd->disk_name, "%s%c%d", mi->type->diskname,
- 'a' + mi->index * mi->type->partn_per_major +
- (minor >> mi->type->partn_shift),
- minor & ((1 << mi->type->partn_shift) - 1));
-
- gd->major = mi->major;
- gd->first_minor = minor;
- gd->fops = &xlvbd_block_fops;
- gd->private_data = di;
- set_capacity(gd, disk->capacity);
-
- if ( !xlbd_blk_queue )
- if ( xlvbd_blk_queue_alloc(mi->type) )
- goto out_gendisk;
-
- gd->queue = xlbd_blk_queue;
- add_disk(gd);
- return gd;
-out_gendisk:
- printk(KERN_ALERT "error gendisk\n");
- del_gendisk(gd);
-out:
- printk(KERN_ALERT "error out\n");
- kfree(di);
- return NULL;
-}
+ /* Hard sector size and max sectors impersonate the equiv. hardware. */
+ blk_queue_hardsect_size(rq, sector_size);
+ blk_queue_max_sectors(rq, 512);
-static int xlvbd_device_add(struct list_head *list, vdisk_t *disk)
-{
- struct lvdisk *new;
- int minor;
- dev_t device;
- struct block_device *bd;
- struct gendisk *gd;
- struct xlbd_major_info *mi;
-
- mi = xlbd_get_major_info(disk->device);
- if ( !mi )
- return -EPERM;
-
- new = xlvbd_device_alloc();
- if ( !new )
- return -1;
- new->capacity = disk->capacity;
- new->device = disk->device;
- new->info = disk->info;
-
- minor = MINOR_XEN(disk->device);
- device = MKDEV(mi->major, minor);
-
- bd = bdget(device);
- if ( !bd )
- goto out;
-
- gd = xlvbd_alloc_gendisk(mi, minor, disk);
- if ( !gd )
- goto out_bd;
-
- if ( VDISK_READONLY(disk->info) )
- set_disk_ro(gd, 1);
-
- switch (VDISK_TYPE(disk->info)) {
- case VDISK_TYPE_CDROM:
- gd->flags |= GENHD_FL_REMOVABLE | GENHD_FL_CD;
- break;
- case VDISK_TYPE_FLOPPY:
- case VDISK_TYPE_TAPE:
- gd->flags |= GENHD_FL_REMOVABLE;
- break;
- case VDISK_TYPE_DISK:
- break;
- default:
- printk(KERN_ALERT "XenLinux: unknown device type %d\n",
- VDISK_TYPE(disk->info));
- break;
- }
-
- list_add(&new->list, list);
-out_bd:
- bdput(bd);
-out:
- return 0;
-}
+ /* Each segment in a request is up to an aligned page in size. */
+ blk_queue_segment_boundary(rq, PAGE_SIZE - 1);
+ blk_queue_max_segment_size(rq, PAGE_SIZE);
-static int xlvbd_device_del(struct lvdisk *disk)
-{
- dev_t device;
- struct block_device *bd;
- struct gendisk *gd;
- struct xlbd_disk_info *di;
- int ret = 0, unused;
+ /* Ensure a merged request will fit in a single I/O ring slot. */
+ blk_queue_max_phys_segments(rq, BLKIF_MAX_SEGMENTS_PER_REQUEST);
+ blk_queue_max_hw_segments(rq, BLKIF_MAX_SEGMENTS_PER_REQUEST);
- device = MKDEV(MAJOR_XEN(disk->device), MINOR_XEN(disk->device));
+ /* Make sure buffer addresses are sector-aligned. */
+ blk_queue_dma_alignment(rq, 511);
- bd = bdget(device);
- if ( !bd )
- return -1;
+ gd->queue = rq;
- gd = get_gendisk(device, &unused);
- di = gd->private_data;
-
- if ( di->mi->usage != 0 ) {
- printk(KERN_ALERT "VBD removal failed: used [dev=%x]\n", device);
- ret = -1;
- goto out;
- }
-
- del_gendisk(gd);
-
- xlvbd_device_free(disk);
-out:
- bdput(bd);
- return ret;
+ return 0;
}
-static int xlvbd_device_update(struct lvdisk *ldisk, vdisk_t *disk)
+static int
+xlvbd_alloc_gendisk(int minor, blkif_sector_t capacity, int vdevice,
+ u16 vdisk_info, u16 sector_size,
+ struct blkfront_info *info)
{
- dev_t device;
- struct block_device *bd;
- struct gendisk *gd;
- int unused;
-
- if ( ldisk->capacity == disk->capacity && ldisk->info == disk->info )
- return 0;
+ struct gendisk *gd;
+ struct xlbd_major_info *mi;
+ int nr_minors = 1;
+ int err = -ENODEV;
+
+ BUG_ON(info->gd != NULL);
+ BUG_ON(info->mi != NULL);
+ BUG_ON(info->rq != NULL);
+
+ mi = xlbd_get_major_info(vdevice);
+ if (mi == NULL)
+ goto out;
+ info->mi = mi;
+
+ if ((minor & ((1 << mi->type->partn_shift) - 1)) == 0)
+ nr_minors = 1 << mi->type->partn_shift;
+
+ gd = alloc_disk(nr_minors);
+ if (gd == NULL)
+ goto out;
+
+ if (nr_minors > 1)
+ sprintf(gd->disk_name, "%s%c", mi->type->diskname,
+ 'a' + mi->index * mi->type->disks_per_major +
+ (minor >> mi->type->partn_shift));
+ else
+ sprintf(gd->disk_name, "%s%c%d", mi->type->diskname,
+ 'a' + mi->index * mi->type->disks_per_major +
+ (minor >> mi->type->partn_shift),
+ minor & ((1 << mi->type->partn_shift) - 1));
+
+ gd->major = mi->major;
+ gd->first_minor = minor;
+ gd->fops = &xlvbd_block_fops;
+ gd->private_data = info;
+ gd->driverfs_dev = &(info->xbdev->dev);
+ set_capacity(gd, capacity);
+
+ if (xlvbd_init_blk_queue(gd, sector_size)) {
+ del_gendisk(gd);
+ goto out;
+ }
+
+ info->rq = gd->queue;
+
+ if (vdisk_info & VDISK_READONLY)
+ set_disk_ro(gd, 1);
+
+ if (vdisk_info & VDISK_REMOVABLE)
+ gd->flags |= GENHD_FL_REMOVABLE;
+
+ if (vdisk_info & VDISK_CDROM)
+ gd->flags |= GENHD_FL_CD;
+
+ info->gd = gd;
+
+ return 0;
+
+ out:
+ if (mi)
+ xlbd_put_major_info(mi);
+ info->mi = NULL;
+ return err;
+}
- device = MKDEV(MAJOR_XEN(ldisk->device), MINOR_XEN(ldisk->device));
+int
+xlvbd_add(blkif_sector_t capacity, int vdevice, u16 vdisk_info,
+ u16 sector_size, struct blkfront_info *info)
+{
+ struct block_device *bd;
+ int err = 0;
- bd = bdget(device);
- if ( !bd )
- return -1;
+ info->dev = MKDEV(BLKIF_MAJOR(vdevice), BLKIF_MINOR(vdevice));
- gd = get_gendisk(device, &unused);
- set_capacity(gd, disk->capacity);
- ldisk->capacity = disk->capacity;
+ bd = bdget(info->dev);
+ if (bd == NULL)
+ return -ENODEV;
- bdput(bd);
+ err = xlvbd_alloc_gendisk(BLKIF_MINOR(vdevice), capacity, vdevice,
+ vdisk_info, sector_size, info);
- return 0;
+ bdput(bd);
+ return err;
}
-void xlvbd_refresh(void)
+void
+xlvbd_del(struct blkfront_info *info)
{
- vdisk_t *newdisks;
- struct list_head *tmp, *tmp2;
- struct lvdisk *disk;
- int i, nr;
-
- newdisks = xlvbd_probe(&nr);
- if ( !newdisks ) {
- printk(KERN_ALERT "failed to probe\n");
- return;
- }
-
- i = 0;
- list_for_each_safe(tmp, tmp2, &vbds_list) {
- disk = list_entry(tmp, struct lvdisk, list);
-
- for (i = 0; i < nr; i++) {
- if ( !newdisks[i].device )
- continue;
- if ( disk->device == newdisks[i].device ) {
- xlvbd_device_update(disk, &newdisks[i]);
- newdisks[i].device = 0;
- break;
- }
- }
- if ( i == nr ) {
- xlvbd_device_del(disk);
- newdisks[i].device = 0;
- }
- }
- for (i = 0; i < nr; i++)
- if ( newdisks[i].device )
- xlvbd_device_add(&vbds_list, &newdisks[i]);
- kfree(newdisks);
-}
+ if (info->mi == NULL)
+ return;
-/*
- * xlvbd_update_vbds - reprobes the VBD status and performs updates driver
- * state. The VBDs need to be updated in this way when the domain is
- * initialised and also each time we receive an XLBLK_UPDATE event.
- */
-void xlvbd_update_vbds(void)
-{
- xlvbd_refresh();
-}
+ BUG_ON(info->gd == NULL);
+ del_gendisk(info->gd);
+ put_disk(info->gd);
+ info->gd = NULL;
-/*
- * Set up all the linux device goop for the virtual block devices
- * (vbd's) that we know about. Note that although from the backend
- * driver's p.o.v. VBDs are addressed simply an opaque 16-bit device
- * number, the domain creation tools conventionally allocate these
- * numbers to correspond to those used by 'real' linux -- this is just
- * for convenience as it means e.g. that the same /etc/fstab can be
- * used when booting with or without Xen.
- */
-int xlvbd_init(void)
-{
- int i, nr;
- vdisk_t *disks;
-
- INIT_LIST_HEAD(&vbds_list);
-
- memset(major_info, 0, sizeof(major_info));
-
- disks = xlvbd_probe(&nr);
- if ( !disks ) {
- printk(KERN_ALERT "failed to probe\n");
- return -1;
- }
-
- for (i = 0; i < nr; i++)
- xlvbd_device_add(&vbds_list, &disks[i]);
- kfree(disks);
- return 0;
+ xlbd_put_major_info(info->mi);
+ info->mi = NULL;
+
+ BUG_ON(info->rq == NULL);
+ blk_cleanup_queue(info->rq);
+ info->rq = NULL;
}