1023 lines · 24.8 KB · C
RawDownload
/* * Kernel-based Virtual Machine - device assignment support * * Copyright (C) 2010 Red Hat, Inc. and/or its affiliates. * * This work is licensed under the terms of the GNU GPL, version 2.  See * the COPYING file in the top-level directory. * */ #include <linux/kvm_host.h>#include <linux/kvm.h>#include <linux/uaccess.h>#include <linux/vmalloc.h>#include <linux/errno.h>#include <linux/spinlock.h>#include <linux/pci.h>#include <linux/interrupt.h>#include <linux/slab.h>#include <linux/namei.h>#include <linux/fs.h>#include "irq.h" static struct kvm_assigned_dev_kernel *kvm_find_assigned_dev(struct list_head *head,						      int assigned_dev_id){	struct list_head *ptr;	struct kvm_assigned_dev_kernel *match; 	list_for_each(ptr, head) {		match = list_entry(ptr, struct kvm_assigned_dev_kernel, list);		if (match->assigned_dev_id == assigned_dev_id)			return match;	}	return NULL;} static int find_index_from_host_irq(struct kvm_assigned_dev_kernel				    *assigned_dev, int irq){	int i, index;	struct msix_entry *host_msix_entries; 	host_msix_entries = assigned_dev->host_msix_entries; 	index = -1;	for (i = 0; i < assigned_dev->entries_nr; i++)		if (irq == host_msix_entries[i].vector) {			index = i;			break;		}	if (index < 0)		printk(KERN_WARNING "Fail to find correlated MSI-X entry!\n"); 	return index;} static irqreturn_t kvm_assigned_dev_intx(int irq, void *dev_id){	struct kvm_assigned_dev_kernel *assigned_dev = dev_id;	int ret; 	spin_lock(&assigned_dev->intx_lock);	if (pci_check_and_mask_intx(assigned_dev->dev)) {		assigned_dev->host_irq_disabled = true;		ret = IRQ_WAKE_THREAD;	} else		ret = IRQ_NONE;	spin_unlock(&assigned_dev->intx_lock); 	return ret;} static voidkvm_assigned_dev_raise_guest_irq(struct kvm_assigned_dev_kernel *assigned_dev,				 int vector){	if (unlikely(assigned_dev->irq_requested_type &		     KVM_DEV_IRQ_GUEST_INTX)) {		spin_lock(&assigned_dev->intx_mask_lock);		if (!(assigned_dev->flags & KVM_DEV_ASSIGN_MASK_INTX))			kvm_set_irq(assigned_dev->kvm,				    assigned_dev->irq_source_id, vector, 1,				    false);		spin_unlock(&assigned_dev->intx_mask_lock);	} else		kvm_set_irq(assigned_dev->kvm, assigned_dev->irq_source_id,			    vector, 1, false);} static irqreturn_t kvm_assigned_dev_thread_intx(int irq, void *dev_id){	struct kvm_assigned_dev_kernel *assigned_dev = dev_id; 	if (!(assigned_dev->flags & KVM_DEV_ASSIGN_PCI_2_3)) {		spin_lock_irq(&assigned_dev->intx_lock);		disable_irq_nosync(irq);		assigned_dev->host_irq_disabled = true;		spin_unlock_irq(&assigned_dev->intx_lock);	} 	kvm_assigned_dev_raise_guest_irq(assigned_dev,					 assigned_dev->guest_irq); 	return IRQ_HANDLED;} #ifdef __KVM_HAVE_MSIstatic irqreturn_t kvm_assigned_dev_msi(int irq, void *dev_id){	struct kvm_assigned_dev_kernel *assigned_dev = dev_id;	int ret = kvm_set_irq_inatomic(assigned_dev->kvm,				       assigned_dev->irq_source_id,				       assigned_dev->guest_irq, 1);	return unlikely(ret == -EWOULDBLOCK) ? IRQ_WAKE_THREAD : IRQ_HANDLED;} static irqreturn_t kvm_assigned_dev_thread_msi(int irq, void *dev_id){	struct kvm_assigned_dev_kernel *assigned_dev = dev_id; 	kvm_assigned_dev_raise_guest_irq(assigned_dev,					 assigned_dev->guest_irq); 	return IRQ_HANDLED;}#endif #ifdef __KVM_HAVE_MSIXstatic irqreturn_t kvm_assigned_dev_msix(int irq, void *dev_id){	struct kvm_assigned_dev_kernel *assigned_dev = dev_id;	int index = find_index_from_host_irq(assigned_dev, irq);	u32 vector;	int ret = 0; 	if (index >= 0) {		vector = assigned_dev->guest_msix_entries[index].vector;		ret = kvm_set_irq_inatomic(assigned_dev->kvm,					   assigned_dev->irq_source_id,					   vector, 1);	} 	return unlikely(ret == -EWOULDBLOCK) ? IRQ_WAKE_THREAD : IRQ_HANDLED;} static irqreturn_t kvm_assigned_dev_thread_msix(int irq, void *dev_id){	struct kvm_assigned_dev_kernel *assigned_dev = dev_id;	int index = find_index_from_host_irq(assigned_dev, irq);	u32 vector; 	if (index >= 0) {		vector = assigned_dev->guest_msix_entries[index].vector;		kvm_assigned_dev_raise_guest_irq(assigned_dev, vector);	} 	return IRQ_HANDLED;}#endif /* Ack the irq line for an assigned device */static void kvm_assigned_dev_ack_irq(struct kvm_irq_ack_notifier *kian){	struct kvm_assigned_dev_kernel *dev =		container_of(kian, struct kvm_assigned_dev_kernel,			     ack_notifier); 	kvm_set_irq(dev->kvm, dev->irq_source_id, dev->guest_irq, 0, false); 	spin_lock(&dev->intx_mask_lock); 	if (!(dev->flags & KVM_DEV_ASSIGN_MASK_INTX)) {		bool reassert = false; 		spin_lock_irq(&dev->intx_lock);		/*		 * The guest IRQ may be shared so this ack can come from an		 * IRQ for another guest device.		 */		if (dev->host_irq_disabled) {			if (!(dev->flags & KVM_DEV_ASSIGN_PCI_2_3))				enable_irq(dev->host_irq);			else if (!pci_check_and_unmask_intx(dev->dev))				reassert = true;			dev->host_irq_disabled = reassert;		}		spin_unlock_irq(&dev->intx_lock); 		if (reassert)			kvm_set_irq(dev->kvm, dev->irq_source_id,				    dev->guest_irq, 1, false);	} 	spin_unlock(&dev->intx_mask_lock);} static void deassign_guest_irq(struct kvm *kvm,			       struct kvm_assigned_dev_kernel *assigned_dev){	if (assigned_dev->ack_notifier.gsi != -1)		kvm_unregister_irq_ack_notifier(kvm,						&assigned_dev->ack_notifier); 	kvm_set_irq(assigned_dev->kvm, assigned_dev->irq_source_id,		    assigned_dev->guest_irq, 0, false); 	if (assigned_dev->irq_source_id != -1)		kvm_free_irq_source_id(kvm, assigned_dev->irq_source_id);	assigned_dev->irq_source_id = -1;	assigned_dev->irq_requested_type &= ~(KVM_DEV_IRQ_GUEST_MASK);} /* The function implicit hold kvm->lock mutex due to cancel_work_sync() */static void deassign_host_irq(struct kvm *kvm,			      struct kvm_assigned_dev_kernel *assigned_dev){	/*	 * We disable irq here to prevent further events.	 *	 * Notice this maybe result in nested disable if the interrupt type is	 * INTx, but it's OK for we are going to free it.	 *	 * If this function is a part of VM destroy, please ensure that till	 * now, the kvm state is still legal for probably we also have to wait	 * on a currently running IRQ handler.	 */	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) {		int i;		for (i = 0; i < assigned_dev->entries_nr; i++)			disable_irq(assigned_dev->host_msix_entries[i].vector); 		for (i = 0; i < assigned_dev->entries_nr; i++)			free_irq(assigned_dev->host_msix_entries[i].vector,				 assigned_dev); 		assigned_dev->entries_nr = 0;		kfree(assigned_dev->host_msix_entries);		kfree(assigned_dev->guest_msix_entries);		pci_disable_msix(assigned_dev->dev);	} else {		/* Deal with MSI and INTx */		if ((assigned_dev->irq_requested_type &		     KVM_DEV_IRQ_HOST_INTX) &&		    (assigned_dev->flags & KVM_DEV_ASSIGN_PCI_2_3)) {			spin_lock_irq(&assigned_dev->intx_lock);			pci_intx(assigned_dev->dev, false);			spin_unlock_irq(&assigned_dev->intx_lock);			synchronize_irq(assigned_dev->host_irq);		} else			disable_irq(assigned_dev->host_irq); 		free_irq(assigned_dev->host_irq, assigned_dev); 		if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSI)			pci_disable_msi(assigned_dev->dev);	} 	assigned_dev->irq_requested_type &= ~(KVM_DEV_IRQ_HOST_MASK);} static int kvm_deassign_irq(struct kvm *kvm,			    struct kvm_assigned_dev_kernel *assigned_dev,			    unsigned long irq_requested_type){	unsigned long guest_irq_type, host_irq_type; 	if (!irqchip_in_kernel(kvm))		return -EINVAL;	/* no irq assignment to deassign */	if (!assigned_dev->irq_requested_type)		return -ENXIO; 	host_irq_type = irq_requested_type & KVM_DEV_IRQ_HOST_MASK;	guest_irq_type = irq_requested_type & KVM_DEV_IRQ_GUEST_MASK; 	if (host_irq_type)		deassign_host_irq(kvm, assigned_dev);	if (guest_irq_type)		deassign_guest_irq(kvm, assigned_dev); 	return 0;} static void kvm_free_assigned_irq(struct kvm *kvm,				  struct kvm_assigned_dev_kernel *assigned_dev){	kvm_deassign_irq(kvm, assigned_dev, assigned_dev->irq_requested_type);} static void kvm_free_assigned_device(struct kvm *kvm,				     struct kvm_assigned_dev_kernel				     *assigned_dev){	kvm_free_assigned_irq(kvm, assigned_dev); 	pci_reset_function(assigned_dev->dev);	if (pci_load_and_free_saved_state(assigned_dev->dev,					  &assigned_dev->pci_saved_state))		printk(KERN_INFO "%s: Couldn't reload %s saved state\n",		       __func__, dev_name(&assigned_dev->dev->dev));	else		pci_restore_state(assigned_dev->dev); 	assigned_dev->dev->dev_flags &= ~PCI_DEV_FLAGS_ASSIGNED; 	pci_release_regions(assigned_dev->dev);	pci_disable_device(assigned_dev->dev);	pci_dev_put(assigned_dev->dev); 	list_del(&assigned_dev->list);	kfree(assigned_dev);} void kvm_free_all_assigned_devices(struct kvm *kvm){	struct list_head *ptr, *ptr2;	struct kvm_assigned_dev_kernel *assigned_dev; 	list_for_each_safe(ptr, ptr2, &kvm->arch.assigned_dev_head) {		assigned_dev = list_entry(ptr,					  struct kvm_assigned_dev_kernel,					  list); 		kvm_free_assigned_device(kvm, assigned_dev);	}} static int assigned_device_enable_host_intx(struct kvm *kvm,					    struct kvm_assigned_dev_kernel *dev){	irq_handler_t irq_handler;	unsigned long flags; 	dev->host_irq = dev->dev->irq; 	/*	 * We can only share the IRQ line with other host devices if we are	 * able to disable the IRQ source at device-level - independently of	 * the guest driver. Otherwise host devices may suffer from unbounded	 * IRQ latencies when the guest keeps the line asserted.	 */	if (dev->flags & KVM_DEV_ASSIGN_PCI_2_3) {		irq_handler = kvm_assigned_dev_intx;		flags = IRQF_SHARED;	} else {		irq_handler = NULL;		flags = IRQF_ONESHOT;	}	if (request_threaded_irq(dev->host_irq, irq_handler,				 kvm_assigned_dev_thread_intx, flags,				 dev->irq_name, dev))		return -EIO; 	if (dev->flags & KVM_DEV_ASSIGN_PCI_2_3) {		spin_lock_irq(&dev->intx_lock);		pci_intx(dev->dev, true);		spin_unlock_irq(&dev->intx_lock);	}	return 0;} #ifdef __KVM_HAVE_MSIstatic int assigned_device_enable_host_msi(struct kvm *kvm,					   struct kvm_assigned_dev_kernel *dev){	int r; 	if (!dev->dev->msi_enabled) {		r = pci_enable_msi(dev->dev);		if (r)			return r;	} 	dev->host_irq = dev->dev->irq;	if (request_threaded_irq(dev->host_irq, kvm_assigned_dev_msi,				 kvm_assigned_dev_thread_msi, 0,				 dev->irq_name, dev)) {		pci_disable_msi(dev->dev);		return -EIO;	} 	return 0;}#endif #ifdef __KVM_HAVE_MSIXstatic int assigned_device_enable_host_msix(struct kvm *kvm,					    struct kvm_assigned_dev_kernel *dev){	int i, r = -EINVAL; 	/* host_msix_entries and guest_msix_entries should have been	 * initialized */	if (dev->entries_nr == 0)		return r; 	r = pci_enable_msix(dev->dev, dev->host_msix_entries, dev->entries_nr);	if (r)		return r; 	for (i = 0; i < dev->entries_nr; i++) {		r = request_threaded_irq(dev->host_msix_entries[i].vector,					 kvm_assigned_dev_msix,					 kvm_assigned_dev_thread_msix,					 0, dev->irq_name, dev);		if (r)			goto err;	} 	return 0;err:	for (i -= 1; i >= 0; i--)		free_irq(dev->host_msix_entries[i].vector, dev);	pci_disable_msix(dev->dev);	return r;} #endif static int assigned_device_enable_guest_intx(struct kvm *kvm,				struct kvm_assigned_dev_kernel *dev,				struct kvm_assigned_irq *irq){	dev->guest_irq = irq->guest_irq;	dev->ack_notifier.gsi = irq->guest_irq;	return 0;} #ifdef __KVM_HAVE_MSIstatic int assigned_device_enable_guest_msi(struct kvm *kvm,			struct kvm_assigned_dev_kernel *dev,			struct kvm_assigned_irq *irq){	dev->guest_irq = irq->guest_irq;	dev->ack_notifier.gsi = -1;	return 0;}#endif #ifdef __KVM_HAVE_MSIXstatic int assigned_device_enable_guest_msix(struct kvm *kvm,			struct kvm_assigned_dev_kernel *dev,			struct kvm_assigned_irq *irq){	dev->guest_irq = irq->guest_irq;	dev->ack_notifier.gsi = -1;	return 0;}#endif static int assign_host_irq(struct kvm *kvm,			   struct kvm_assigned_dev_kernel *dev,			   __u32 host_irq_type){	int r = -EEXIST; 	if (dev->irq_requested_type & KVM_DEV_IRQ_HOST_MASK)		return r; 	snprintf(dev->irq_name, sizeof(dev->irq_name), "kvm:%s",		 pci_name(dev->dev)); 	switch (host_irq_type) {	case KVM_DEV_IRQ_HOST_INTX:		r = assigned_device_enable_host_intx(kvm, dev);		break;#ifdef __KVM_HAVE_MSI	case KVM_DEV_IRQ_HOST_MSI:		r = assigned_device_enable_host_msi(kvm, dev);		break;#endif#ifdef __KVM_HAVE_MSIX	case KVM_DEV_IRQ_HOST_MSIX:		r = assigned_device_enable_host_msix(kvm, dev);		break;#endif	default:		r = -EINVAL;	}	dev->host_irq_disabled = false; 	if (!r)		dev->irq_requested_type |= host_irq_type; 	return r;} static int assign_guest_irq(struct kvm *kvm,			    struct kvm_assigned_dev_kernel *dev,			    struct kvm_assigned_irq *irq,			    unsigned long guest_irq_type){	int id;	int r = -EEXIST; 	if (dev->irq_requested_type & KVM_DEV_IRQ_GUEST_MASK)		return r; 	id = kvm_request_irq_source_id(kvm);

Lines 1–500 of 1023.