QEMU main repository: Please see https://www.qemu.org/docs/master/devel/submitting-a-patch.html for how to submit changes to QEMU. Pull Requests are ignored. Please only use release tarballs from the QEMU website. http://www.qemu.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
777 lines
25 KiB
777 lines
25 KiB
/*
|
|
* Copyright (c) 2025 Huawei Technologies R & D (UK) Ltd
|
|
* Copyright (C) 2025 NVIDIA
|
|
* Written by Nicolin Chen, Shameer Kolothum
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/error-report.h"
|
|
#include "trace.h"
|
|
|
|
#include "hw/arm/smmuv3.h"
|
|
#include "hw/core/iommu.h"
|
|
#include "hw/pci/pci_bridge.h"
|
|
#include "hw/pci-host/gpex.h"
|
|
#include "hw/vfio/pci.h"
|
|
|
|
#include "smmuv3-internal.h"
|
|
#include "smmuv3-accel.h"
|
|
|
|
/*
|
|
* The root region aliases the global system memory, and shared_as_sysmem
|
|
* provides a shared Address Space referencing it. This Address Space is used
|
|
* by all vfio-pci devices behind all accelerated SMMUv3 instances within a VM.
|
|
*/
|
|
static MemoryRegion root, sysmem;
|
|
static AddressSpace *shared_as_sysmem;
|
|
|
|
static int smmuv3_oas_bits(uint32_t oas)
|
|
{
|
|
static const int map[] = { 32, 36, 40, 42, 44, 48, 52, 56 };
|
|
|
|
g_assert(oas < ARRAY_SIZE(map));
|
|
return map[oas];
|
|
}
|
|
|
|
static bool
|
|
smmuv3_accel_check_hw_compatible(SMMUv3State *s,
|
|
struct iommu_hw_info_arm_smmuv3 *info,
|
|
Error **errp)
|
|
{
|
|
/* QEMU SMMUv3 supports both linear and 2-level stream tables */
|
|
if (FIELD_EX32(info->idr[0], IDR0, STLEVEL) !=
|
|
FIELD_EX32(s->idr[0], IDR0, STLEVEL)) {
|
|
error_setg(errp, "Host SMMUv3 Stream Table format mismatch "
|
|
"(host STLEVEL=%u, QEMU STLEVEL=%u)",
|
|
FIELD_EX32(info->idr[0], IDR0, STLEVEL),
|
|
FIELD_EX32(s->idr[0], IDR0, STLEVEL));
|
|
return false;
|
|
}
|
|
|
|
/* QEMU SMMUv3 supports only little-endian translation table walks */
|
|
if (FIELD_EX32(info->idr[0], IDR0, TTENDIAN) >
|
|
FIELD_EX32(s->idr[0], IDR0, TTENDIAN)) {
|
|
error_setg(errp, "Host SMMUv3 doesn't support Little-endian "
|
|
"translation table");
|
|
return false;
|
|
}
|
|
|
|
/* QEMU SMMUv3 supports only AArch64 translation table format */
|
|
if (FIELD_EX32(info->idr[0], IDR0, TTF) <
|
|
FIELD_EX32(s->idr[0], IDR0, TTF)) {
|
|
error_setg(errp, "Host SMMUv3 doesn't support AArch64 translation "
|
|
"table format");
|
|
return false;
|
|
}
|
|
|
|
/* QEMU SMMUv3 supports SIDSIZE 16 */
|
|
if (FIELD_EX32(info->idr[1], IDR1, SIDSIZE) <
|
|
FIELD_EX32(s->idr[1], IDR1, SIDSIZE)) {
|
|
error_setg(errp, "Host SMMUv3 SIDSIZE not compatible "
|
|
"(host=%u, QEMU=%u)",
|
|
FIELD_EX32(info->idr[1], IDR1, SIDSIZE),
|
|
FIELD_EX32(s->idr[1], IDR1, SIDSIZE));
|
|
return false;
|
|
}
|
|
|
|
/* Check SSIDSIZE value opted-in is compatible with Host SMMUv3 SSIDSIZE */
|
|
if (FIELD_EX32(info->idr[1], IDR1, SSIDSIZE) <
|
|
FIELD_EX32(s->idr[1], IDR1, SSIDSIZE)) {
|
|
error_setg(errp, "Host SMMUv3 SSIDSIZE not compatible "
|
|
"(host=%u, QEMU=%u)",
|
|
FIELD_EX32(info->idr[1], IDR1, SSIDSIZE),
|
|
FIELD_EX32(s->idr[1], IDR1, SSIDSIZE));
|
|
return false;
|
|
}
|
|
|
|
/* User can disable QEMU SMMUv3 Range Invalidation support */
|
|
if (FIELD_EX32(info->idr[3], IDR3, RIL) <
|
|
FIELD_EX32(s->idr[3], IDR3, RIL)) {
|
|
error_setg(errp, "Host SMMUv3 doesn't support Range Invalidation");
|
|
return false;
|
|
}
|
|
/* Check OAS value opted is compatible with Host SMMUv3 IPA */
|
|
if (FIELD_EX32(info->idr[5], IDR5, OAS) <
|
|
FIELD_EX32(s->idr[5], IDR5, OAS)) {
|
|
error_setg(errp, "Host SMMUv3 supports only %d-bit IPA, but the vSMMU "
|
|
"OAS implies %d-bit IPA",
|
|
smmuv3_oas_bits(FIELD_EX32(info->idr[5], IDR5, OAS)),
|
|
smmuv3_oas_bits(FIELD_EX32(s->idr[5], IDR5, OAS)));
|
|
return false;
|
|
}
|
|
|
|
/* QEMU SMMUv3 supports GRAN4K/GRAN16K/GRAN64K translation granules */
|
|
if (FIELD_EX32(info->idr[5], IDR5, GRAN4K) !=
|
|
FIELD_EX32(s->idr[5], IDR5, GRAN4K)) {
|
|
error_setg(errp, "Host SMMUv3 doesn't support 4K translation granule");
|
|
return false;
|
|
}
|
|
if (FIELD_EX32(info->idr[5], IDR5, GRAN16K) !=
|
|
FIELD_EX32(s->idr[5], IDR5, GRAN16K)) {
|
|
error_setg(errp, "Host SMMUv3 doesn't support 16K translation granule");
|
|
return false;
|
|
}
|
|
if (FIELD_EX32(info->idr[5], IDR5, GRAN64K) !=
|
|
FIELD_EX32(s->idr[5], IDR5, GRAN64K)) {
|
|
error_setg(errp, "Host SMMUv3 doesn't support 64K translation granule");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
smmuv3_accel_hw_compatible(SMMUv3State *s, HostIOMMUDeviceIOMMUFD *idev,
|
|
Error **errp)
|
|
{
|
|
struct iommu_hw_info_arm_smmuv3 info;
|
|
uint32_t data_type;
|
|
uint64_t caps;
|
|
|
|
if (!iommufd_backend_get_device_info(idev->iommufd, idev->devid, &data_type,
|
|
&info, sizeof(info), &caps, NULL,
|
|
errp)) {
|
|
return false;
|
|
}
|
|
|
|
if (data_type != IOMMU_HW_INFO_TYPE_ARM_SMMUV3) {
|
|
error_setg(errp, "Wrong data type (%d) for Host SMMUv3 device info",
|
|
data_type);
|
|
return false;
|
|
}
|
|
|
|
if (!smmuv3_accel_check_hw_compatible(s, &info, errp)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static SMMUv3AccelDevice *smmuv3_accel_get_dev(SMMUState *bs, SMMUPciBus *sbus,
|
|
PCIBus *bus, int devfn)
|
|
{
|
|
SMMUDevice *sdev = sbus->pbdev[devfn];
|
|
SMMUv3AccelDevice *accel_dev;
|
|
|
|
if (sdev) {
|
|
return container_of(sdev, SMMUv3AccelDevice, sdev);
|
|
}
|
|
|
|
accel_dev = g_new0(SMMUv3AccelDevice, 1);
|
|
sdev = &accel_dev->sdev;
|
|
|
|
sbus->pbdev[devfn] = sdev;
|
|
smmu_init_sdev(bs, sdev, bus, devfn);
|
|
return accel_dev;
|
|
}
|
|
|
|
static uint32_t smmuv3_accel_gbpa_hwpt(SMMUv3State *s, SMMUv3AccelState *accel)
|
|
{
|
|
return FIELD_EX32(s->gbpa, GBPA, ABORT) ?
|
|
accel->abort_hwpt_id : accel->bypass_hwpt_id;
|
|
}
|
|
|
|
static bool
|
|
smmuv3_accel_alloc_vdev(SMMUv3AccelDevice *accel_dev, int sid, Error **errp)
|
|
{
|
|
SMMUv3AccelState *accel = accel_dev->s_accel;
|
|
HostIOMMUDeviceIOMMUFD *idev = accel_dev->idev;
|
|
IOMMUFDVdev *vdev = accel_dev->vdev;
|
|
uint32_t vdevice_id;
|
|
|
|
if (!idev || vdev) {
|
|
return true;
|
|
}
|
|
|
|
if (!iommufd_backend_alloc_vdev(idev->iommufd, idev->devid,
|
|
accel->viommu->viommu_id, sid,
|
|
&vdevice_id, errp)) {
|
|
return false;
|
|
}
|
|
|
|
vdev = g_new(IOMMUFDVdev, 1);
|
|
vdev->vdevice_id = vdevice_id;
|
|
vdev->virt_id = sid;
|
|
accel_dev->vdev = vdev;
|
|
return true;
|
|
}
|
|
|
|
static SMMUS1Hwpt *
|
|
smmuv3_accel_dev_alloc_translate(SMMUv3AccelDevice *accel_dev, STE *ste,
|
|
Error **errp)
|
|
{
|
|
uint64_t ste_0 = (uint64_t)ste->word[0] | (uint64_t)ste->word[1] << 32;
|
|
uint64_t ste_1 = (uint64_t)ste->word[2] | (uint64_t)ste->word[3] << 32;
|
|
HostIOMMUDeviceIOMMUFD *idev = accel_dev->idev;
|
|
SMMUv3AccelState *accel = accel_dev->s_accel;
|
|
struct iommu_hwpt_arm_smmuv3 nested_data = {
|
|
.ste = {
|
|
cpu_to_le64(ste_0 & STE0_MASK),
|
|
cpu_to_le64(ste_1 & STE1_MASK),
|
|
},
|
|
};
|
|
uint32_t hwpt_id = 0, flags = 0;
|
|
SMMUS1Hwpt *s1_hwpt;
|
|
|
|
if (!iommufd_backend_alloc_hwpt(idev->iommufd, idev->devid,
|
|
accel->viommu->viommu_id, flags,
|
|
IOMMU_HWPT_DATA_ARM_SMMUV3,
|
|
sizeof(nested_data), &nested_data,
|
|
&hwpt_id, errp)) {
|
|
return NULL;
|
|
}
|
|
|
|
s1_hwpt = g_new0(SMMUS1Hwpt, 1);
|
|
s1_hwpt->hwpt_id = hwpt_id;
|
|
trace_smmuv3_accel_translate_ste(accel_dev->vdev->virt_id, hwpt_id,
|
|
nested_data.ste[1], nested_data.ste[0]);
|
|
return s1_hwpt;
|
|
}
|
|
|
|
bool smmuv3_accel_install_ste(SMMUv3State *s, SMMUDevice *sdev, int sid,
|
|
Error **errp)
|
|
{
|
|
SMMUEventInfo event = {.type = SMMU_EVT_NONE, .sid = sid,
|
|
.inval_ste_allowed = true};
|
|
SMMUv3AccelState *accel = s->s_accel;
|
|
SMMUv3AccelDevice *accel_dev;
|
|
HostIOMMUDeviceIOMMUFD *idev;
|
|
uint32_t config, hwpt_id = 0;
|
|
SMMUS1Hwpt *s1_hwpt = NULL;
|
|
const char *type;
|
|
STE ste;
|
|
|
|
if (!accel || !accel->viommu) {
|
|
return true;
|
|
}
|
|
|
|
accel_dev = container_of(sdev, SMMUv3AccelDevice, sdev);
|
|
if (!accel_dev->s_accel) {
|
|
return true;
|
|
}
|
|
|
|
idev = accel_dev->idev;
|
|
if (!smmuv3_accel_alloc_vdev(accel_dev, sid, errp)) {
|
|
return false;
|
|
}
|
|
|
|
if (smmu_find_ste(sdev->smmu, sid, &ste, &event)) {
|
|
/* No STE found, nothing to install */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Install the STE based on SMMU enabled/config:
|
|
* - attach a pre-allocated HWPT for abort/bypass
|
|
* - or a new HWPT for translate STE
|
|
*
|
|
* Note: The vdev remains associated with accel_dev even if HWPT
|
|
* attach/alloc fails, since the Guest–Host SID mapping stays
|
|
* valid as long as the device is behind the accelerated SMMUv3.
|
|
*/
|
|
if (!smmu_enabled(s)) {
|
|
hwpt_id = smmuv3_accel_gbpa_hwpt(s, accel);
|
|
} else {
|
|
config = STE_CONFIG(&ste);
|
|
|
|
if (!STE_VALID(&ste) || STE_CFG_ABORT(config)) {
|
|
hwpt_id = accel->abort_hwpt_id;
|
|
} else if (STE_CFG_BYPASS(config)) {
|
|
hwpt_id = accel->bypass_hwpt_id;
|
|
} else if (STE_CFG_S1_TRANSLATE(config)) {
|
|
s1_hwpt = smmuv3_accel_dev_alloc_translate(accel_dev, &ste, errp);
|
|
if (!s1_hwpt) {
|
|
return false;
|
|
}
|
|
hwpt_id = s1_hwpt->hwpt_id;
|
|
}
|
|
}
|
|
|
|
if (!hwpt_id) {
|
|
error_setg(errp, "Invalid STE config for sid 0x%x",
|
|
smmu_get_sid(&accel_dev->sdev));
|
|
return false;
|
|
}
|
|
|
|
if (!host_iommu_device_iommufd_attach_hwpt(idev, hwpt_id, errp)) {
|
|
if (s1_hwpt) {
|
|
iommufd_backend_free_id(idev->iommufd, s1_hwpt->hwpt_id);
|
|
g_free(s1_hwpt);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Free the previous s1_hwpt */
|
|
if (accel_dev->s1_hwpt) {
|
|
iommufd_backend_free_id(idev->iommufd, accel_dev->s1_hwpt->hwpt_id);
|
|
g_free(accel_dev->s1_hwpt);
|
|
}
|
|
|
|
accel_dev->s1_hwpt = s1_hwpt;
|
|
if (hwpt_id == accel->abort_hwpt_id) {
|
|
type = "abort";
|
|
} else if (hwpt_id == accel->bypass_hwpt_id) {
|
|
type = "bypass";
|
|
} else {
|
|
type = "translate";
|
|
}
|
|
|
|
trace_smmuv3_accel_install_ste(sid, type, hwpt_id);
|
|
return true;
|
|
}
|
|
|
|
bool smmuv3_accel_install_ste_range(SMMUv3State *s, SMMUSIDRange *range,
|
|
Error **errp)
|
|
{
|
|
SMMUv3AccelState *accel = s->s_accel;
|
|
SMMUv3AccelDevice *accel_dev;
|
|
Error *local_err = NULL;
|
|
bool all_ok = true;
|
|
|
|
if (!accel || !accel->viommu) {
|
|
return true;
|
|
}
|
|
|
|
QLIST_FOREACH(accel_dev, &accel->device_list, next) {
|
|
uint32_t sid = smmu_get_sid(&accel_dev->sdev);
|
|
|
|
if (sid >= range->start && sid <= range->end) {
|
|
if (!smmuv3_accel_install_ste(s, &accel_dev->sdev,
|
|
sid, &local_err)) {
|
|
error_append_hint(&local_err, "Device 0x%x: Failed to install "
|
|
"STE\n", sid);
|
|
error_report_err(local_err);
|
|
local_err = NULL;
|
|
all_ok = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!all_ok) {
|
|
error_setg(errp, "Failed to install all STEs properly");
|
|
}
|
|
return all_ok;
|
|
}
|
|
|
|
/*
|
|
* This issues the invalidation cmd to the host SMMUv3.
|
|
*
|
|
* sdev is non-NULL for SID based invalidations (e.g. CFGI_CD), and NULL for
|
|
* non SID invalidations such as SMMU_CMD_TLBI_NH_ASID and SMMU_CMD_TLBI_NH_VA.
|
|
*/
|
|
bool smmuv3_accel_issue_inv_cmd(SMMUv3State *bs, void *cmd, SMMUDevice *sdev,
|
|
Error **errp)
|
|
{
|
|
SMMUv3State *s = ARM_SMMUV3(bs);
|
|
SMMUv3AccelState *accel = s->s_accel;
|
|
uint32_t entry_num = 1;
|
|
|
|
/*
|
|
* No accel or viommu means no VFIO/IOMMUFD devices, nothing to
|
|
* invalidate.
|
|
*/
|
|
if (!accel || !accel->viommu) {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* SID based invalidations (e.g. CFGI_CD) apply only to vfio-pci endpoints
|
|
* with a valid vIOMMU vdev.
|
|
*/
|
|
if (sdev && !container_of(sdev, SMMUv3AccelDevice, sdev)->vdev) {
|
|
return true;
|
|
}
|
|
|
|
/* Single command (entry_num = 1); no need to check returned entry_num */
|
|
return iommufd_backend_invalidate_cache(
|
|
accel->viommu->iommufd, accel->viommu->viommu_id,
|
|
IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3,
|
|
sizeof(Cmd), &entry_num, cmd, errp);
|
|
}
|
|
|
|
static void smmuv3_accel_free_viommu(SMMUv3AccelState *accel)
|
|
{
|
|
IOMMUFDViommu *viommu = accel->viommu;
|
|
|
|
if (!viommu) {
|
|
return;
|
|
}
|
|
iommufd_backend_free_id(viommu->iommufd, accel->bypass_hwpt_id);
|
|
iommufd_backend_free_id(viommu->iommufd, accel->abort_hwpt_id);
|
|
iommufd_backend_free_id(viommu->iommufd, accel->viommu->viommu_id);
|
|
g_free(viommu);
|
|
accel->viommu = NULL;
|
|
}
|
|
|
|
static bool
|
|
smmuv3_accel_alloc_viommu(SMMUv3State *s, HostIOMMUDeviceIOMMUFD *idev,
|
|
Error **errp)
|
|
{
|
|
SMMUv3AccelState *accel = s->s_accel;
|
|
struct iommu_hwpt_arm_smmuv3 bypass_data = {
|
|
.ste = { SMMU_STE_CFG_BYPASS | SMMU_STE_VALID, 0x0ULL },
|
|
};
|
|
struct iommu_hwpt_arm_smmuv3 abort_data = {
|
|
.ste = { SMMU_STE_VALID, 0x0ULL },
|
|
};
|
|
uint32_t s2_hwpt_id = idev->hwpt_id;
|
|
uint32_t viommu_id, hwpt_id;
|
|
IOMMUFDViommu *viommu;
|
|
|
|
if (!iommufd_backend_alloc_viommu(idev->iommufd, idev->devid,
|
|
IOMMU_VIOMMU_TYPE_ARM_SMMUV3,
|
|
s2_hwpt_id, &viommu_id, errp)) {
|
|
return false;
|
|
}
|
|
|
|
viommu = g_new0(IOMMUFDViommu, 1);
|
|
viommu->viommu_id = viommu_id;
|
|
viommu->s2_hwpt_id = s2_hwpt_id;
|
|
viommu->iommufd = idev->iommufd;
|
|
|
|
/*
|
|
* Pre-allocate HWPTs for S1 bypass and abort cases. These will be attached
|
|
* later for guest STEs or GBPAs that require bypass or abort configuration.
|
|
*/
|
|
if (!iommufd_backend_alloc_hwpt(idev->iommufd, idev->devid, viommu_id,
|
|
0, IOMMU_HWPT_DATA_ARM_SMMUV3,
|
|
sizeof(abort_data), &abort_data,
|
|
&accel->abort_hwpt_id, errp)) {
|
|
goto free_viommu;
|
|
}
|
|
|
|
if (!iommufd_backend_alloc_hwpt(idev->iommufd, idev->devid, viommu_id,
|
|
0, IOMMU_HWPT_DATA_ARM_SMMUV3,
|
|
sizeof(bypass_data), &bypass_data,
|
|
&accel->bypass_hwpt_id, errp)) {
|
|
goto free_abort_hwpt;
|
|
}
|
|
|
|
/* Attach a HWPT based on SMMUv3 GBPA.ABORT value */
|
|
hwpt_id = smmuv3_accel_gbpa_hwpt(s, accel);
|
|
if (!host_iommu_device_iommufd_attach_hwpt(idev, hwpt_id, errp)) {
|
|
goto free_bypass_hwpt;
|
|
}
|
|
accel->viommu = viommu;
|
|
return true;
|
|
|
|
free_bypass_hwpt:
|
|
iommufd_backend_free_id(idev->iommufd, accel->bypass_hwpt_id);
|
|
free_abort_hwpt:
|
|
iommufd_backend_free_id(idev->iommufd, accel->abort_hwpt_id);
|
|
free_viommu:
|
|
iommufd_backend_free_id(idev->iommufd, viommu->viommu_id);
|
|
g_free(viommu);
|
|
return false;
|
|
}
|
|
|
|
static bool smmuv3_accel_set_iommu_device(PCIBus *bus, void *opaque, int devfn,
|
|
HostIOMMUDevice *hiod, Error **errp)
|
|
{
|
|
HostIOMMUDeviceIOMMUFD *idev = HOST_IOMMU_DEVICE_IOMMUFD(hiod);
|
|
SMMUState *bs = opaque;
|
|
SMMUv3State *s = ARM_SMMUV3(bs);
|
|
SMMUPciBus *sbus = smmu_get_sbus(bs, bus);
|
|
SMMUv3AccelDevice *accel_dev = smmuv3_accel_get_dev(bs, sbus, bus, devfn);
|
|
|
|
if (!idev) {
|
|
return true;
|
|
}
|
|
|
|
if (accel_dev->idev) {
|
|
if (accel_dev->idev != idev) {
|
|
error_setg(errp, "Device already has an associated idev 0x%x",
|
|
idev->devid);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Check the host SMMUv3 associated with the dev is compatible with the
|
|
* QEMU SMMUv3 accel.
|
|
*/
|
|
if (!smmuv3_accel_hw_compatible(s, idev, errp)) {
|
|
return false;
|
|
}
|
|
|
|
if (s->s_accel->viommu) {
|
|
goto done;
|
|
}
|
|
|
|
if (!smmuv3_accel_alloc_viommu(s, idev, errp)) {
|
|
error_append_hint(errp, "Unable to alloc vIOMMU: idev devid 0x%x: ",
|
|
idev->devid);
|
|
return false;
|
|
}
|
|
|
|
done:
|
|
accel_dev->idev = idev;
|
|
accel_dev->s_accel = s->s_accel;
|
|
QLIST_INSERT_HEAD(&s->s_accel->device_list, accel_dev, next);
|
|
trace_smmuv3_accel_set_iommu_device(devfn, idev->devid);
|
|
return true;
|
|
}
|
|
|
|
static void smmuv3_accel_unset_iommu_device(PCIBus *bus, void *opaque,
|
|
int devfn)
|
|
{
|
|
SMMUState *bs = opaque;
|
|
SMMUPciBus *sbus = g_hash_table_lookup(bs->smmu_pcibus_by_busptr, bus);
|
|
HostIOMMUDeviceIOMMUFD *idev;
|
|
SMMUv3AccelDevice *accel_dev;
|
|
SMMUv3AccelState *accel;
|
|
IOMMUFDVdev *vdev;
|
|
SMMUDevice *sdev;
|
|
|
|
if (!sbus) {
|
|
return;
|
|
}
|
|
|
|
sdev = sbus->pbdev[devfn];
|
|
if (!sdev) {
|
|
return;
|
|
}
|
|
|
|
accel_dev = container_of(sdev, SMMUv3AccelDevice, sdev);
|
|
idev = accel_dev->idev;
|
|
accel = accel_dev->s_accel;
|
|
/* Re-attach the default s2 hwpt id */
|
|
if (!host_iommu_device_iommufd_attach_hwpt(idev, idev->hwpt_id, NULL)) {
|
|
error_report("Unable to attach the default HW pagetable: idev devid "
|
|
"0x%x", idev->devid);
|
|
}
|
|
|
|
if (accel_dev->s1_hwpt) {
|
|
iommufd_backend_free_id(accel_dev->idev->iommufd,
|
|
accel_dev->s1_hwpt->hwpt_id);
|
|
g_free(accel_dev->s1_hwpt);
|
|
accel_dev->s1_hwpt = NULL;
|
|
}
|
|
|
|
vdev = accel_dev->vdev;
|
|
if (vdev) {
|
|
iommufd_backend_free_id(accel->viommu->iommufd, vdev->vdevice_id);
|
|
g_free(vdev);
|
|
accel_dev->vdev = NULL;
|
|
}
|
|
|
|
accel_dev->idev = NULL;
|
|
accel_dev->s_accel = NULL;
|
|
QLIST_REMOVE(accel_dev, next);
|
|
trace_smmuv3_accel_unset_iommu_device(devfn, idev->devid);
|
|
|
|
if (QLIST_EMPTY(&accel->device_list)) {
|
|
smmuv3_accel_free_viommu(accel);
|
|
}
|
|
}
|
|
|
|
static uint64_t smmuv3_accel_get_msi_gpa(PCIBus *bus, void *opaque, int devfn)
|
|
{
|
|
SMMUState *bs = opaque;
|
|
SMMUv3State *s = ARM_SMMUV3(bs);
|
|
|
|
g_assert(s->msi_gpa);
|
|
return s->msi_gpa;
|
|
}
|
|
|
|
/*
|
|
* Only allow PCIe bridges, pxb-pcie roots, and GPEX roots so vfio-pci
|
|
* endpoints can sit downstream. Accelerated SMMUv3 requires a vfio-pci
|
|
* endpoint using the iommufd backend; all other device types are rejected.
|
|
* This avoids supporting emulated endpoints, which would complicate IOTLB
|
|
* invalidation and hurt performance.
|
|
*/
|
|
static bool smmuv3_accel_pdev_allowed(PCIDevice *pdev, bool *vfio_pci)
|
|
{
|
|
|
|
if (object_dynamic_cast(OBJECT(pdev), TYPE_PCI_BRIDGE) ||
|
|
object_dynamic_cast(OBJECT(pdev), TYPE_PXB_PCIE_DEV) ||
|
|
object_dynamic_cast(OBJECT(pdev), TYPE_GPEX_ROOT_DEVICE)) {
|
|
return true;
|
|
} else if ((object_dynamic_cast(OBJECT(pdev), TYPE_VFIO_PCI))) {
|
|
*vfio_pci = true;
|
|
if (object_property_get_link(OBJECT(pdev), "iommufd", NULL)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool smmuv3_accel_supports_as(PCIBus *bus, void *opaque, int devfn,
|
|
Error **errp)
|
|
{
|
|
PCIDevice *pdev = pci_find_device(bus, pci_bus_num(bus), devfn);
|
|
bool vfio_pci = false;
|
|
|
|
if (pdev && !smmuv3_accel_pdev_allowed(pdev, &vfio_pci)) {
|
|
if (vfio_pci) {
|
|
error_setg(errp, "vfio-pci endpoint devices without an iommufd "
|
|
"backend not allowed when using arm-smmuv3,accel=on");
|
|
|
|
} else {
|
|
error_setg(errp, "Emulated endpoint devices are not allowed when "
|
|
"using arm-smmuv3,accel=on");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
/*
|
|
* Find or add an address space for the given PCI device.
|
|
*
|
|
* If a device matching @bus and @devfn already exists, return its
|
|
* corresponding address space. Otherwise, create a new device entry
|
|
* and initialize address space for it.
|
|
*/
|
|
static AddressSpace *smmuv3_accel_find_add_as(PCIBus *bus, void *opaque,
|
|
int devfn)
|
|
{
|
|
PCIDevice *pdev = pci_find_device(bus, pci_bus_num(bus), devfn);
|
|
SMMUState *bs = opaque;
|
|
SMMUPciBus *sbus = smmu_get_sbus(bs, bus);
|
|
SMMUv3AccelDevice *accel_dev = smmuv3_accel_get_dev(bs, sbus, bus, devfn);
|
|
SMMUDevice *sdev = &accel_dev->sdev;
|
|
bool vfio_pci = false;
|
|
|
|
if (pdev && !smmuv3_accel_pdev_allowed(pdev, &vfio_pci)) {
|
|
/* Should never be here: supports_address_space() filters these out */
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
/*
|
|
* In the accelerated mode, a vfio-pci device attached via the iommufd
|
|
* backend must remain in the system address space. Such a device is
|
|
* always translated by its physical SMMU (using either a stage-2-only
|
|
* STE or a nested STE), where the parent stage-2 page table is allocated
|
|
* by the VFIO core to back the system address space.
|
|
*
|
|
* Return the shared_as_sysmem aliased to the global system memory in this
|
|
* case. Sharing address_space_memory also allows devices under different
|
|
* vSMMU instances in the same VM to reuse a single nesting parent HWPT in
|
|
* the VFIO core.
|
|
*
|
|
* For non-endpoint emulated devices such as PCIe root ports and bridges,
|
|
* which may use the normal emulated translation path and software IOTLBs,
|
|
* return the SMMU's IOMMU address space.
|
|
*/
|
|
if (vfio_pci) {
|
|
return shared_as_sysmem;
|
|
} else {
|
|
return &sdev->as;
|
|
}
|
|
}
|
|
|
|
static uint64_t smmuv3_accel_get_viommu_flags(void *opaque)
|
|
{
|
|
/*
|
|
* We return VIOMMU_FLAG_WANT_NESTING_PARENT to inform VFIO core to create a
|
|
* nesting parent which is required for accelerated SMMUv3 support.
|
|
* The real HW nested support should be reported from host SMMUv3 and if
|
|
* it doesn't, the nesting parent allocation will fail anyway in VFIO core.
|
|
*/
|
|
uint64_t flags = VIOMMU_FLAG_WANT_NESTING_PARENT;
|
|
SMMUState *bs = opaque;
|
|
SMMUv3State *s = ARM_SMMUV3(bs);
|
|
|
|
if (s->ssidsize) {
|
|
flags |= VIOMMU_FLAG_PASID_SUPPORTED;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
static const PCIIOMMUOps smmuv3_accel_ops = {
|
|
.supports_address_space = smmuv3_accel_supports_as,
|
|
.get_address_space = smmuv3_accel_find_add_as,
|
|
.get_viommu_flags = smmuv3_accel_get_viommu_flags,
|
|
.set_iommu_device = smmuv3_accel_set_iommu_device,
|
|
.unset_iommu_device = smmuv3_accel_unset_iommu_device,
|
|
.get_msi_direct_gpa = smmuv3_accel_get_msi_gpa,
|
|
};
|
|
|
|
void smmuv3_accel_idr_override(SMMUv3State *s)
|
|
{
|
|
if (!s->accel) {
|
|
return;
|
|
}
|
|
|
|
/* By default QEMU SMMUv3 has RIL. Update IDR3 if user has disabled it */
|
|
s->idr[3] = FIELD_DP32(s->idr[3], IDR3, RIL, s->ril);
|
|
|
|
/* QEMU SMMUv3 has no ATS. Advertise ATS if opt-in by property */
|
|
s->idr[0] = FIELD_DP32(s->idr[0], IDR0, ATS, s->ats);
|
|
|
|
/* Advertise 48-bit OAS in IDR5 when requested (default is 44 bits). */
|
|
if (s->oas == SMMU_OAS_48BIT) {
|
|
s->idr[5] = FIELD_DP32(s->idr[5], IDR5, OAS, SMMU_IDR5_OAS_48);
|
|
}
|
|
|
|
/*
|
|
* By default QEMU SMMUv3 has no SubstreamID support. Update IDR1 if user
|
|
* has enabled it.
|
|
*/
|
|
s->idr[1] = FIELD_DP32(s->idr[1], IDR1, SSIDSIZE, s->ssidsize);
|
|
}
|
|
|
|
/* Based on SMUUv3 GPBA.ABORT configuration, attach a corresponding HWPT */
|
|
bool smmuv3_accel_attach_gbpa_hwpt(SMMUv3State *s, Error **errp)
|
|
{
|
|
SMMUv3AccelState *accel = s->s_accel;
|
|
SMMUv3AccelDevice *accel_dev;
|
|
Error *local_err = NULL;
|
|
bool all_ok = true;
|
|
uint32_t hwpt_id;
|
|
|
|
if (!accel || !accel->viommu) {
|
|
return true;
|
|
}
|
|
|
|
hwpt_id = smmuv3_accel_gbpa_hwpt(s, accel);
|
|
QLIST_FOREACH(accel_dev, &accel->device_list, next) {
|
|
if (!host_iommu_device_iommufd_attach_hwpt(accel_dev->idev, hwpt_id,
|
|
&local_err)) {
|
|
error_append_hint(&local_err, "Failed to attach GBPA hwpt %u for "
|
|
"idev devid %u", hwpt_id, accel_dev->idev->devid);
|
|
error_report_err(local_err);
|
|
local_err = NULL;
|
|
all_ok = false;
|
|
}
|
|
}
|
|
if (!all_ok) {
|
|
error_setg(errp, "Failed to attach all GBPA based HWPTs properly");
|
|
}
|
|
return all_ok;
|
|
}
|
|
|
|
void smmuv3_accel_reset(SMMUv3State *s)
|
|
{
|
|
/* Attach a HWPT based on GBPA reset value */
|
|
smmuv3_accel_attach_gbpa_hwpt(s, NULL);
|
|
}
|
|
|
|
static void smmuv3_accel_as_init(SMMUv3State *s)
|
|
{
|
|
|
|
if (shared_as_sysmem) {
|
|
return;
|
|
}
|
|
|
|
memory_region_init(&root, OBJECT(s), "root", UINT64_MAX);
|
|
memory_region_init_alias(&sysmem, OBJECT(s), "smmuv3-accel-sysmem",
|
|
get_system_memory(), 0,
|
|
memory_region_size(get_system_memory()));
|
|
memory_region_add_subregion(&root, 0, &sysmem);
|
|
|
|
shared_as_sysmem = g_new0(AddressSpace, 1);
|
|
address_space_init(shared_as_sysmem, &root, "smmuv3-accel-as-sysmem");
|
|
}
|
|
|
|
void smmuv3_accel_init(SMMUv3State *s)
|
|
{
|
|
SMMUState *bs = ARM_SMMU(s);
|
|
|
|
s->s_accel = g_new0(SMMUv3AccelState, 1);
|
|
bs->iommu_ops = &smmuv3_accel_ops;
|
|
smmuv3_accel_as_init(s);
|
|
}
|
|
|