Commit 7a7c924c authored by Christoph Hellwig's avatar Christoph Hellwig Committed by Rusty Russell

virtio_blk: allow re-reading config space at runtime

Wire up the virtio_driver config_changed method to get notified about
config changes raised by the host.  For now we just re-read the device
size to support online resizing of devices, but once we add more
attributes that might be changeable they could be added as well.

Note that the config_changed method is called from irq context, so
we'll have to use the workqueue infrastructure to provide us a proper
user context for our changes.
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 990c91f0
...@@ -6,10 +6,12 @@ ...@@ -6,10 +6,12 @@
#include <linux/virtio.h> #include <linux/virtio.h>
#include <linux/virtio_blk.h> #include <linux/virtio_blk.h>
#include <linux/scatterlist.h> #include <linux/scatterlist.h>
#include <linux/string_helpers.h>
#define PART_BITS 4 #define PART_BITS 4
static int major, index; static int major, index;
struct workqueue_struct *virtblk_wq;
struct virtio_blk struct virtio_blk
{ {
...@@ -26,6 +28,9 @@ struct virtio_blk ...@@ -26,6 +28,9 @@ struct virtio_blk
mempool_t *pool; mempool_t *pool;
/* Process context for config space updates */
struct work_struct config_work;
/* What host tells us, plus 2 for header & tailer. */ /* What host tells us, plus 2 for header & tailer. */
unsigned int sg_elems; unsigned int sg_elems;
...@@ -291,6 +296,46 @@ static ssize_t virtblk_serial_show(struct device *dev, ...@@ -291,6 +296,46 @@ static ssize_t virtblk_serial_show(struct device *dev,
} }
DEVICE_ATTR(serial, S_IRUGO, virtblk_serial_show, NULL); DEVICE_ATTR(serial, S_IRUGO, virtblk_serial_show, NULL);
static void virtblk_config_changed_work(struct work_struct *work)
{
struct virtio_blk *vblk =
container_of(work, struct virtio_blk, config_work);
struct virtio_device *vdev = vblk->vdev;
struct request_queue *q = vblk->disk->queue;
char cap_str_2[10], cap_str_10[10];
u64 capacity, size;
/* Host must always specify the capacity. */
vdev->config->get(vdev, offsetof(struct virtio_blk_config, capacity),
&capacity, sizeof(capacity));
/* If capacity is too big, truncate with warning. */
if ((sector_t)capacity != capacity) {
dev_warn(&vdev->dev, "Capacity %llu too large: truncating\n",
(unsigned long long)capacity);
capacity = (sector_t)-1;
}
size = capacity * queue_logical_block_size(q);
string_get_size(size, STRING_UNITS_2, cap_str_2, sizeof(cap_str_2));
string_get_size(size, STRING_UNITS_10, cap_str_10, sizeof(cap_str_10));
dev_notice(&vdev->dev,
"new size: %llu %d-byte logical blocks (%s/%s)\n",
(unsigned long long)capacity,
queue_logical_block_size(q),
cap_str_10, cap_str_2);
set_capacity(vblk->disk, capacity);
}
static void virtblk_config_changed(struct virtio_device *vdev)
{
struct virtio_blk *vblk = vdev->priv;
queue_work(virtblk_wq, &vblk->config_work);
}
static int __devinit virtblk_probe(struct virtio_device *vdev) static int __devinit virtblk_probe(struct virtio_device *vdev)
{ {
struct virtio_blk *vblk; struct virtio_blk *vblk;
...@@ -327,6 +372,7 @@ static int __devinit virtblk_probe(struct virtio_device *vdev) ...@@ -327,6 +372,7 @@ static int __devinit virtblk_probe(struct virtio_device *vdev)
vblk->vdev = vdev; vblk->vdev = vdev;
vblk->sg_elems = sg_elems; vblk->sg_elems = sg_elems;
sg_init_table(vblk->sg, vblk->sg_elems); sg_init_table(vblk->sg, vblk->sg_elems);
INIT_WORK(&vblk->config_work, virtblk_config_changed_work);
/* We expect one virtqueue, for output. */ /* We expect one virtqueue, for output. */
vblk->vq = virtio_find_single_vq(vdev, blk_done, "requests"); vblk->vq = virtio_find_single_vq(vdev, blk_done, "requests");
...@@ -477,6 +523,8 @@ static void __devexit virtblk_remove(struct virtio_device *vdev) ...@@ -477,6 +523,8 @@ static void __devexit virtblk_remove(struct virtio_device *vdev)
{ {
struct virtio_blk *vblk = vdev->priv; struct virtio_blk *vblk = vdev->priv;
flush_work(&vblk->config_work);
/* Nothing should be pending. */ /* Nothing should be pending. */
BUG_ON(!list_empty(&vblk->reqs)); BUG_ON(!list_empty(&vblk->reqs));
...@@ -508,27 +556,47 @@ static unsigned int features[] = { ...@@ -508,27 +556,47 @@ static unsigned int features[] = {
* Use __refdata to avoid this warning. * Use __refdata to avoid this warning.
*/ */
static struct virtio_driver __refdata virtio_blk = { static struct virtio_driver __refdata virtio_blk = {
.feature_table = features, .feature_table = features,
.feature_table_size = ARRAY_SIZE(features), .feature_table_size = ARRAY_SIZE(features),
.driver.name = KBUILD_MODNAME, .driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE, .driver.owner = THIS_MODULE,
.id_table = id_table, .id_table = id_table,
.probe = virtblk_probe, .probe = virtblk_probe,
.remove = __devexit_p(virtblk_remove), .remove = __devexit_p(virtblk_remove),
.config_changed = virtblk_config_changed,
}; };
static int __init init(void) static int __init init(void)
{ {
int error;
virtblk_wq = alloc_workqueue("virtio-blk", 0, 0);
if (!virtblk_wq)
return -ENOMEM;
major = register_blkdev(0, "virtblk"); major = register_blkdev(0, "virtblk");
if (major < 0) if (major < 0) {
return major; error = major;
return register_virtio_driver(&virtio_blk); goto out_destroy_workqueue;
}
error = register_virtio_driver(&virtio_blk);
if (error)
goto out_unregister_blkdev;
return 0;
out_unregister_blkdev:
unregister_blkdev(major, "virtblk");
out_destroy_workqueue:
destroy_workqueue(virtblk_wq);
return error;
} }
static void __exit fini(void) static void __exit fini(void)
{ {
unregister_blkdev(major, "virtblk"); unregister_blkdev(major, "virtblk");
unregister_virtio_driver(&virtio_blk); unregister_virtio_driver(&virtio_blk);
destroy_workqueue(virtblk_wq);
} }
module_init(init); module_init(init);
module_exit(fini); module_exit(fini);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment