328 lines
7.6 KiB
C
328 lines
7.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2019-2020 HUAWEI TECHNOLOGIES CO., LTD., All Rights Reserved.
|
|
* Author: Wanghaibin <wanghaibin.wang@huawei.com>
|
|
*/
|
|
|
|
#include <linux/irq.h>
|
|
#include <linux/kvm.h>
|
|
#include <linux/kvm_host.h>
|
|
#include <linux/module.h>
|
|
#include <linux/msi.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
static struct workqueue_struct *sdev_cleanup_wq;
|
|
static bool virt_msi_bypass;
|
|
bool sdev_enable;
|
|
|
|
static void shadow_dev_destroy(struct work_struct *work);
|
|
static void sdev_virt_pdev_delete(struct platform_device *pdev);
|
|
|
|
int shadow_dev_virq_bypass_inject(struct kvm *kvm,
|
|
struct kvm_kernel_irq_routing_entry *e)
|
|
{
|
|
struct shadow_dev *sdev = e->cache.data;
|
|
u32 vec = e->msi.data;
|
|
u32 host_irq = sdev->host_irq[vec];
|
|
int ret;
|
|
|
|
ret = irq_set_irqchip_state(host_irq, IRQCHIP_STATE_PENDING, true);
|
|
WARN_RATELIMIT(ret, "IRQ %d", host_irq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Must be called with the dist->sdev_list_lock held */
|
|
struct shadow_dev *kvm_shadow_dev_get(struct kvm *kvm, struct kvm_msi *msi)
|
|
{
|
|
struct vgic_dist *dist = &kvm->arch.vgic;
|
|
struct shadow_dev *sdev;
|
|
|
|
if (!sdev_enable)
|
|
return NULL;
|
|
|
|
list_for_each_entry(sdev, &dist->sdev_list_head, entry) {
|
|
if (sdev->devid != msi->devid)
|
|
continue;
|
|
|
|
if (sdev->nvecs <= msi->data ||
|
|
!test_bit(msi->data, sdev->enable))
|
|
break;
|
|
|
|
return sdev;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct platform_device *sdev_virt_pdev_add(u32 nvec)
|
|
{
|
|
struct platform_device *virtdev;
|
|
int ret = -ENOMEM;
|
|
|
|
virtdev = platform_device_alloc("virt_plat_dev", PLATFORM_DEVID_AUTO);
|
|
if (!virtdev) {
|
|
kvm_err("Allocate virtual platform device failed\n");
|
|
goto out;
|
|
}
|
|
|
|
dev_set_drvdata(&virtdev->dev, &nvec);
|
|
|
|
ret = platform_device_add(virtdev);
|
|
if (ret) {
|
|
kvm_err("Add virtual platform device failed (%d)\n", ret);
|
|
goto put_device;
|
|
}
|
|
|
|
return virtdev;
|
|
|
|
put_device:
|
|
platform_device_put(virtdev);
|
|
out:
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static void sdev_set_irq_entry(struct shadow_dev *sdev,
|
|
struct kvm_kernel_irq_routing_entry *irq_entries)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sdev->nvecs; i++) {
|
|
irq_entries[i].msi.address_lo = sdev->msi[i].address_lo;
|
|
irq_entries[i].msi.address_hi = sdev->msi[i].address_hi;
|
|
irq_entries[i].msi.data = sdev->msi[i].data;
|
|
irq_entries[i].msi.flags = sdev->msi[i].flags;
|
|
irq_entries[i].msi.devid = sdev->msi[i].devid;
|
|
}
|
|
}
|
|
|
|
static int sdev_virq_bypass_active(struct kvm *kvm, struct shadow_dev *sdev)
|
|
{
|
|
struct kvm_kernel_irq_routing_entry *irq_entries;
|
|
struct msi_desc *desc;
|
|
u32 vec = 0;
|
|
|
|
sdev->host_irq = kcalloc(sdev->nvecs, sizeof(int), GFP_KERNEL);
|
|
sdev->enable = bitmap_zalloc(sdev->nvecs, GFP_KERNEL);
|
|
irq_entries = kcalloc(sdev->nvecs,
|
|
sizeof(struct kvm_kernel_irq_routing_entry),
|
|
GFP_KERNEL);
|
|
|
|
if (!irq_entries || !sdev->enable || !sdev->host_irq) {
|
|
kfree(sdev->host_irq);
|
|
kfree(sdev->enable);
|
|
kfree(irq_entries);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
sdev_set_irq_entry(sdev, irq_entries);
|
|
|
|
msi_for_each_desc(desc, &sdev->pdev->dev, MSI_DESC_ALL) {
|
|
if (!kvm_vgic_v4_set_forwarding(kvm, desc->irq,
|
|
&irq_entries[vec])) {
|
|
set_bit(vec, sdev->enable);
|
|
sdev->host_irq[vec] = desc->irq;
|
|
} else {
|
|
/*
|
|
* Can not use shadow device for direct injection,
|
|
* though not fatal...
|
|
*/
|
|
kvm_err("Shadow device set (%d) forwarding failed",
|
|
desc->irq);
|
|
}
|
|
vec++;
|
|
}
|
|
|
|
kfree(irq_entries);
|
|
return 0;
|
|
}
|
|
|
|
static void sdev_msi_entry_init(struct kvm_master_dev_info *mdi,
|
|
struct shadow_dev *sdev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sdev->nvecs; i++) {
|
|
sdev->msi[i].address_lo = mdi->msi[i].address_lo;
|
|
sdev->msi[i].address_hi = mdi->msi[i].address_hi;
|
|
sdev->msi[i].data = mdi->msi[i].data;
|
|
sdev->msi[i].flags = mdi->msi[i].flags;
|
|
sdev->msi[i].devid = mdi->msi[i].devid;
|
|
}
|
|
}
|
|
|
|
int kvm_shadow_dev_create(struct kvm *kvm, struct kvm_master_dev_info *mdi)
|
|
{
|
|
struct vgic_dist *dist = &kvm->arch.vgic;
|
|
struct shadow_dev *sdev;
|
|
struct kvm_msi *msi;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
if (WARN_ON(!sdev_enable))
|
|
return -EINVAL;
|
|
|
|
ret = -ENOMEM;
|
|
sdev = kzalloc(sizeof(struct shadow_dev), GFP_KERNEL);
|
|
if (!sdev)
|
|
return ret;
|
|
|
|
sdev->nvecs = mdi->nvectors;
|
|
|
|
msi = kcalloc(sdev->nvecs, sizeof(struct kvm_msi), GFP_KERNEL);
|
|
if (!msi)
|
|
goto free_sdev;
|
|
|
|
sdev->msi = msi;
|
|
sdev_msi_entry_init(mdi, sdev);
|
|
sdev->devid = sdev->msi[0].devid;
|
|
|
|
sdev->pdev = sdev_virt_pdev_add(sdev->nvecs);
|
|
if (IS_ERR(sdev->pdev)) {
|
|
ret = PTR_ERR(sdev->pdev);
|
|
goto free_sdev_msi;
|
|
}
|
|
|
|
ret = sdev_virq_bypass_active(kvm, sdev);
|
|
if (ret)
|
|
goto delete_virtdev;
|
|
|
|
sdev->kvm = kvm;
|
|
INIT_WORK(&sdev->destroy, shadow_dev_destroy);
|
|
|
|
raw_spin_lock_irqsave(&dist->sdev_list_lock, flags);
|
|
list_add_tail(&sdev->entry, &dist->sdev_list_head);
|
|
raw_spin_unlock_irqrestore(&dist->sdev_list_lock, flags);
|
|
|
|
kvm_info("Create shadow device: 0x%x\n", sdev->devid);
|
|
return ret;
|
|
|
|
delete_virtdev:
|
|
sdev_virt_pdev_delete(sdev->pdev);
|
|
free_sdev_msi:
|
|
kfree(sdev->msi);
|
|
free_sdev:
|
|
kfree(sdev);
|
|
return ret;
|
|
}
|
|
|
|
static void sdev_virt_pdev_delete(struct platform_device *pdev)
|
|
{
|
|
platform_device_unregister(pdev);
|
|
}
|
|
|
|
static void sdev_virq_bypass_deactive(struct kvm *kvm, struct shadow_dev *sdev)
|
|
{
|
|
struct kvm_kernel_irq_routing_entry *irq_entries;
|
|
struct msi_desc *desc;
|
|
u32 vec = 0;
|
|
|
|
irq_entries = kcalloc(sdev->nvecs,
|
|
sizeof(struct kvm_kernel_irq_routing_entry),
|
|
GFP_KERNEL);
|
|
if (!irq_entries)
|
|
return;
|
|
|
|
sdev_set_irq_entry(sdev, irq_entries);
|
|
|
|
msi_for_each_desc(desc, &sdev->pdev->dev, MSI_DESC_ALL) {
|
|
if (!kvm_vgic_v4_unset_forwarding(kvm, desc->irq,
|
|
&irq_entries[vec])) {
|
|
clear_bit(vec, sdev->enable);
|
|
sdev->host_irq[vec] = 0;
|
|
} else {
|
|
kvm_err("Shadow device unset (%d) forwarding failed",
|
|
desc->irq);
|
|
}
|
|
vec++;
|
|
}
|
|
|
|
kfree(sdev->host_irq);
|
|
kfree(sdev->enable);
|
|
kfree(irq_entries);
|
|
|
|
/* FIXME: no error handling */
|
|
}
|
|
|
|
static void shadow_dev_destroy(struct work_struct *work)
|
|
{
|
|
struct shadow_dev *sdev = container_of(work, struct shadow_dev, destroy);
|
|
struct kvm *kvm = sdev->kvm;
|
|
|
|
sdev_virq_bypass_deactive(kvm, sdev);
|
|
sdev_virt_pdev_delete(sdev->pdev);
|
|
|
|
sdev->nvecs = 0;
|
|
kfree(sdev->msi);
|
|
kfree(sdev);
|
|
}
|
|
|
|
void kvm_shadow_dev_delete(struct kvm *kvm, u32 devid)
|
|
{
|
|
struct vgic_dist *dist = &kvm->arch.vgic;
|
|
struct shadow_dev *sdev, *tmp;
|
|
unsigned long flags;
|
|
|
|
if (WARN_ON(!sdev_enable))
|
|
return;
|
|
|
|
raw_spin_lock_irqsave(&dist->sdev_list_lock, flags);
|
|
WARN_ON(list_empty(&dist->sdev_list_head)); /* shouldn't be invoked */
|
|
|
|
list_for_each_entry_safe(sdev, tmp, &dist->sdev_list_head, entry) {
|
|
if (sdev->devid != devid)
|
|
continue;
|
|
|
|
list_del(&sdev->entry);
|
|
queue_work(sdev_cleanup_wq, &sdev->destroy);
|
|
break;
|
|
}
|
|
raw_spin_unlock_irqrestore(&dist->sdev_list_lock, flags);
|
|
|
|
flush_workqueue(sdev_cleanup_wq);
|
|
}
|
|
|
|
void kvm_shadow_dev_delete_all(struct kvm *kvm)
|
|
{
|
|
struct vgic_dist *dist = &kvm->arch.vgic;
|
|
struct shadow_dev *sdev, *tmp;
|
|
unsigned long flags;
|
|
|
|
if (!sdev_enable)
|
|
return;
|
|
|
|
raw_spin_lock_irqsave(&dist->sdev_list_lock, flags);
|
|
|
|
list_for_each_entry_safe(sdev, tmp, &dist->sdev_list_head, entry) {
|
|
list_del(&sdev->entry);
|
|
queue_work(sdev_cleanup_wq, &sdev->destroy);
|
|
}
|
|
|
|
raw_spin_unlock_irqrestore(&dist->sdev_list_lock, flags);
|
|
|
|
flush_workqueue(sdev_cleanup_wq);
|
|
}
|
|
|
|
static int __init early_virt_msi_bypass(char *buf)
|
|
{
|
|
return strtobool(buf, &virt_msi_bypass);
|
|
}
|
|
early_param("kvm-arm.virt_msi_bypass", early_virt_msi_bypass);
|
|
|
|
void kvm_shadow_dev_init(void)
|
|
{
|
|
/*
|
|
* FIXME: Ideally shadow device should only rely on a GICv4.0
|
|
* capable ITS, but we should also take the reserved device ID
|
|
* pools into account.
|
|
*/
|
|
sdev_enable = kvm_vgic_global_state.has_gicv4 && virt_msi_bypass;
|
|
|
|
sdev_cleanup_wq = alloc_workqueue("kvm-sdev-cleanup", 0, 0);
|
|
if (!sdev_cleanup_wq)
|
|
sdev_enable = false;
|
|
|
|
kvm_info("Shadow device %sabled\n", sdev_enable ? "en" : "dis");
|
|
}
|