diff options
Diffstat (limited to 'drivers/virtio/virtio_pci.c')
| -rw-r--r-- | drivers/virtio/virtio_pci.c | 252 | 
1 files changed, 170 insertions, 82 deletions
diff --git a/drivers/virtio/virtio_pci.c b/drivers/virtio/virtio_pci.c index ef8d9d558fc..101db3faf5d 100644 --- a/drivers/virtio/virtio_pci.c +++ b/drivers/virtio/virtio_pci.c @@ -48,6 +48,7 @@ struct virtio_pci_device  	int msix_enabled;  	int intx_enabled;  	struct msix_entry *msix_entries; +	cpumask_var_t *msix_affinity_masks;  	/* Name strings for interrupts. This size should be enough,  	 * and I'm too lazy to allocate each name separately. */  	char (*msix_names)[256]; @@ -55,6 +56,10 @@ struct virtio_pci_device  	unsigned msix_vectors;  	/* Vectors allocated, excluding per-vq vectors if any */  	unsigned msix_used_vectors; + +	/* Status saved during hibernate/restore */ +	u8 saved_status; +  	/* Whether we have vector per vq */  	bool per_vq_vectors;  }; @@ -75,9 +80,6 @@ struct virtio_pci_vq_info  	/* the number of entries in the queue */  	int num; -	/* the index of the queue */ -	int queue_index; -  	/* the virtual address of the ring queue */  	void *queue; @@ -89,18 +91,13 @@ struct virtio_pci_vq_info  };  /* Qumranet donated their vendor ID for devices 0x1000 thru 0x10FF. */ -static struct pci_device_id virtio_pci_id_table[] = { -	{ 0x1af4, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, -	{ 0 }, +static DEFINE_PCI_DEVICE_TABLE(virtio_pci_id_table) = { +	{ PCI_DEVICE(0x1af4, PCI_ANY_ID) }, +	{ 0 }  };  MODULE_DEVICE_TABLE(pci, virtio_pci_id_table); -/* A PCI device has it's own struct device and so does a virtio device so - * we create a place for the virtio devices to show up in sysfs.  I think it - * would make more sense for virtio to not insist on having it's own device. */ -static struct device *virtio_pci_root; -  /* Convert a generic virtio device to our structure */  static struct virtio_pci_device *to_vp_device(struct virtio_device *vdev)  { @@ -174,22 +171,40 @@ static void vp_set_status(struct virtio_device *vdev, u8 status)  	iowrite8(status, vp_dev->ioaddr + VIRTIO_PCI_STATUS);  } +/* wait for pending irq handlers */ +static void vp_synchronize_vectors(struct virtio_device *vdev) +{ +	struct virtio_pci_device *vp_dev = to_vp_device(vdev); +	int i; + +	if (vp_dev->intx_enabled) +		synchronize_irq(vp_dev->pci_dev->irq); + +	for (i = 0; i < vp_dev->msix_vectors; ++i) +		synchronize_irq(vp_dev->msix_entries[i].vector); +} +  static void vp_reset(struct virtio_device *vdev)  {  	struct virtio_pci_device *vp_dev = to_vp_device(vdev);  	/* 0 status means a reset. */  	iowrite8(0, vp_dev->ioaddr + VIRTIO_PCI_STATUS); +	/* Flush out the status write, and flush in device writes, +	 * including MSi-X interrupts, if any. */ +	ioread8(vp_dev->ioaddr + VIRTIO_PCI_STATUS); +	/* Flush pending VQ/configuration callbacks. */ +	vp_synchronize_vectors(vdev);  }  /* the notify function used when creating a virt queue */ -static void vp_notify(struct virtqueue *vq) +static bool vp_notify(struct virtqueue *vq)  {  	struct virtio_pci_device *vp_dev = to_vp_device(vq->vdev); -	struct virtio_pci_vq_info *info = vq->priv;  	/* we write the queue's selector into the notification register to  	 * signal the other end */ -	iowrite16(info->queue_index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY); +	iowrite16(vq->index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY); +	return true;  }  /* Handle a configuration change: Tell driver if it wants to know. */ @@ -262,6 +277,10 @@ static void vp_free_vectors(struct virtio_device *vdev)  	for (i = 0; i < vp_dev->msix_used_vectors; ++i)  		free_irq(vp_dev->msix_entries[i].vector, vp_dev); +	for (i = 0; i < vp_dev->msix_vectors; i++) +		if (vp_dev->msix_affinity_masks[i]) +			free_cpumask_var(vp_dev->msix_affinity_masks[i]); +  	if (vp_dev->msix_enabled) {  		/* Disable the vector used for configuration */  		iowrite16(VIRTIO_MSI_NO_VECTOR, @@ -271,14 +290,16 @@ static void vp_free_vectors(struct virtio_device *vdev)  		pci_disable_msix(vp_dev->pci_dev);  		vp_dev->msix_enabled = 0; -		vp_dev->msix_vectors = 0;  	} +	vp_dev->msix_vectors = 0;  	vp_dev->msix_used_vectors = 0;  	kfree(vp_dev->msix_names);  	vp_dev->msix_names = NULL;  	kfree(vp_dev->msix_entries);  	vp_dev->msix_entries = NULL; +	kfree(vp_dev->msix_affinity_masks); +	vp_dev->msix_affinity_masks = NULL;  }  static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors, @@ -289,6 +310,8 @@ static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,  	unsigned i, v;  	int err = -ENOMEM; +	vp_dev->msix_vectors = nvectors; +  	vp_dev->msix_entries = kmalloc(nvectors * sizeof *vp_dev->msix_entries,  				       GFP_KERNEL);  	if (!vp_dev->msix_entries) @@ -297,17 +320,23 @@ static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,  				     GFP_KERNEL);  	if (!vp_dev->msix_names)  		goto error; +	vp_dev->msix_affinity_masks +		= kzalloc(nvectors * sizeof *vp_dev->msix_affinity_masks, +			  GFP_KERNEL); +	if (!vp_dev->msix_affinity_masks) +		goto error; +	for (i = 0; i < nvectors; ++i) +		if (!alloc_cpumask_var(&vp_dev->msix_affinity_masks[i], +					GFP_KERNEL)) +			goto error;  	for (i = 0; i < nvectors; ++i)  		vp_dev->msix_entries[i].entry = i; -	/* pci_enable_msix returns positive if we can't get this many. */ -	err = pci_enable_msix(vp_dev->pci_dev, vp_dev->msix_entries, nvectors); -	if (err > 0) -		err = -ENOSPC; +	err = pci_enable_msix_exact(vp_dev->pci_dev, +				    vp_dev->msix_entries, nvectors);  	if (err)  		goto error; -	vp_dev->msix_vectors = nvectors;  	vp_dev->msix_enabled = 1;  	/* Set the vector used for configuration */ @@ -385,7 +414,6 @@ static struct virtqueue *setup_vq(struct virtio_device *vdev, unsigned index,  	if (!info)  		return ERR_PTR(-ENOMEM); -	info->queue_index = index;  	info->num = num;  	info->msix_vector = msix_vec; @@ -401,8 +429,8 @@ static struct virtqueue *setup_vq(struct virtio_device *vdev, unsigned index,  		  vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN);  	/* create the vring */ -	vq = vring_new_virtqueue(info->num, VIRTIO_PCI_VRING_ALIGN, -				 vdev, info->queue, vp_notify, callback, name); +	vq = vring_new_virtqueue(index, info->num, VIRTIO_PCI_VRING_ALIGN, vdev, +				 true, info->queue, vp_notify, callback, name);  	if (!vq) {  		err = -ENOMEM;  		goto out_activate_queue; @@ -420,9 +448,13 @@ static struct virtqueue *setup_vq(struct virtio_device *vdev, unsigned index,  		}  	} -	spin_lock_irqsave(&vp_dev->lock, flags); -	list_add(&info->node, &vp_dev->virtqueues); -	spin_unlock_irqrestore(&vp_dev->lock, flags); +	if (callback) { +		spin_lock_irqsave(&vp_dev->lock, flags); +		list_add(&info->node, &vp_dev->virtqueues); +		spin_unlock_irqrestore(&vp_dev->lock, flags); +	} else { +		INIT_LIST_HEAD(&info->node); +	}  	return vq; @@ -446,7 +478,7 @@ static void vp_del_vq(struct virtqueue *vq)  	list_del(&info->node);  	spin_unlock_irqrestore(&vp_dev->lock, flags); -	iowrite16(info->queue_index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL); +	iowrite16(vq->index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL);  	if (vp_dev->msix_enabled) {  		iowrite16(VIRTIO_MSI_NO_VECTOR, @@ -521,7 +553,10 @@ static int vp_try_to_find_vqs(struct virtio_device *vdev, unsigned nvqs,  	vp_dev->per_vq_vectors = per_vq_vectors;  	allocated_vectors = vp_dev->msix_used_vectors;  	for (i = 0; i < nvqs; ++i) { -		if (!callbacks[i] || !vp_dev->msix_enabled) +		if (!names[i]) { +			vqs[i] = NULL; +			continue; +		} else if (!callbacks[i] || !vp_dev->msix_enabled)  			msix_vec = VIRTIO_MSI_NO_VECTOR;  		else if (vp_dev->per_vq_vectors)  			msix_vec = allocated_vectors++; @@ -581,7 +616,43 @@ static int vp_find_vqs(struct virtio_device *vdev, unsigned nvqs,  				  false, false);  } -static struct virtio_config_ops virtio_pci_config_ops = { +static const char *vp_bus_name(struct virtio_device *vdev) +{ +	struct virtio_pci_device *vp_dev = to_vp_device(vdev); + +	return pci_name(vp_dev->pci_dev); +} + +/* Setup the affinity for a virtqueue: + * - force the affinity for per vq vector + * - OR over all affinities for shared MSI + * - ignore the affinity request if we're using INTX + */ +static int vp_set_vq_affinity(struct virtqueue *vq, int cpu) +{ +	struct virtio_device *vdev = vq->vdev; +	struct virtio_pci_device *vp_dev = to_vp_device(vdev); +	struct virtio_pci_vq_info *info = vq->priv; +	struct cpumask *mask; +	unsigned int irq; + +	if (!vq->callback) +		return -EINVAL; + +	if (vp_dev->msix_enabled) { +		mask = vp_dev->msix_affinity_masks[info->msix_vector]; +		irq = vp_dev->msix_entries[info->msix_vector].vector; +		if (cpu == -1) +			irq_set_affinity_hint(irq, NULL); +		else { +			cpumask_set_cpu(cpu, mask); +			irq_set_affinity_hint(irq, mask); +		} +	} +	return 0; +} + +static const struct virtio_config_ops virtio_pci_config_ops = {  	.get		= vp_get,  	.set		= vp_set,  	.get_status	= vp_get_status, @@ -591,25 +662,22 @@ static struct virtio_config_ops virtio_pci_config_ops = {  	.del_vqs	= vp_del_vqs,  	.get_features	= vp_get_features,  	.finalize_features = vp_finalize_features, +	.bus_name	= vp_bus_name, +	.set_vq_affinity = vp_set_vq_affinity,  };  static void virtio_pci_release_dev(struct device *_d)  { -	struct virtio_device *dev = container_of(_d, struct virtio_device, dev); -	struct virtio_pci_device *vp_dev = to_vp_device(dev); -	struct pci_dev *pci_dev = vp_dev->pci_dev; - -	vp_del_vqs(dev); -	pci_set_drvdata(pci_dev, NULL); -	pci_iounmap(pci_dev, vp_dev->ioaddr); -	pci_release_regions(pci_dev); -	pci_disable_device(pci_dev); -	kfree(vp_dev); +	/* +	 * No need for a release method as we allocate/free +	 * all devices together with the pci devices. +	 * Provide an empty one to avoid getting a warning from core. +	 */  }  /* the PCI probing function */ -static int __devinit virtio_pci_probe(struct pci_dev *pci_dev, -				      const struct pci_device_id *id) +static int virtio_pci_probe(struct pci_dev *pci_dev, +			    const struct pci_device_id *id)  {  	struct virtio_pci_device *vp_dev;  	int err; @@ -629,7 +697,7 @@ static int __devinit virtio_pci_probe(struct pci_dev *pci_dev,  	if (vp_dev == NULL)  		return -ENOMEM; -	vp_dev->vdev.dev.parent = virtio_pci_root; +	vp_dev->vdev.dev.parent = &pci_dev->dev;  	vp_dev->vdev.dev.release = virtio_pci_release_dev;  	vp_dev->vdev.config = &virtio_pci_config_ops;  	vp_dev->pci_dev = pci_dev; @@ -649,8 +717,10 @@ static int __devinit virtio_pci_probe(struct pci_dev *pci_dev,  		goto out_enable_device;  	vp_dev->ioaddr = pci_iomap(pci_dev, 0, 0); -	if (vp_dev->ioaddr == NULL) +	if (vp_dev->ioaddr == NULL) { +		err = -ENOMEM;  		goto out_req_regions; +	}  	pci_set_drvdata(pci_dev, vp_dev);  	pci_set_master(pci_dev); @@ -670,7 +740,6 @@ static int __devinit virtio_pci_probe(struct pci_dev *pci_dev,  	return 0;  out_set_drvdata: -	pci_set_drvdata(pci_dev, NULL);  	pci_iounmap(pci_dev, vp_dev->ioaddr);  out_req_regions:  	pci_release_regions(pci_dev); @@ -681,61 +750,80 @@ out:  	return err;  } -static void __devexit virtio_pci_remove(struct pci_dev *pci_dev) +static void virtio_pci_remove(struct pci_dev *pci_dev)  {  	struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev);  	unregister_virtio_device(&vp_dev->vdev); + +	vp_del_vqs(&vp_dev->vdev); +	pci_iounmap(pci_dev, vp_dev->ioaddr); +	pci_release_regions(pci_dev); +	pci_disable_device(pci_dev); +	kfree(vp_dev);  } -#ifdef CONFIG_PM -static int virtio_pci_suspend(struct pci_dev *pci_dev, pm_message_t state) +#ifdef CONFIG_PM_SLEEP +static int virtio_pci_freeze(struct device *dev)  { -	pci_save_state(pci_dev); -	pci_set_power_state(pci_dev, PCI_D3hot); -	return 0; +	struct pci_dev *pci_dev = to_pci_dev(dev); +	struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev); +	struct virtio_driver *drv; +	int ret; + +	drv = container_of(vp_dev->vdev.dev.driver, +			   struct virtio_driver, driver); + +	ret = 0; +	vp_dev->saved_status = vp_get_status(&vp_dev->vdev); +	if (drv && drv->freeze) +		ret = drv->freeze(&vp_dev->vdev); + +	if (!ret) +		pci_disable_device(pci_dev); +	return ret;  } -static int virtio_pci_resume(struct pci_dev *pci_dev) +static int virtio_pci_restore(struct device *dev)  { -	pci_restore_state(pci_dev); -	pci_set_power_state(pci_dev, PCI_D0); -	return 0; +	struct pci_dev *pci_dev = to_pci_dev(dev); +	struct virtio_pci_device *vp_dev = pci_get_drvdata(pci_dev); +	struct virtio_driver *drv; +	int ret; + +	drv = container_of(vp_dev->vdev.dev.driver, +			   struct virtio_driver, driver); + +	ret = pci_enable_device(pci_dev); +	if (ret) +		return ret; + +	pci_set_master(pci_dev); +	vp_finalize_features(&vp_dev->vdev); + +	if (drv && drv->restore) +		ret = drv->restore(&vp_dev->vdev); + +	/* Finally, tell the device we're all set */ +	if (!ret) +		vp_set_status(&vp_dev->vdev, vp_dev->saved_status); + +	return ret;  } + +static const struct dev_pm_ops virtio_pci_pm_ops = { +	SET_SYSTEM_SLEEP_PM_OPS(virtio_pci_freeze, virtio_pci_restore) +};  #endif  static struct pci_driver virtio_pci_driver = {  	.name		= "virtio-pci",  	.id_table	= virtio_pci_id_table,  	.probe		= virtio_pci_probe, -	.remove		= __devexit_p(virtio_pci_remove), -#ifdef CONFIG_PM -	.suspend	= virtio_pci_suspend, -	.resume		= virtio_pci_resume, +	.remove		= virtio_pci_remove, +#ifdef CONFIG_PM_SLEEP +	.driver.pm	= &virtio_pci_pm_ops,  #endif  }; -static int __init virtio_pci_init(void) -{ -	int err; - -	virtio_pci_root = root_device_register("virtio-pci"); -	if (IS_ERR(virtio_pci_root)) -		return PTR_ERR(virtio_pci_root); - -	err = pci_register_driver(&virtio_pci_driver); -	if (err) -		root_device_unregister(virtio_pci_root); - -	return err; -} - -module_init(virtio_pci_init); - -static void __exit virtio_pci_exit(void) -{ -	pci_unregister_driver(&virtio_pci_driver); -	root_device_unregister(virtio_pci_root); -} - -module_exit(virtio_pci_exit); +module_pci_driver(virtio_pci_driver);  | 
