mirror of
https://github.com/torvalds/linux.git
synced 2024-12-30 14:52:05 +00:00
571ee1b685
After commit80ce163
(KVM: VFIO: register kvm_device_ops dynamically), kvm_device_ops of vfio can be registered dynamically. Commit3c3c29fd
(kvm-vfio: do not use module_init) move the dynamic register invoked by kvm_init in order to fix broke unloading of the kvm module. However, kvm_device_ops of vfio is unregistered after rmmod kvm-intel module which lead to device type collision detection warning after kvm-intel module reinsmod. WARNING: CPU: 1 PID: 10358 at /root/cathy/kvm/arch/x86/kvm/../../../virt/kvm/kvm_main.c:3289 kvm_init+0x234/0x282 [kvm]() Modules linked in: kvm_intel(O+) kvm(O) nfsv3 nfs_acl auth_rpcgss oid_registry nfsv4 dns_resolver nfs fscache lockd sunrpc pci_stub bridge stp llc autofs4 8021q cpufreq_ondemand ipv6 joydev microcode pcspkr igb i2c_algo_bit ehci_pci ehci_hcd e1000e i2c_i801 ixgbe ptp pps_core hwmon mdio tpm_tis tpm ipmi_si ipmi_msghandler acpi_cpufreq isci libsas scsi_transport_sas button dm_mirror dm_region_hash dm_log dm_mod [last unloaded: kvm_intel] CPU: 1 PID: 10358 Comm: insmod Tainted: G W O 3.17.0-rc1 #2 Hardware name: Intel Corporation S2600CP/S2600CP, BIOS RMLSDP.86I.00.29.D696.1311111329 11/11/2013 0000000000000cd9 ffff880ff08cfd18 ffffffff814a61d9 0000000000000cd9 0000000000000000 ffff880ff08cfd58 ffffffff810417b7 ffff880ff08cfd48 ffffffffa045bcac ffffffffa049c420 0000000000000040 00000000000000ff Call Trace: [<ffffffff814a61d9>] dump_stack+0x49/0x60 [<ffffffff810417b7>] warn_slowpath_common+0x7c/0x96 [<ffffffffa045bcac>] ? kvm_init+0x234/0x282 [kvm] [<ffffffff810417e6>] warn_slowpath_null+0x15/0x17 [<ffffffffa045bcac>] kvm_init+0x234/0x282 [kvm] [<ffffffffa016e995>] vmx_init+0x1bf/0x42a [kvm_intel] [<ffffffffa016e7d6>] ? vmx_check_processor_compat+0x64/0x64 [kvm_intel] [<ffffffff810002ab>] do_one_initcall+0xe3/0x170 [<ffffffff811168a9>] ? __vunmap+0xad/0xb8 [<ffffffff8109c58f>] do_init_module+0x2b/0x174 [<ffffffff8109d414>] load_module+0x43e/0x569 [<ffffffff8109c6d8>] ? do_init_module+0x174/0x174 [<ffffffff8109c75a>] ? copy_module_from_user+0x39/0x82 [<ffffffff8109b7dd>] ? module_sect_show+0x20/0x20 [<ffffffff8109d65f>] SyS_init_module+0x54/0x81 [<ffffffff814a9a12>] system_call_fastpath+0x16/0x1b ---[ end trace 0626f4a3ddea56f3 ]--- The bug can be reproduced by: rmmod kvm_intel.ko insmod kvm_intel.ko without rmmod/insmod kvm.ko This patch fixes the bug by unregistering kvm_device_ops of vfio when the kvm-intel module is removed. Reported-by: Liu Rongrong <rongrongx.liu@intel.com> Fixes:3c3c29fd0d
Signed-off-by: Wanpeng Li <wanpeng.li@linux.intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
291 lines
6.0 KiB
C
291 lines
6.0 KiB
C
/*
|
|
* VFIO-KVM bridge pseudo device
|
|
*
|
|
* Copyright (C) 2013 Red Hat, Inc. All rights reserved.
|
|
* Author: Alex Williamson <alex.williamson@redhat.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/file.h>
|
|
#include <linux/kvm_host.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/vfio.h>
|
|
#include "vfio.h"
|
|
|
|
struct kvm_vfio_group {
|
|
struct list_head node;
|
|
struct vfio_group *vfio_group;
|
|
};
|
|
|
|
struct kvm_vfio {
|
|
struct list_head group_list;
|
|
struct mutex lock;
|
|
bool noncoherent;
|
|
};
|
|
|
|
static struct vfio_group *kvm_vfio_group_get_external_user(struct file *filep)
|
|
{
|
|
struct vfio_group *vfio_group;
|
|
struct vfio_group *(*fn)(struct file *);
|
|
|
|
fn = symbol_get(vfio_group_get_external_user);
|
|
if (!fn)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
vfio_group = fn(filep);
|
|
|
|
symbol_put(vfio_group_get_external_user);
|
|
|
|
return vfio_group;
|
|
}
|
|
|
|
static void kvm_vfio_group_put_external_user(struct vfio_group *vfio_group)
|
|
{
|
|
void (*fn)(struct vfio_group *);
|
|
|
|
fn = symbol_get(vfio_group_put_external_user);
|
|
if (!fn)
|
|
return;
|
|
|
|
fn(vfio_group);
|
|
|
|
symbol_put(vfio_group_put_external_user);
|
|
}
|
|
|
|
static bool kvm_vfio_group_is_coherent(struct vfio_group *vfio_group)
|
|
{
|
|
long (*fn)(struct vfio_group *, unsigned long);
|
|
long ret;
|
|
|
|
fn = symbol_get(vfio_external_check_extension);
|
|
if (!fn)
|
|
return false;
|
|
|
|
ret = fn(vfio_group, VFIO_DMA_CC_IOMMU);
|
|
|
|
symbol_put(vfio_external_check_extension);
|
|
|
|
return ret > 0;
|
|
}
|
|
|
|
/*
|
|
* Groups can use the same or different IOMMU domains. If the same then
|
|
* adding a new group may change the coherency of groups we've previously
|
|
* been told about. We don't want to care about any of that so we retest
|
|
* each group and bail as soon as we find one that's noncoherent. This
|
|
* means we only ever [un]register_noncoherent_dma once for the whole device.
|
|
*/
|
|
static void kvm_vfio_update_coherency(struct kvm_device *dev)
|
|
{
|
|
struct kvm_vfio *kv = dev->private;
|
|
bool noncoherent = false;
|
|
struct kvm_vfio_group *kvg;
|
|
|
|
mutex_lock(&kv->lock);
|
|
|
|
list_for_each_entry(kvg, &kv->group_list, node) {
|
|
if (!kvm_vfio_group_is_coherent(kvg->vfio_group)) {
|
|
noncoherent = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (noncoherent != kv->noncoherent) {
|
|
kv->noncoherent = noncoherent;
|
|
|
|
if (kv->noncoherent)
|
|
kvm_arch_register_noncoherent_dma(dev->kvm);
|
|
else
|
|
kvm_arch_unregister_noncoherent_dma(dev->kvm);
|
|
}
|
|
|
|
mutex_unlock(&kv->lock);
|
|
}
|
|
|
|
static int kvm_vfio_set_group(struct kvm_device *dev, long attr, u64 arg)
|
|
{
|
|
struct kvm_vfio *kv = dev->private;
|
|
struct vfio_group *vfio_group;
|
|
struct kvm_vfio_group *kvg;
|
|
int32_t __user *argp = (int32_t __user *)(unsigned long)arg;
|
|
struct fd f;
|
|
int32_t fd;
|
|
int ret;
|
|
|
|
switch (attr) {
|
|
case KVM_DEV_VFIO_GROUP_ADD:
|
|
if (get_user(fd, argp))
|
|
return -EFAULT;
|
|
|
|
f = fdget(fd);
|
|
if (!f.file)
|
|
return -EBADF;
|
|
|
|
vfio_group = kvm_vfio_group_get_external_user(f.file);
|
|
fdput(f);
|
|
|
|
if (IS_ERR(vfio_group))
|
|
return PTR_ERR(vfio_group);
|
|
|
|
mutex_lock(&kv->lock);
|
|
|
|
list_for_each_entry(kvg, &kv->group_list, node) {
|
|
if (kvg->vfio_group == vfio_group) {
|
|
mutex_unlock(&kv->lock);
|
|
kvm_vfio_group_put_external_user(vfio_group);
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
kvg = kzalloc(sizeof(*kvg), GFP_KERNEL);
|
|
if (!kvg) {
|
|
mutex_unlock(&kv->lock);
|
|
kvm_vfio_group_put_external_user(vfio_group);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
list_add_tail(&kvg->node, &kv->group_list);
|
|
kvg->vfio_group = vfio_group;
|
|
|
|
mutex_unlock(&kv->lock);
|
|
|
|
kvm_vfio_update_coherency(dev);
|
|
|
|
return 0;
|
|
|
|
case KVM_DEV_VFIO_GROUP_DEL:
|
|
if (get_user(fd, argp))
|
|
return -EFAULT;
|
|
|
|
f = fdget(fd);
|
|
if (!f.file)
|
|
return -EBADF;
|
|
|
|
vfio_group = kvm_vfio_group_get_external_user(f.file);
|
|
fdput(f);
|
|
|
|
if (IS_ERR(vfio_group))
|
|
return PTR_ERR(vfio_group);
|
|
|
|
ret = -ENOENT;
|
|
|
|
mutex_lock(&kv->lock);
|
|
|
|
list_for_each_entry(kvg, &kv->group_list, node) {
|
|
if (kvg->vfio_group != vfio_group)
|
|
continue;
|
|
|
|
list_del(&kvg->node);
|
|
kvm_vfio_group_put_external_user(kvg->vfio_group);
|
|
kfree(kvg);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&kv->lock);
|
|
|
|
kvm_vfio_group_put_external_user(vfio_group);
|
|
|
|
kvm_vfio_update_coherency(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
return -ENXIO;
|
|
}
|
|
|
|
static int kvm_vfio_set_attr(struct kvm_device *dev,
|
|
struct kvm_device_attr *attr)
|
|
{
|
|
switch (attr->group) {
|
|
case KVM_DEV_VFIO_GROUP:
|
|
return kvm_vfio_set_group(dev, attr->attr, attr->addr);
|
|
}
|
|
|
|
return -ENXIO;
|
|
}
|
|
|
|
static int kvm_vfio_has_attr(struct kvm_device *dev,
|
|
struct kvm_device_attr *attr)
|
|
{
|
|
switch (attr->group) {
|
|
case KVM_DEV_VFIO_GROUP:
|
|
switch (attr->attr) {
|
|
case KVM_DEV_VFIO_GROUP_ADD:
|
|
case KVM_DEV_VFIO_GROUP_DEL:
|
|
return 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return -ENXIO;
|
|
}
|
|
|
|
static void kvm_vfio_destroy(struct kvm_device *dev)
|
|
{
|
|
struct kvm_vfio *kv = dev->private;
|
|
struct kvm_vfio_group *kvg, *tmp;
|
|
|
|
list_for_each_entry_safe(kvg, tmp, &kv->group_list, node) {
|
|
kvm_vfio_group_put_external_user(kvg->vfio_group);
|
|
list_del(&kvg->node);
|
|
kfree(kvg);
|
|
}
|
|
|
|
kvm_vfio_update_coherency(dev);
|
|
|
|
kfree(kv);
|
|
kfree(dev); /* alloc by kvm_ioctl_create_device, free by .destroy */
|
|
}
|
|
|
|
static int kvm_vfio_create(struct kvm_device *dev, u32 type);
|
|
|
|
static struct kvm_device_ops kvm_vfio_ops = {
|
|
.name = "kvm-vfio",
|
|
.create = kvm_vfio_create,
|
|
.destroy = kvm_vfio_destroy,
|
|
.set_attr = kvm_vfio_set_attr,
|
|
.has_attr = kvm_vfio_has_attr,
|
|
};
|
|
|
|
static int kvm_vfio_create(struct kvm_device *dev, u32 type)
|
|
{
|
|
struct kvm_device *tmp;
|
|
struct kvm_vfio *kv;
|
|
|
|
/* Only one VFIO "device" per VM */
|
|
list_for_each_entry(tmp, &dev->kvm->devices, vm_node)
|
|
if (tmp->ops == &kvm_vfio_ops)
|
|
return -EBUSY;
|
|
|
|
kv = kzalloc(sizeof(*kv), GFP_KERNEL);
|
|
if (!kv)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&kv->group_list);
|
|
mutex_init(&kv->lock);
|
|
|
|
dev->private = kv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kvm_vfio_ops_init(void)
|
|
{
|
|
return kvm_register_device_ops(&kvm_vfio_ops, KVM_DEV_TYPE_VFIO);
|
|
}
|
|
|
|
void kvm_vfio_ops_exit(void)
|
|
{
|
|
kvm_unregister_device_ops(KVM_DEV_TYPE_VFIO);
|
|
}
|