878 lines · 20.9 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 0;	} 	return index;} static irqreturn_t kvm_assigned_dev_thread(int irq, void *dev_id){	struct kvm_assigned_dev_kernel *assigned_dev = dev_id; 	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_INTX) {		spin_lock(&assigned_dev->intx_lock);		disable_irq_nosync(irq);		assigned_dev->host_irq_disabled = true;		spin_unlock(&assigned_dev->intx_lock);	} 	kvm_set_irq(assigned_dev->kvm, assigned_dev->irq_source_id,		    assigned_dev->guest_irq, 1); 	return IRQ_HANDLED;} #ifdef __KVM_HAVE_MSIXstatic 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_set_irq(assigned_dev->kvm, assigned_dev->irq_source_id,			    vector, 1);	} 	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); 	/* The guest irq may be shared so this ack may be	 * from another device.	 */	spin_lock(&dev->intx_lock);	if (dev->host_irq_disabled) {		enable_irq(dev->host_irq);		dev->host_irq_disabled = false;	}	spin_unlock(&dev->intx_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); 	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 */		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){	dev->host_irq = dev->dev->irq;	/* Even though this is PCI, we don't want to use shared	 * interrupts. Sharing host devices with guest-assigned devices	 * on the same interrupt line is not a happy situation: there	 * are going to be long delays in accepting, acking, etc.	 */	if (request_threaded_irq(dev->host_irq, NULL, kvm_assigned_dev_thread,				 IRQF_ONESHOT, dev->irq_name, dev))		return -EIO;	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, NULL, kvm_assigned_dev_thread,				 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,					 NULL, 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;	dev->host_irq_disabled = false;	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;	dev->host_irq_disabled = false;	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;	} 	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);	if (id < 0)		return id; 	dev->irq_source_id = id; 	switch (guest_irq_type) {	case KVM_DEV_IRQ_GUEST_INTX:		r = assigned_device_enable_guest_intx(kvm, dev, irq);		break;#ifdef __KVM_HAVE_MSI	case KVM_DEV_IRQ_GUEST_MSI:		r = assigned_device_enable_guest_msi(kvm, dev, irq);		break;#endif#ifdef __KVM_HAVE_MSIX	case KVM_DEV_IRQ_GUEST_MSIX:		r = assigned_device_enable_guest_msix(kvm, dev, irq);		break;#endif	default:		r = -EINVAL;	} 	if (!r) {		dev->irq_requested_type |= guest_irq_type;		if (dev->ack_notifier.gsi != -1)			kvm_register_irq_ack_notifier(kvm, &dev->ack_notifier);	} else		kvm_free_irq_source_id(kvm, dev->irq_source_id); 	return r;} /* TODO Deal with KVM_DEV_IRQ_ASSIGNED_MASK_MSIX */static int kvm_vm_ioctl_assign_irq(struct kvm *kvm,				   struct kvm_assigned_irq *assigned_irq){	int r = -EINVAL;	struct kvm_assigned_dev_kernel *match;	unsigned long host_irq_type, guest_irq_type; 	if (!irqchip_in_kernel(kvm))		return r; 	mutex_lock(&kvm->lock);	r = -ENODEV;	match = kvm_find_assigned_dev(&kvm->arch.assigned_dev_head,				      assigned_irq->assigned_dev_id);	if (!match)		goto out; 	host_irq_type = (assigned_irq->flags & KVM_DEV_IRQ_HOST_MASK);	guest_irq_type = (assigned_irq->flags & KVM_DEV_IRQ_GUEST_MASK); 	r = -EINVAL;	/* can only assign one type at a time */	if (hweight_long(host_irq_type) > 1)		goto out;	if (hweight_long(guest_irq_type) > 1)		goto out;	if (host_irq_type == 0 && guest_irq_type == 0)		goto out; 	r = 0;	if (host_irq_type)		r = assign_host_irq(kvm, match, host_irq_type);	if (r)		goto out; 	if (guest_irq_type)		r = assign_guest_irq(kvm, match, assigned_irq, guest_irq_type);out:	mutex_unlock(&kvm->lock);	return r;} static int kvm_vm_ioctl_deassign_dev_irq(struct kvm *kvm,					 struct kvm_assigned_irq					 *assigned_irq){	int r = -ENODEV;	struct kvm_assigned_dev_kernel *match; 	mutex_lock(&kvm->lock); 	match = kvm_find_assigned_dev(&kvm->arch.assigned_dev_head,				      assigned_irq->assigned_dev_id);	if (!match)		goto out; 	r = kvm_deassign_irq(kvm, match, assigned_irq->flags);out:	mutex_unlock(&kvm->lock);	return r;} /* * We want to test whether the caller has been granted permissions to * use this device.  To be able to configure and control the device, * the user needs access to PCI configuration space and BAR resources. * These are accessed through PCI sysfs.  PCI config space is often * passed to the process calling this ioctl via file descriptor, so we * can't rely on access to that file.  We can check for permissions * on each of the BAR resource files, which is a pretty clear * indicator that the user has been granted access to the device. */static int probe_sysfs_permissions(struct pci_dev *dev){#ifdef CONFIG_SYSFS	int i;	bool bar_found = false; 

Lines 1–500 of 878.