mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
Merge tag 'iommu-updates-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux
Pull IOMMU updates from Joerg Roedel:
"Core:
- Detect scatterlist length overflows and finalize deferred domain
attachments before mapping MSI pages
- Consolidate x86 MSI reserved-region handling and clean up internal
DMA and SVA helpers
AMD IOMMU:
- Add IOMMU PerfOpt support, enabled by amdgpu for supported
integrated GPUs in identity domains
- Restrict forced identity mapping to GPUs that require it
- Fix kdump device-table allocation and reuse, ATS configuration
error handling, and an interrupt-remapping allocation leak
- Clean up device probing and initialization
- Add Vasant Hegde as an AMD IOMMU maintainer
ARM SMMUv2:
- Device-tree binding updates for Qualcomm Maili and Nord SoCs
ARM SMMUv3:
- Work around hardware integration issue on Google Tensor G5 SoCs
where all DMA transactions appear as instruction fetches
- Allow command queue size to be configured on the kernel
command-line
- Reduce queue sizes when running as a kdump kernel
- Fix some pre-existing issues in the driver and kunit tests reported
by Sashiko
- Fix handling of Arm hardware errata relating to the contiguous hint
when using SVA
- Rework the TLB invalidation logic in preparation for enabling
iommu-pt in future
- Minor cleanups in preparation for improving support for kdump
Apple DART:
- Support device-tree DMA aperture specifications
Broadcom:
- Add BCM2712 IOMMU support for Raspberry Pi 5 display and multimedia
devices, including the generic page-table format and shared TLB
cache management
Hyper-V:
- Add a para-virtualized guest IOMMU driver with guest-managed
stage-1 page tables and page-selective IOTLB invalidation
Intel VT-d:
- Enhanced IQE fault handling for batched multiple descriptors,
- Optimization of page-table level calculation in
compute_vasz_lg2_ss(),
- Fixes for several pre-existing issues reported by Sashiko,
- General cleanup and refinement
RISC-V:
- Report cache-coherency capability for coherent devices, enabling
VFIO/iommufd use
Samsung Exynos:
- Support SysMMUs without BLOCK mode, fixing their enable sequence
and TLB invalidation
- Improve v7 fault diagnostics with transaction information"
* tag 'iommu-updates-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux: (69 commits)
MAINTAINERS: Make Vasant Hegde AMD IOMMU maintainer
iommu/hyperv: Make hv_iommu_device static
iommu/hyperv: Add missing PCI dependency for the guest IOMMU driver
iommu/amd: Don't allocate kdump device table from DMA32
iommu/amd: Program device table when kdump reuse fails
iommu/dma: Clean up redundant MSI cookie check
iommu/dma: Catch scatterlist length overflows
iommu/hyperv: Add page-selective IOTLB flush support
iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest
iommu/x86: Add architectural MSI reserved region helper
Drivers: hv: Add logical device ID registry for vPCI devices
hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU
iommu: Make iommu-dma helpers private
iommu/arm-smmu-v3: Don't rb_erase() a never-inserted stream node
iommu/arm-smmu-v3: Add Q_POS() macro
iommu/arm-smmu-v3: Add arm_smmu_attach_release()
iommu/arm-smmu-v3: Disable the impl before disabling the SMMU on shutdown
iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain
iommu/arm-smmu-v3: Precompute the invalidation commands
iommu/arm-smmu-v3: Keep track in arm_smmu_invs if range invalidation is used
...
This commit is contained in:
commit
4da9dedf3f
61 files changed
+3716
-489
No files matched your search
@@ -276,7 +276,8 @@ stable kernels.
|
||||
+----------------+-----------------+-----------------+-----------------------------+
|
||||
| ARM | MMU L1 | #3878312 | N/A |
|
||||
+----------------+-----------------+-----------------+-----------------------------+
|
||||
| ARM | MMU S3 | #3995052 | N/A |
|
||||
| ARM | MMU S3 | #3995052, | N/A |
|
||||
| | | #3673557 | |
|
||||
+----------------+-----------------+-----------------+-----------------------------+
|
||||
| ARM | GIC-700 | #2941627 | ARM64_ERRATUM_2941627 |
|
||||
+----------------+-----------------+-----------------+-----------------------------+
|
||||
|
||||
@@ -35,6 +35,9 @@ properties:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
iommus:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
@@ -44,10 +44,12 @@ properties:
|
||||
Optional since not all IOMMUs are attached to a clock gate.
|
||||
|
||||
'#iommu-cells':
|
||||
const: 1
|
||||
enum: [ 1, 5 ]
|
||||
description:
|
||||
Has to be one. The single cell describes the stream id emitted by
|
||||
a master to the IOMMU.
|
||||
Has to be one or five. The first cell describes the stream id emitted by
|
||||
a master to the IOMMU. The optional second and third cell describe the
|
||||
aperture start of a master's DMA window and fourth and fifth cell describe
|
||||
the size of DMA window.
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
@@ -66,11 +68,11 @@ examples:
|
||||
compatible = "apple,t8103-dart";
|
||||
reg = <0x82f80000 0x4000>;
|
||||
interrupts = <1 781 4>;
|
||||
#iommu-cells = <1>;
|
||||
#iommu-cells = <5>;
|
||||
};
|
||||
|
||||
master1 {
|
||||
iommus = <&dart1 0>;
|
||||
iommus = <&dart1 0 0x100 0x0 0x10 0x0>;
|
||||
};
|
||||
|
||||
- |+
|
||||
|
||||
@@ -83,6 +83,16 @@ properties:
|
||||
register access with page 0 offsets. Set for Cavium ThunderX2 silicon that
|
||||
doesn't support SMMU page1 register space.
|
||||
|
||||
arm,instdata-override:
|
||||
type: boolean
|
||||
description:
|
||||
Some SoCs like Google Tensor G5 (Laguna) have an SMMU integration
|
||||
silicon erratum where AxPROT[2] signal is always 1, making all
|
||||
transactions appear as instructions.
|
||||
|
||||
This property enables a software workaround to override the
|
||||
instruction/data attribute of incoming traffic to data.
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
not:
|
||||
|
||||
@@ -101,7 +101,9 @@ properties:
|
||||
- qcom,glymur-smmu-500
|
||||
- qcom,hawi-smmu-500
|
||||
- qcom,kaanapali-smmu-500
|
||||
- qcom,maili-smmu-500
|
||||
- qcom,milos-smmu-500
|
||||
- qcom,nord-smmu-500
|
||||
- qcom,qcm2290-smmu-500
|
||||
- qcom,qcs615-smmu-500
|
||||
- qcom,qcs8300-smmu-500
|
||||
@@ -579,6 +581,8 @@ allOf:
|
||||
- qcom,glymur-smmu-500
|
||||
- qcom,hawi-smmu-500
|
||||
- qcom,kaanapali-smmu-500
|
||||
- qcom,maili-smmu-500
|
||||
- qcom,nord-smmu-500
|
||||
- qcom,sm8750-smmu-500
|
||||
- const: qcom,adreno-smmu
|
||||
- const: qcom,smmu-500
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iommu/brcm,bcm2712-iommu.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Broadcom BCM2712 IOMMU
|
||||
|
||||
maintainers:
|
||||
- Daniel Drake <dan@reactivated.net>
|
||||
- Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
- Jim Quinlan <james.quinlan@broadcom.com>
|
||||
|
||||
description:
|
||||
The BCM2712 SoC features multiple independent IOMMU instances providing
|
||||
address translation for multimedia hardware blocks, such as the VC6 display
|
||||
pipeline, camera receivers, and ISP. Each IOMMU has its own page tables,
|
||||
control registers, and local TLB, and is additionally linked to a centralized
|
||||
L2 TLB (IOMMUC).
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: brcm,bcm2712-iommu
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
'#iommu-cells':
|
||||
const: 0
|
||||
|
||||
brcm,iommu-cache:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description:
|
||||
Phandle to the shared IOMMU cache (IOMMUC). Each IOMMU instance caches
|
||||
translated descriptors in this centralized L2 TLB cache block and must
|
||||
trigger cache invalidation commands on it when updating or unmapping page
|
||||
table entries.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- '#iommu-cells'
|
||||
- brcm,iommu-cache
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
iommu@5200 {
|
||||
compatible = "brcm,bcm2712-iommu";
|
||||
reg = <0x5200 0x80>;
|
||||
brcm,iommu-cache = <&iommuc>;
|
||||
#iommu-cells = <0>;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iommu/brcm,bcm2712-iommuc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Broadcom BCM2712 Shared IOMMU Cache (IOMMUC)
|
||||
|
||||
maintainers:
|
||||
- Daniel Drake <dan@reactivated.net>
|
||||
- Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
- Jim Quinlan <james.quinlan@broadcom.com>
|
||||
|
||||
description:
|
||||
The BCM2712 IOMMUC is a centralized Translation Lookaside Buffer (TLB) cache
|
||||
which accelerates address translation across the SoC's IOMMU devices. If an
|
||||
address mapping is not found in the IOMMU's local TLB cache, then this
|
||||
IOMMUC is consulted as a L2 TLB. The SoC includes a single IOMMUC which is
|
||||
shared between all IOMMUs, and it must be explicitly invalidated when
|
||||
modifying or unmapping IOMMU page tables.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: brcm,bcm2712-iommuc
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
iommuc@5b00 {
|
||||
compatible = "brcm,bcm2712-iommuc";
|
||||
reg = <0x5b00 0x80>;
|
||||
};
|
||||
+13
-1
@@ -1191,8 +1191,8 @@ F: drivers/platform/x86/amd/hfi/
|
||||
|
||||
AMD IOMMU (AMD-VI)
|
||||
M: Joerg Roedel (AMD) <joro@8bytes.org>
|
||||
M: Vasant Hegde <vasant.hegde@amd.com>
|
||||
R: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com>
|
||||
R: Vasant Hegde <vasant.hegde@amd.com>
|
||||
L: iommu@lists.linux.dev
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git
|
||||
@@ -5188,6 +5188,18 @@ N: bcm2712
|
||||
N: bcm283*
|
||||
N: raspberrypi
|
||||
|
||||
BROADCOM BCM2712 IOMMU DRIVER
|
||||
M: Daniel Drake <dan@reactivated.net>
|
||||
M: Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
M: Jim Quinlan <james.quinlan@broadcom.com>
|
||||
R: Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com>
|
||||
L: iommu@lists.linux.dev
|
||||
L: linux-rpi-kernel@lists.infradead.org (moderated for non-subscribers)
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/iommu/brcm,bcm2712-iommu*
|
||||
F: drivers/iommu/bcm2712-iommu*
|
||||
F: drivers/iommu/generic_pt/fmt/*bcm2712*
|
||||
|
||||
BROADCOM BCM281XX/BCM11XXX/BCM216XX ARM ARCHITECTURE
|
||||
M: Florian Fainelli <florian.fainelli@broadcom.com>
|
||||
M: Ray Jui <rjui@broadcom.com>
|
||||
|
||||
@@ -579,6 +579,10 @@ skip_hypercall_pg_init:
|
||||
old_setup_percpu_clockev = x86_init.timers.setup_percpu_clockev;
|
||||
x86_init.timers.setup_percpu_clockev = hv_stimer_setup_percpu_clockev;
|
||||
|
||||
#ifdef CONFIG_HYPERV_PVIOMMU
|
||||
x86_init.iommu.iommu_init = hv_iommu_init;
|
||||
#endif
|
||||
|
||||
hv_apic_init();
|
||||
|
||||
x86_init.pci.arch_init = hv_pci_init;
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#ifdef CONFIG_IOMMU_API
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/sizes.h>
|
||||
#endif
|
||||
|
||||
#include <asm/e820/api.h>
|
||||
|
||||
extern int force_iommu, no_iommu;
|
||||
@@ -21,6 +26,21 @@ extern bool x86_swiotlb_enable;
|
||||
/* 10 seconds */
|
||||
#define DMAR_OPERATION_TIMEOUT ((cycles_t) tsc_khz*10*1000)
|
||||
|
||||
#ifdef CONFIG_IOMMU_API
|
||||
static inline int iommu_add_resv_x86_msi_region(struct list_head *head)
|
||||
{
|
||||
struct iommu_resv_region *region;
|
||||
|
||||
region = iommu_alloc_resv_region(0xfee00000, SZ_1M, 0,
|
||||
IOMMU_RESV_MSI, GFP_KERNEL);
|
||||
if (!region)
|
||||
return -ENOMEM;
|
||||
|
||||
list_add_tail(®ion->list, head);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline int __init
|
||||
arch_rmrr_sanity_check(struct acpi_dmar_reserved_memory *rmrr)
|
||||
{
|
||||
|
||||
@@ -313,6 +313,10 @@ static inline void mshv_vtl_return_hypercall(void) {}
|
||||
static inline void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HYPERV_PVIOMMU
|
||||
int __init hv_iommu_init(void);
|
||||
#endif
|
||||
|
||||
#include <asm-generic/mshyperv.h>
|
||||
|
||||
#endif
|
||||
@@ -156,6 +156,7 @@ struct amdgpu_watchdog_timer {
|
||||
* Modules parameters.
|
||||
*/
|
||||
extern int amdgpu_modeset;
|
||||
extern int amdgpu_iommu_perfopt;
|
||||
extern unsigned int amdgpu_vram_limit;
|
||||
extern int amdgpu_vis_vram_limit;
|
||||
extern int amdgpu_gart_size;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <linux/console.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/amd-iommu.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci-p2pdma.h>
|
||||
#include <linux/apple-gmux.h>
|
||||
@@ -3751,6 +3752,28 @@ amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px)
|
||||
apple_gmux_detect(NULL, NULL)));
|
||||
}
|
||||
|
||||
static inline bool amdgpu_device_identity(struct amdgpu_device *adev)
|
||||
{
|
||||
struct pci_dev *pdev = adev->pdev;
|
||||
struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev);
|
||||
|
||||
if (!domain)
|
||||
return false;
|
||||
|
||||
return domain->type == IOMMU_DOMAIN_IDENTITY;
|
||||
}
|
||||
|
||||
static bool amdgpu_device_use_perfopt(struct amdgpu_device *adev)
|
||||
{
|
||||
if (amdgpu_iommu_perfopt == 0)
|
||||
return false;
|
||||
|
||||
if (!(adev->flags & AMD_IS_APU))
|
||||
return false;
|
||||
|
||||
return amdgpu_device_identity(adev);
|
||||
}
|
||||
|
||||
/**
|
||||
* amdgpu_device_init - initialize the driver
|
||||
*
|
||||
@@ -3958,6 +3981,16 @@ int amdgpu_device_init(struct amdgpu_device *adev,
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
if (amdgpu_device_use_perfopt(adev)) {
|
||||
int perfopt_ret = amd_iommu_enable_perfopt(pdev);
|
||||
|
||||
/* Optional optimization; a failure to arm it must not abort probe. */
|
||||
if (perfopt_ret)
|
||||
dev_warn(adev->dev,
|
||||
"Failed to enable IOMMU PerfOpt (%d); continuing without it\n",
|
||||
perfopt_ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* No need to remove conflicting FBs for non-display class devices.
|
||||
* This prevents the sysfb from being freed accidently.
|
||||
@@ -4327,6 +4360,9 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev)
|
||||
|
||||
amdgpu_gart_dummy_page_fini(adev);
|
||||
|
||||
if (amdgpu_device_use_perfopt(adev))
|
||||
amd_iommu_disable_perfopt(adev->pdev);
|
||||
|
||||
if (pci_dev_is_disconnected(adev->pdev))
|
||||
amdgpu_device_unmap_mmio(adev);
|
||||
|
||||
@@ -4690,6 +4726,20 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients)
|
||||
if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
|
||||
return 0;
|
||||
|
||||
if (amdgpu_device_use_perfopt(adev)) {
|
||||
int perfopt_ret = amd_iommu_enable_perfopt(adev->pdev);
|
||||
|
||||
/*
|
||||
* Must not return on failure: a bare return would leak the
|
||||
* SR-IOV VF exclusive-mode acquisition taken above (released
|
||||
* via the exit: path).
|
||||
*/
|
||||
if (perfopt_ret)
|
||||
dev_warn(adev->dev,
|
||||
"Failed to enable IOMMU PerfOpt (%d); continuing without it\n",
|
||||
perfopt_ret);
|
||||
}
|
||||
|
||||
if (adev->in_s0ix)
|
||||
amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D0Entry);
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ char *amdgpu_disable_cu;
|
||||
char *amdgpu_virtual_display;
|
||||
int amdgpu_enforce_isolation = -1;
|
||||
int amdgpu_modeset = -1;
|
||||
int amdgpu_iommu_perfopt = -1;
|
||||
|
||||
/* Specifies the default granularity for SVM, used in buffer
|
||||
* migration and restoration of backing memory when handling
|
||||
@@ -392,6 +393,17 @@ module_param_named(fw_load_type, amdgpu_fw_load_type, int, 0444);
|
||||
MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)");
|
||||
module_param_named(aspm, amdgpu_aspm, int, 0444);
|
||||
|
||||
/**
|
||||
* DOC: iommu_perfopt (int)
|
||||
* Control the AMD IOMMU PerfOpt DMA-latency optimization
|
||||
* (0 = disable; -1 = enable on supported devices).
|
||||
* This arms the IOMMU PerfOpt control (IOMMU spec, MMIO Offset 016Ch, EFR PerfOptSup / PerfOptEn)
|
||||
* Arming it disables ATS, PRI, PASID and SVA for the GPU and removes IOMMU DMA containment for it,
|
||||
* trading isolation for lower DMA latency.
|
||||
*/
|
||||
MODULE_PARM_DESC(iommu_perfopt, "Control IOMMU PerfOpt DMA-latency optimization (-1 = enable on supported devices, 0 = disable)");
|
||||
module_param_named(iommu_perfopt, amdgpu_iommu_perfopt, int, 0444);
|
||||
|
||||
/**
|
||||
* DOC: runpm (int)
|
||||
* Override for runtime power management control for dGPUs. The amdgpu driver can dynamically power down
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <linux/panic_notifier.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/rhashtable.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/kdebug.h>
|
||||
#include <linux/kmsg_dump.h>
|
||||
@@ -78,6 +79,22 @@ static struct ctl_table_header *hv_ctl_table_hdr;
|
||||
u8 * __percpu *hv_synic_eventring_tail;
|
||||
EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
|
||||
|
||||
struct hv_pci_busdata {
|
||||
int pci_domain_nr;
|
||||
u32 logical_dev_id_prefix;
|
||||
struct rhash_head node;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
static struct rhashtable hv_pci_bus_ht;
|
||||
static bool hv_pci_bus_ht_initialized;
|
||||
|
||||
static const struct rhashtable_params hv_pci_bus_ht_params = {
|
||||
.key_len = sizeof_field(struct hv_pci_busdata, pci_domain_nr),
|
||||
.key_offset = offsetof(struct hv_pci_busdata, pci_domain_nr),
|
||||
.head_offset = offsetof(struct hv_pci_busdata, node),
|
||||
};
|
||||
|
||||
/*
|
||||
* Hyper-V specific initialization and shutdown code that is
|
||||
* common across all architectures. Called from architecture
|
||||
@@ -86,6 +103,11 @@ EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
|
||||
|
||||
void __init hv_common_free(void)
|
||||
{
|
||||
if (hv_pci_bus_ht_initialized) {
|
||||
rhashtable_destroy(&hv_pci_bus_ht);
|
||||
hv_pci_bus_ht_initialized = false;
|
||||
}
|
||||
|
||||
unregister_sysctl_table(hv_ctl_table_hdr);
|
||||
hv_ctl_table_hdr = NULL;
|
||||
|
||||
@@ -315,6 +337,7 @@ u8 __init get_vtl(void)
|
||||
int __init hv_common_init(void)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
union hv_hypervisor_version_info version;
|
||||
|
||||
/* Get information about the Microsoft Hypervisor version */
|
||||
@@ -394,6 +417,13 @@ int __init hv_common_init(void)
|
||||
for (i = 0; i < nr_cpu_ids; i++)
|
||||
hv_vp_index[i] = VP_INVAL;
|
||||
|
||||
ret = rhashtable_init(&hv_pci_bus_ht, &hv_pci_bus_ht_params);
|
||||
if (ret) {
|
||||
hv_common_free();
|
||||
return ret;
|
||||
}
|
||||
hv_pci_bus_ht_initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -808,6 +838,7 @@ static const struct hv_status_info hv_status_infos[] = {
|
||||
_STATUS_INFO(HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED, -EOPNOTSUPP),
|
||||
_STATUS_INFO(HV_STATUS_INVALID_LP_INDEX, -EINVAL),
|
||||
_STATUS_INFO(HV_STATUS_INVALID_REGISTER_VALUE, -EINVAL),
|
||||
_STATUS_INFO(HV_STATUS_DEVICE_ALREADY_IN_DOMAIN, -EEXIST),
|
||||
_STATUS_INFO(HV_STATUS_OPERATION_FAILED, -EIO),
|
||||
_STATUS_INFO(HV_STATUS_TIME_OUT, -EIO),
|
||||
_STATUS_INFO(HV_STATUS_CALL_PENDING, -EIO),
|
||||
@@ -861,3 +892,87 @@ const char *hv_result_to_string(u64 status)
|
||||
return "Unknown";
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hv_result_to_string);
|
||||
|
||||
/*
|
||||
* Logical device ID registry for Hyper-V PCI buses. The pci-hyperv
|
||||
* driver registers each bus's logical device ID prefix before scanning
|
||||
* its devices. Consumers look up the prefix by PCI domain number when
|
||||
* building logical device IDs for Hyper-V interfaces.
|
||||
*/
|
||||
int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix)
|
||||
{
|
||||
struct hv_pci_busdata *bus, *new;
|
||||
int ret;
|
||||
|
||||
new = kzalloc_obj(*new, GFP_KERNEL);
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
new->pci_domain_nr = pci_domain_nr;
|
||||
new->logical_dev_id_prefix = logical_dev_id_prefix;
|
||||
|
||||
bus = rhashtable_lookup_get_insert_fast(&hv_pci_bus_ht, &new->node,
|
||||
hv_pci_bus_ht_params);
|
||||
if (IS_ERR(bus)) {
|
||||
ret = PTR_ERR(bus);
|
||||
goto free_new;
|
||||
}
|
||||
|
||||
if (WARN_ONCE(bus != NULL,
|
||||
"Hyper-V PCI domain %04x is already registered\n",
|
||||
pci_domain_nr)) {
|
||||
ret = -EEXIST;
|
||||
goto free_new;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_new:
|
||||
kfree(new);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_MODULES(hv_pci_register_dev_id, "pci-hyperv");
|
||||
|
||||
void hv_pci_unregister_dev_id(int pci_domain_nr)
|
||||
{
|
||||
struct hv_pci_busdata *bus;
|
||||
int ret = -ENOENT;
|
||||
|
||||
rcu_read_lock();
|
||||
bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr,
|
||||
hv_pci_bus_ht_params);
|
||||
if (bus)
|
||||
ret = rhashtable_remove_fast(&hv_pci_bus_ht, &bus->node,
|
||||
hv_pci_bus_ht_params);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (WARN_ONCE(ret,
|
||||
"Failed to unregister Hyper-V PCI domain %04x\n",
|
||||
pci_domain_nr))
|
||||
return;
|
||||
|
||||
kfree_rcu(bus, rcu);
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_MODULES(hv_pci_unregister_dev_id, "pci-hyperv");
|
||||
|
||||
/*
|
||||
* Look up the logical device ID prefix registered for @pci_domain_nr.
|
||||
* Returns 0 on success with *prefix filled in; -ENODEV if no entry is
|
||||
* registered for that PCI domain.
|
||||
*/
|
||||
int hv_pci_lookup_dev_id(int pci_domain_nr, u32 *prefix)
|
||||
{
|
||||
struct hv_pci_busdata *bus;
|
||||
int ret = -ENODEV;
|
||||
|
||||
rcu_read_lock();
|
||||
bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr,
|
||||
hv_pci_bus_ht_params);
|
||||
if (bus) {
|
||||
*prefix = bus->logical_dev_id_prefix;
|
||||
ret = 0;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
+17
-1
@@ -195,6 +195,7 @@ config MSM_IOMMU
|
||||
source "drivers/iommu/amd/Kconfig"
|
||||
source "drivers/iommu/arm/Kconfig"
|
||||
source "drivers/iommu/intel/Kconfig"
|
||||
source "drivers/iommu/hyperv/Kconfig"
|
||||
source "drivers/iommu/iommufd/Kconfig"
|
||||
source "drivers/iommu/riscv/Kconfig"
|
||||
|
||||
@@ -303,7 +304,7 @@ config SPAPR_TCE_IOMMU
|
||||
|
||||
config APPLE_DART
|
||||
tristate "Apple DART IOMMU Support"
|
||||
depends on ARCH_APPLE || COMPILE_TEST
|
||||
depends on ARCH_APPLE || (COMPILE_TEST && 64BIT)
|
||||
depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_DART
|
||||
select IOMMU_API
|
||||
select IOMMU_IO_PGTABLE_DART
|
||||
@@ -386,6 +387,21 @@ config VSI_IOMMU
|
||||
Say Y here if you want to use this IOMMU in front of these
|
||||
hardware blocks.
|
||||
|
||||
config BCM2712_IOMMU
|
||||
bool "BCM2712 IOMMU driver"
|
||||
depends on (ARCH_BCM2835 && ARM64) || COMPILE_TEST
|
||||
select IOMMU_API
|
||||
select GENERIC_PT
|
||||
select IOMMU_PT
|
||||
select IOMMU_PT_BCM2712
|
||||
help
|
||||
Support for IOMMU on BCM2712 SoC. This IOMMU can be used by the
|
||||
display controller and various multimedia devices to perform
|
||||
efficient memory management.
|
||||
|
||||
Say Y here if you want to use this IOMMU in front of these
|
||||
hardware blocks.
|
||||
|
||||
config IOMMU_DEBUG_PAGEALLOC
|
||||
bool "Debug IOMMU mappings against page allocations"
|
||||
depends on DEBUG_PAGEALLOC && IOMMU_API && PAGE_EXTENSION
|
||||
|
||||
@@ -37,4 +37,5 @@ obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o
|
||||
obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o
|
||||
obj-$(CONFIG_APPLE_DART) += apple-dart.o
|
||||
obj-$(CONFIG_VSI_IOMMU) += vsi-iommu.o
|
||||
obj-$(CONFIG_BCM2712_IOMMU) += bcm2712-iommu.o bcm2712-iommu-cache.o
|
||||
obj-$(CONFIG_IOMMU_DEBUG_PAGEALLOC) += iommu-debug-pagealloc.o
|
||||
@@ -48,6 +48,9 @@ extern u8 amd_iommu_hpt_vasize;
|
||||
extern unsigned long amd_iommu_pgsize_bitmap;
|
||||
extern bool amd_iommu_hatdis;
|
||||
|
||||
int amd_iommu_perfopt_clear(struct amd_iommu *iommu);
|
||||
int amd_iommu_perfopt_restore(struct amd_iommu *iommu);
|
||||
|
||||
/* Protection domain ops */
|
||||
void amd_iommu_init_identity_domain(void);
|
||||
struct protection_domain *protection_domain_alloc(void);
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#define MMIO_MSI_ADDR_LO_OFFSET 0x015C
|
||||
#define MMIO_MSI_ADDR_HI_OFFSET 0x0160
|
||||
#define MMIO_MSI_DATA_OFFSET 0x0164
|
||||
#define MMIO_PERF_OPT_OFFSET 0x016C
|
||||
#define MMIO_INTCAPXT_EVT_OFFSET 0x0170
|
||||
#define MMIO_INTCAPXT_PPR_OFFSET 0x0178
|
||||
#define MMIO_INTCAPXT_GALOG_OFFSET 0x0180
|
||||
@@ -87,6 +88,8 @@
|
||||
#define FEATURE_GLX GENMASK_ULL(15, 14)
|
||||
#define FEATURE_GAM_VAPIC BIT_ULL(21)
|
||||
#define FEATURE_PASMAX GENMASK_ULL(36, 32)
|
||||
#define FEATURE_PERF_OPT BIT_ULL(45)
|
||||
#define PERF_OPT_EN BIT(13)
|
||||
#define FEATURE_GIOSUP BIT_ULL(48)
|
||||
#define FEATURE_HASUP BIT_ULL(49)
|
||||
#define FEATURE_EPHSUP BIT_ULL(50)
|
||||
@@ -657,6 +660,9 @@ struct amd_iommu {
|
||||
/* Extended features 2 */
|
||||
u64 features2;
|
||||
|
||||
/* Devices requesting PerfOpt; the shared PERF_OPT_EN bit is on while >0. Protected by @lock. */
|
||||
int perfopt_refcount;
|
||||
|
||||
/* PCI device id of the IOMMU device */
|
||||
u16 devid;
|
||||
|
||||
@@ -818,6 +824,7 @@ struct iommu_dev_data {
|
||||
u8 ppr :1; /* Enable device PPR support */
|
||||
bool use_vapic; /* Enable device to use vapic mode */
|
||||
bool defer_attach;
|
||||
bool perfopt;
|
||||
|
||||
struct ratelimit_state rs; /* Ratelimit IOPF messages */
|
||||
};
|
||||
|
||||
+131
-61
@@ -163,7 +163,6 @@ int amd_iommu_gpt_level = PAGE_MODE_4_LEVEL;
|
||||
int amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_VAPIC;
|
||||
static int amd_iommu_xt_mode = IRQ_REMAP_XAPIC_MODE;
|
||||
|
||||
static bool amd_iommu_detected;
|
||||
static bool amd_iommu_disabled __initdata;
|
||||
static bool amd_iommu_force_enable __initdata;
|
||||
static bool amd_iommu_irtcachedis;
|
||||
@@ -390,7 +389,13 @@ static void iommu_set_device_table(struct amd_iommu *iommu)
|
||||
|
||||
BUG_ON(iommu->mmio_base == NULL);
|
||||
|
||||
if (is_kdump_kernel())
|
||||
/*
|
||||
* A kdump kernel that reuses the previous kernel's device table must
|
||||
* leave the base register alone. It already points at that table, and
|
||||
* with SNP enabled the register is locked. If reuse failed, program
|
||||
* the freshly allocated table like a normal boot.
|
||||
*/
|
||||
if (iommu->pci_seg->old_dev_tbl_cpy)
|
||||
return;
|
||||
|
||||
entry = iommu_virt_to_phys(dev_table);
|
||||
@@ -623,8 +628,19 @@ static int __init find_last_devid_acpi(struct acpi_table_header *table, u16 pci_
|
||||
/* Allocate per PCI segment device table */
|
||||
static inline int __init alloc_dev_table(struct amd_iommu_pci_seg *pci_seg)
|
||||
{
|
||||
pci_seg->dev_table = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32,
|
||||
pci_seg->dev_table_size);
|
||||
gfp_t gfp = GFP_KERNEL;
|
||||
|
||||
/*
|
||||
* Keep normal kernel device tables below 4G because a later kdump
|
||||
* kernel only trusts and reuses old tables below that limit. A kdump
|
||||
* kernel's new table is either discarded when reuse succeeds or
|
||||
* programmed after the IOMMU is disabled, so the table does not
|
||||
* need DMA32 memory (which the crashkernel may not have).
|
||||
*/
|
||||
if (!is_kdump_kernel())
|
||||
gfp |= GFP_DMA32;
|
||||
|
||||
pci_seg->dev_table = iommu_alloc_pages_sz(gfp, pci_seg->dev_table_size);
|
||||
if (!pci_seg->dev_table)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -916,13 +932,13 @@ static void free_ga_log(struct amd_iommu *iommu)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IRQ_REMAP
|
||||
static int iommu_ga_log_enable(struct amd_iommu *iommu)
|
||||
static bool iommu_ga_log_enable(struct amd_iommu *iommu)
|
||||
{
|
||||
u32 status, i;
|
||||
u64 entry;
|
||||
|
||||
if (!iommu->ga_log)
|
||||
return -EINVAL;
|
||||
return false;
|
||||
|
||||
entry = iommu_virt_to_phys(iommu->ga_log) | GA_LOG_SIZE_512;
|
||||
memcpy_toio(iommu->mmio_base + MMIO_GA_LOG_BASE_OFFSET,
|
||||
@@ -946,35 +962,31 @@ static int iommu_ga_log_enable(struct amd_iommu *iommu)
|
||||
}
|
||||
|
||||
if (WARN_ON(i >= MMIO_STATUS_TIMEOUT))
|
||||
return -EINVAL;
|
||||
return false;
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
#endif /* CONFIG_IRQ_REMAP */
|
||||
|
||||
static int iommu_init_ga_log(struct amd_iommu *iommu)
|
||||
static int alloc_ga_log(struct amd_iommu *iommu)
|
||||
{
|
||||
#ifdef CONFIG_IRQ_REMAP
|
||||
int nid = iommu->dev ? dev_to_node(&iommu->dev->dev) : NUMA_NO_NODE;
|
||||
|
||||
if (WARN_ON_ONCE(!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir)))
|
||||
return -EINVAL;
|
||||
|
||||
if (iommu->ga_log && iommu->ga_log_tail)
|
||||
return 0;
|
||||
|
||||
iommu->ga_log = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, GA_LOG_SIZE);
|
||||
if (!iommu->ga_log)
|
||||
goto err_out;
|
||||
return -ENOMEM;
|
||||
|
||||
iommu->ga_log_tail = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, 8);
|
||||
if (!iommu->ga_log_tail)
|
||||
goto err_out;
|
||||
if (!iommu->ga_log_tail) {
|
||||
iommu_free_pages(iommu->ga_log);
|
||||
iommu->ga_log = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
err_out:
|
||||
free_ga_log(iommu);
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif /* CONFIG_IRQ_REMAP */
|
||||
|
||||
static int __init alloc_cwwb_sem(struct amd_iommu *iommu)
|
||||
{
|
||||
@@ -1948,6 +1960,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h,
|
||||
if (!iommu->mmio_base)
|
||||
return -ENOMEM;
|
||||
|
||||
if (amd_iommu_perfopt_clear(iommu))
|
||||
pr_err("IOMMU%d: failed to clear PerfOpt\n", iommu->index);
|
||||
|
||||
return init_iommu_from_acpi(iommu, h);
|
||||
}
|
||||
|
||||
@@ -2152,6 +2167,27 @@ static void __init late_iommu_features_init(struct amd_iommu *iommu)
|
||||
}
|
||||
}
|
||||
|
||||
/* Must be called after SNP support check is complete (iommu_snp_enable()) */
|
||||
static bool check_vapic_support(void)
|
||||
{
|
||||
if (!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))
|
||||
return false;
|
||||
|
||||
if (!check_feature(FEATURE_GAM_VAPIC)) {
|
||||
amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amd_iommu_snp_en &&
|
||||
!FEATURE_SNPAVICSUP_GAM(amd_iommu_efr2)) {
|
||||
pr_warn("Force to disable Virtual APIC due to SNP\n");
|
||||
amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int __init iommu_init_pci(struct amd_iommu *iommu)
|
||||
{
|
||||
int cap_ptr = iommu->cap_ptr;
|
||||
@@ -2196,6 +2232,12 @@ static int __init iommu_init_pci(struct amd_iommu *iommu)
|
||||
if (check_feature(FEATURE_PPR) && amd_iommu_alloc_ppr_log(iommu))
|
||||
return -ENOMEM;
|
||||
|
||||
if (check_vapic_support()) {
|
||||
ret = alloc_ga_log(iommu);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (iommu->cap & (1UL << IOMMU_CAP_NPCACHE)) {
|
||||
pr_info("Using strict mode due to virtualization\n");
|
||||
iommu_set_dma_strict();
|
||||
@@ -3010,22 +3052,9 @@ static void enable_iommus_vapic(void)
|
||||
if (!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))
|
||||
return;
|
||||
|
||||
if (!check_feature(FEATURE_GAM_VAPIC)) {
|
||||
amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
|
||||
return;
|
||||
}
|
||||
|
||||
if (amd_iommu_snp_en &&
|
||||
!FEATURE_SNPAVICSUP_GAM(amd_iommu_efr2)) {
|
||||
pr_warn("Force to disable Virtual APIC due to SNP\n");
|
||||
amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY_GA;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Enabling GAM and SNPAVIC support */
|
||||
for_each_iommu(iommu) {
|
||||
if (iommu_init_ga_log(iommu) ||
|
||||
iommu_ga_log_enable(iommu))
|
||||
if (!iommu_ga_log_enable(iommu))
|
||||
return;
|
||||
|
||||
iommu_feature_enable(iommu, CONTROL_GAM_EN);
|
||||
@@ -3038,10 +3067,46 @@ static void enable_iommus_vapic(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
static int clear_perfopt_all(void)
|
||||
{
|
||||
struct amd_iommu *iommu;
|
||||
int err, ret = 0;
|
||||
|
||||
for_each_iommu(iommu) {
|
||||
err = amd_iommu_perfopt_clear(iommu);
|
||||
if (err)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int restore_perfopt_all(void)
|
||||
{
|
||||
struct amd_iommu *iommu;
|
||||
int err, ret = 0;
|
||||
|
||||
for_each_iommu(iommu) {
|
||||
err = amd_iommu_perfopt_restore(iommu);
|
||||
if (err)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void disable_iommus(void)
|
||||
{
|
||||
struct amd_iommu *iommu;
|
||||
|
||||
/*
|
||||
* PerfOpt is an optional performance bit, so a failure to clear it must
|
||||
* not skip the mandatory disable below. This also runs from the void
|
||||
* amd_iommu_disable() shutdown/kexec path, which cannot report an error.
|
||||
*/
|
||||
if (clear_perfopt_all())
|
||||
pr_err("Failed to clear PerfOpt while disabling IOMMUs\n");
|
||||
|
||||
for_each_iommu(iommu)
|
||||
iommu_disable(iommu);
|
||||
|
||||
@@ -3067,6 +3132,10 @@ static void amd_iommu_resume(void *data)
|
||||
for_each_iommu(iommu)
|
||||
early_enable_iommu(iommu);
|
||||
|
||||
/* early_enable_iommu() cleared PERF_OPT_EN; re-assert it from the refcount. */
|
||||
if (restore_perfopt_all())
|
||||
pr_err("Failed to restore PerfOpt after IOMMU resume\n");
|
||||
|
||||
iommu_enable_event_buffer();
|
||||
amd_iommu_enable_interrupts();
|
||||
}
|
||||
@@ -3228,9 +3297,6 @@ static int __init early_amd_iommu_init(void)
|
||||
acpi_status status;
|
||||
u8 efr_hats, max_vasize;
|
||||
|
||||
if (!amd_iommu_detected)
|
||||
return -ENODEV;
|
||||
|
||||
status = acpi_get_table("IVRS", 0, &ivrs_base);
|
||||
if (status == AE_NOT_FOUND)
|
||||
return -ENODEV;
|
||||
@@ -3309,7 +3375,7 @@ static int __init early_amd_iommu_init(void)
|
||||
}
|
||||
|
||||
/* Disable any previously enabled IOMMUs */
|
||||
if (!is_kdump_kernel() || amd_iommu_disabled)
|
||||
if (!is_kdump_kernel())
|
||||
disable_iommus();
|
||||
|
||||
if (amd_iommu_irq_remap)
|
||||
@@ -3404,12 +3470,6 @@ static __init void iommu_snp_enable(void)
|
||||
if (!cc_platform_has(CC_ATTR_HOST_SEV_SNP))
|
||||
return;
|
||||
|
||||
/* SNP support required IOMMU to be ON */
|
||||
if (no_iommu) {
|
||||
pr_warn("SNP: IOMMU disabled, SNP cannot be supported.\n");
|
||||
goto disable_snp;
|
||||
}
|
||||
|
||||
amd_iommu_snp_mode0_sup = check_feature2(FEATURE_SNP_PAGE_MODE0_SUP);
|
||||
/*
|
||||
* If SNP page mode 0 is not enabled, then SNP support requires that IOMMU
|
||||
@@ -3476,6 +3536,8 @@ static void amd_iommu_apply_erratum_snp(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool amd_iommu_sme_check(void);
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* AMD IOMMU Initialization State Machine
|
||||
@@ -3488,7 +3550,13 @@ static int __init state_next(void)
|
||||
|
||||
switch (init_state) {
|
||||
case IOMMU_START_STATE:
|
||||
if (!detect_ivrs()) {
|
||||
if (no_iommu || amd_iommu_disabled) {
|
||||
init_state = IOMMU_CMDLINE_DISABLED;
|
||||
ret = -EINVAL;
|
||||
} else if (!amd_iommu_sme_check()) {
|
||||
init_state = IOMMU_INIT_ERROR;
|
||||
ret = -EINVAL;
|
||||
} else if (!detect_ivrs()) {
|
||||
init_state = IOMMU_NOT_FOUND;
|
||||
ret = -ENODEV;
|
||||
} else {
|
||||
@@ -3496,13 +3564,8 @@ static int __init state_next(void)
|
||||
}
|
||||
break;
|
||||
case IOMMU_IVRS_DETECTED:
|
||||
if (amd_iommu_disabled) {
|
||||
init_state = IOMMU_CMDLINE_DISABLED;
|
||||
ret = -EINVAL;
|
||||
} else {
|
||||
ret = early_amd_iommu_init();
|
||||
init_state = ret ? IOMMU_INIT_ERROR : IOMMU_ACPI_FINISHED;
|
||||
}
|
||||
ret = early_amd_iommu_init();
|
||||
init_state = ret ? IOMMU_INIT_ERROR : IOMMU_ACPI_FINISHED;
|
||||
break;
|
||||
case IOMMU_ACPI_FINISHED:
|
||||
early_enable_iommus();
|
||||
@@ -3574,6 +3637,19 @@ static int __init iommu_go_to_state(enum iommu_init_state state)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
|
||||
/*
|
||||
* Some essential housekeeping work is done by amd_iommu_detect().
|
||||
* Skipping calling it implies that the platform (e.g., Xen hypervisor)
|
||||
* has taken over the hardware. Progressing the state machine in this
|
||||
* case is worthless and fragile.
|
||||
*
|
||||
* There are several paths requesting later states, so disallow implicit
|
||||
* START_STATE => IVRS_DETECTED transition to prevent these paths from
|
||||
* accidentally progressing the state machine.
|
||||
*/
|
||||
if (init_state == IOMMU_START_STATE && state != IOMMU_IVRS_DETECTED)
|
||||
goto out;
|
||||
|
||||
while (init_state != state) {
|
||||
if (init_state == IOMMU_NOT_FOUND ||
|
||||
init_state == IOMMU_INIT_ERROR ||
|
||||
@@ -3582,6 +3658,7 @@ static int __init iommu_go_to_state(enum iommu_init_state state)
|
||||
ret = state_next();
|
||||
}
|
||||
|
||||
out:
|
||||
/*
|
||||
* SNP platform initilazation requires IOMMUs to be fully configured.
|
||||
* If the SNP support on IOMMUs has NOT been checked, simply mark SNP
|
||||
@@ -3701,17 +3778,10 @@ void __init amd_iommu_detect(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (no_iommu || (iommu_detected && !gart_iommu_aperture))
|
||||
goto disable_snp;
|
||||
|
||||
if (!amd_iommu_sme_check())
|
||||
goto disable_snp;
|
||||
|
||||
ret = iommu_go_to_state(IOMMU_IVRS_DETECTED);
|
||||
if (ret)
|
||||
goto disable_snp;
|
||||
|
||||
amd_iommu_detected = true;
|
||||
iommu_detected = 1;
|
||||
x86_init.iommu.iommu_init = amd_iommu_init;
|
||||
return;
|
||||
|
||||
+351
-117
@@ -50,8 +50,6 @@
|
||||
#define CMD_SET_TYPE(cmd, t) ((cmd)->data[1] |= ((t) << 28))
|
||||
|
||||
/* Reserved IOVA ranges */
|
||||
#define MSI_RANGE_START (0xfee00000)
|
||||
#define MSI_RANGE_END (0xfeefffff)
|
||||
#define HT_RANGE_START (0xfd00000000ULL)
|
||||
#define HT_RANGE_END (0xffffffffffULL)
|
||||
|
||||
@@ -573,10 +571,17 @@ static inline int pdev_enable_cap_ats(struct pci_dev *pdev)
|
||||
if (amd_iommu_iotlb_sup &&
|
||||
(dev_data->flags & AMD_IOMMU_DEVICE_FLAG_ATS_SUP)) {
|
||||
ret = pci_enable_ats(pdev, PAGE_SHIFT);
|
||||
if (!ret) {
|
||||
dev_data->ats_enabled = 1;
|
||||
dev_data->ats_qdep = pci_ats_queue_depth(pdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* pci_enable_ats() should not fail here because earlier
|
||||
* checks have already verified support & config.
|
||||
*/
|
||||
if (WARN_ON(ret))
|
||||
return ret;
|
||||
|
||||
dev_data->ats_enabled = 1;
|
||||
dev_data->ats_qdep = pci_ats_queue_depth(pdev);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -675,7 +680,8 @@ static void pdev_disable_caps(struct pci_dev *pdev)
|
||||
* This function checks if the driver got a valid device from the caller to
|
||||
* avoid dereferencing invalid pointers.
|
||||
*/
|
||||
static bool check_device(struct device *dev)
|
||||
static bool lookup_device(struct device *dev,
|
||||
struct amd_iommu **iommu_out, u16 *devid_out)
|
||||
{
|
||||
struct amd_iommu_pci_seg *pci_seg;
|
||||
struct amd_iommu *iommu;
|
||||
@@ -690,7 +696,7 @@ static bool check_device(struct device *dev)
|
||||
devid = PCI_SBDF_TO_DEVID(sbdf);
|
||||
|
||||
iommu = rlookup_amd_iommu(dev);
|
||||
if (!iommu)
|
||||
if (!iommu || !iommu->iommu.ops)
|
||||
return false;
|
||||
|
||||
/* Out of our scope? */
|
||||
@@ -698,65 +704,35 @@ static bool check_device(struct device *dev)
|
||||
if (devid > pci_seg->last_bdf)
|
||||
return false;
|
||||
|
||||
*iommu_out = iommu;
|
||||
*devid_out = devid;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int iommu_init_device(struct amd_iommu *iommu, struct device *dev)
|
||||
static struct iommu_dev_data *iommu_init_device(struct amd_iommu *iommu,
|
||||
struct device *dev, u16 devid)
|
||||
{
|
||||
struct iommu_dev_data *dev_data;
|
||||
int devid, sbdf;
|
||||
|
||||
if (dev_iommu_priv_get(dev))
|
||||
return 0;
|
||||
|
||||
sbdf = get_device_sbdf_id(dev);
|
||||
if (sbdf < 0)
|
||||
return sbdf;
|
||||
|
||||
devid = PCI_SBDF_TO_DEVID(sbdf);
|
||||
dev_data = find_dev_data(iommu, devid);
|
||||
if (!dev_data)
|
||||
return -ENOMEM;
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
dev_data->dev = dev;
|
||||
|
||||
/*
|
||||
* The dev_iommu_priv_set() needes to be called before setup_aliases.
|
||||
* The dev_iommu_priv_set() needs to be called before setup_aliases.
|
||||
* Otherwise, subsequent call to dev_iommu_priv_get() will fail.
|
||||
*/
|
||||
dev_iommu_priv_set(dev, dev_data);
|
||||
setup_aliases(iommu, dev);
|
||||
|
||||
/*
|
||||
* By default we use passthrough mode for IOMMUv2 capable device.
|
||||
* But if amd_iommu=force_isolation is set (e.g. to debug DMA to
|
||||
* invalid address), we ignore the capability for the device so
|
||||
* it'll be forced to go into translation mode.
|
||||
*/
|
||||
if ((iommu_default_passthrough() || !amd_iommu_force_isolation) &&
|
||||
dev_is_pci(dev) && amd_iommu_gt_ppr_supported()) {
|
||||
dev_data->flags = pdev_get_caps(to_pci_dev(dev));
|
||||
}
|
||||
/* Wait for DTE updates to go through */
|
||||
iommu_completion_wait(iommu);
|
||||
|
||||
return 0;
|
||||
return dev_data;
|
||||
}
|
||||
|
||||
static void iommu_ignore_device(struct amd_iommu *iommu, struct device *dev)
|
||||
{
|
||||
struct amd_iommu_pci_seg *pci_seg = iommu->pci_seg;
|
||||
struct dev_table_entry *dev_table = get_dev_table(iommu);
|
||||
int devid, sbdf;
|
||||
|
||||
sbdf = get_device_sbdf_id(dev);
|
||||
if (sbdf < 0)
|
||||
return;
|
||||
|
||||
devid = PCI_SBDF_TO_DEVID(sbdf);
|
||||
pci_seg->rlookup_table[devid] = NULL;
|
||||
memset(&dev_table[devid], 0, sizeof(struct dev_table_entry));
|
||||
|
||||
setup_aliases(iommu, dev);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
@@ -2392,6 +2368,9 @@ static int attach_device(struct device *dev,
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (dev_data->perfopt)
|
||||
goto skip_caps;
|
||||
|
||||
/* Setup GCR3 table */
|
||||
if (pdom_is_sva_capable(domain)) {
|
||||
ret = init_gcr3_table(dev_data, domain);
|
||||
@@ -2416,6 +2395,7 @@ static int attach_device(struct device *dev,
|
||||
pdev_enable_cap_ats(pdev);
|
||||
}
|
||||
|
||||
skip_caps:
|
||||
/* Update data structures */
|
||||
dev_data->domain = domain;
|
||||
spin_lock_irqsave(&domain->lock, flags);
|
||||
@@ -2484,73 +2464,289 @@ out:
|
||||
mutex_unlock(&dev_data->mutex);
|
||||
}
|
||||
|
||||
static struct iommu_device *amd_iommu_probe_device(struct device *dev)
|
||||
static int iommu_init_device_caps(struct iommu_dev_data *dev_data,
|
||||
struct device *dev,
|
||||
struct amd_iommu *iommu)
|
||||
{
|
||||
struct iommu_device *iommu_dev;
|
||||
struct amd_iommu *iommu;
|
||||
struct iommu_dev_data *dev_data;
|
||||
int ret;
|
||||
|
||||
if (!check_device(dev))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
iommu = rlookup_amd_iommu(dev);
|
||||
if (!iommu)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
/* Not registered yet? */
|
||||
if (!iommu->iommu.ops)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
if (dev_iommu_priv_get(dev))
|
||||
return &iommu->iommu;
|
||||
|
||||
ret = iommu_init_device(iommu, dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize - trying to proceed anyway\n");
|
||||
iommu_dev = ERR_PTR(ret);
|
||||
iommu_ignore_device(iommu, dev);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
amd_iommu_set_pci_msi_domain(dev, iommu);
|
||||
iommu_dev = &iommu->iommu;
|
||||
|
||||
/*
|
||||
* If IOMMU and device supports PASID then it will contain max
|
||||
* supported PASIDs, else it will be zero.
|
||||
*/
|
||||
dev_data = dev_iommu_priv_get(dev);
|
||||
if (amd_iommu_pasid_supported() && dev_is_pci(dev) &&
|
||||
pdev_pasid_supported(dev_data)) {
|
||||
dev_data->max_pasids = min_t(u32, iommu->iommu.max_pasids,
|
||||
pci_max_pasids(to_pci_dev(dev)));
|
||||
}
|
||||
|
||||
if (amd_iommu_pgtable == PD_MODE_NONE) {
|
||||
pr_warn_once("%s: DMA translation not supported by iommu.\n",
|
||||
__func__);
|
||||
iommu_dev = ERR_PTR(-ENODEV);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
iommu_completion_wait(iommu);
|
||||
|
||||
if (FEATURE_NUM_INT_REMAP_SUP_2K(amd_iommu_efr2))
|
||||
dev_data->max_irqs = MAX_IRQS_PER_TABLE_2K;
|
||||
else
|
||||
dev_data->max_irqs = MAX_IRQS_PER_TABLE_512;
|
||||
|
||||
if (dev_is_pci(dev))
|
||||
pci_prepare_ats(to_pci_dev(dev), PAGE_SHIFT);
|
||||
amd_iommu_set_pci_msi_domain(dev, iommu);
|
||||
|
||||
if (!dev_is_pci(dev))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* By default we use passthrough mode for IOMMUv2 capable device.
|
||||
* But if amd_iommu=force_isolation is set (e.g. to debug DMA to
|
||||
* invalid address), we ignore the capability for the device so
|
||||
* it'll be forced to go into translation mode.
|
||||
*/
|
||||
if ((iommu_default_passthrough() || !amd_iommu_force_isolation) &&
|
||||
amd_iommu_gt_ppr_supported()) {
|
||||
dev_data->flags = pdev_get_caps(to_pci_dev(dev));
|
||||
}
|
||||
|
||||
/*
|
||||
* If IOMMU and device supports PASID then it will contain max
|
||||
* supported PASIDs, else it will be zero.
|
||||
*/
|
||||
if (amd_iommu_pasid_supported() &&
|
||||
pdev_pasid_supported(dev_data)) {
|
||||
dev_data->max_pasids = min_t(u32, iommu->iommu.max_pasids,
|
||||
pci_max_pasids(to_pci_dev(dev)));
|
||||
}
|
||||
|
||||
if (pci_ats_supported(to_pci_dev(dev))) {
|
||||
ret = pci_prepare_ats(to_pci_dev(dev), PAGE_SHIFT);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Program the per-IOMMU PerfOpt enable bit. Caller must hold iommu->lock. */
|
||||
static int __perfopt_write(struct amd_iommu *iommu, bool enable)
|
||||
{
|
||||
u32 old, val, readback;
|
||||
|
||||
if (!(readq(iommu->mmio_base + MMIO_EXT_FEATURES) & FEATURE_PERF_OPT))
|
||||
return enable ? -ENODEV : 0;
|
||||
|
||||
old = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
|
||||
if (old == U32_MAX)
|
||||
return -EIO;
|
||||
|
||||
val = enable ? old | PERF_OPT_EN : old & ~PERF_OPT_EN;
|
||||
if (val != old)
|
||||
writel(val, iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
|
||||
readback = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
|
||||
if (readback == U32_MAX ||
|
||||
(readback & PERF_OPT_EN) != (val & PERF_OPT_EN))
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* PERF_OPT_EN is a single bit shared by every device behind @iommu, so it is
|
||||
* reference counted: armed on the first requesting device, cleared on the last.
|
||||
*/
|
||||
static int perfopt_get(struct amd_iommu *iommu)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (!iommu->mmio_base)
|
||||
return 0;
|
||||
|
||||
raw_spin_lock_irqsave(&iommu->lock, flags);
|
||||
if (iommu->perfopt_refcount == 0) {
|
||||
ret = __perfopt_write(iommu, true);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
iommu->perfopt_refcount++;
|
||||
out:
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int perfopt_put(struct amd_iommu *iommu)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (!iommu->mmio_base)
|
||||
return 0;
|
||||
|
||||
raw_spin_lock_irqsave(&iommu->lock, flags);
|
||||
if (iommu->perfopt_refcount > 0 && --iommu->perfopt_refcount == 0)
|
||||
ret = __perfopt_write(iommu, false);
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Force PERF_OPT_EN off without touching the refcount (used on init, shutdown,
|
||||
* and suspend). The count is preserved so amd_iommu_perfopt_restore() can
|
||||
* re-arm on resume.
|
||||
*/
|
||||
int amd_iommu_perfopt_clear(struct amd_iommu *iommu)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (!iommu->mmio_base)
|
||||
return 0;
|
||||
|
||||
raw_spin_lock_irqsave(&iommu->lock, flags);
|
||||
ret = __perfopt_write(iommu, false);
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-assert PERF_OPT_EN from the refcount after the hardware was reprogrammed on
|
||||
* resume, so devices armed before suspend keep the optimization without each
|
||||
* consumer driver re-arming.
|
||||
*/
|
||||
int amd_iommu_perfopt_restore(struct amd_iommu *iommu)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (!iommu->mmio_base)
|
||||
return 0;
|
||||
|
||||
raw_spin_lock_irqsave(&iommu->lock, flags);
|
||||
ret = __perfopt_write(iommu, iommu->perfopt_refcount > 0);
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int amd_iommu_enable_perfopt(struct pci_dev *pdev)
|
||||
{
|
||||
struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev);
|
||||
struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev);
|
||||
struct protection_domain *domain;
|
||||
int ret;
|
||||
|
||||
if (!iommu || !dev_data)
|
||||
return -ENODEV;
|
||||
|
||||
if (!(iommu->features & FEATURE_PERF_OPT))
|
||||
return -ENODEV;
|
||||
|
||||
domain = dev_data->domain;
|
||||
if (!domain)
|
||||
return -ENODEV;
|
||||
|
||||
/* Already armed for this device (e.g. re-entry on resume). */
|
||||
if (dev_data->perfopt)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The bit is only architecturally valid while the device is untranslated:
|
||||
* identity domain with ATS/PRI/PASID off. The identity domain is
|
||||
* SVA-capable so attach_device() enabled ATS/PRI/PASID and built a GCR3
|
||||
* table. Re-home the device onto the same identity domain with
|
||||
* perfopt set, so the attach_device() skip_caps path leaves
|
||||
* ATS/PRI/PASID off and no GCR3 table. This follows the detach/attach
|
||||
* pattern used by amd_iommu_attach_device().
|
||||
*
|
||||
* Locking: this and amd_iommu_disable_perfopt() run only from the
|
||||
* consumer driver's bind/unbind path. group->mutex is not exposed to
|
||||
* drivers, but a device bound to its native driver cannot have its domain
|
||||
* changed concurrently by the core (VFIO ownership is mutually exclusive;
|
||||
* sysfs domain changes require an unused group), so the detach/attach pair
|
||||
* is serialized without it.
|
||||
*/
|
||||
dev_data->perfopt = true;
|
||||
detach_device(&pdev->dev);
|
||||
ret = attach_device(&pdev->dev, domain);
|
||||
if (ret)
|
||||
goto err_restore;
|
||||
|
||||
ret = perfopt_get(iommu);
|
||||
if (ret)
|
||||
goto err_rearm;
|
||||
|
||||
dev_info_once(&pdev->dev, "PerfOpt armed on IOMMU%d\n", iommu->index);
|
||||
return 0;
|
||||
|
||||
err_rearm:
|
||||
detach_device(&pdev->dev);
|
||||
err_restore:
|
||||
dev_data->perfopt = false;
|
||||
if (attach_device(&pdev->dev, domain))
|
||||
pci_err(pdev, "failed to restore state after PerfOpt setup; device left detached\n");
|
||||
dev_err_once(&pdev->dev, "PerfOpt failed to arm on IOMMU%d (%d)\n",
|
||||
iommu->index, ret);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amd_iommu_enable_perfopt);
|
||||
|
||||
void amd_iommu_disable_perfopt(struct pci_dev *pdev)
|
||||
{
|
||||
struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev);
|
||||
struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev);
|
||||
struct protection_domain *domain;
|
||||
|
||||
if (!iommu || !dev_data || !dev_data->perfopt || !dev_data->domain)
|
||||
return;
|
||||
|
||||
if (WARN_ON(perfopt_put(iommu)))
|
||||
pci_err(pdev, "failed to clear PerfOpt\n");
|
||||
|
||||
/*
|
||||
* Restore ATS/PRI/PASID (and thus SVA) by re-homing the device onto its
|
||||
* identity domain with the flag cleared, so a later bind without PerfOpt
|
||||
* sees a normally-capable device. See the locking note in
|
||||
* amd_iommu_enable_perfopt().
|
||||
*/
|
||||
domain = dev_data->domain;
|
||||
dev_data->perfopt = false;
|
||||
detach_device(&pdev->dev);
|
||||
if (attach_device(&pdev->dev, domain))
|
||||
pci_err(pdev, "failed to restore caps after PerfOpt disable\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amd_iommu_disable_perfopt);
|
||||
static struct iommu_device *amd_iommu_probe_device(struct device *dev)
|
||||
{
|
||||
struct iommu_device *iommu_dev;
|
||||
struct amd_iommu *iommu;
|
||||
struct iommu_dev_data *dev_data;
|
||||
u16 devid;
|
||||
int ret;
|
||||
|
||||
if (!lookup_device(dev, &iommu, &devid))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
if (dev_iommu_priv_get(dev))
|
||||
return &iommu->iommu;
|
||||
|
||||
dev_data = iommu_init_device(iommu, dev, devid);
|
||||
if (IS_ERR(dev_data)) {
|
||||
dev_err(dev, "Failed to initialize - trying to proceed anyway\n");
|
||||
return ERR_CAST(dev_data);
|
||||
}
|
||||
|
||||
ret = iommu_init_device_caps(dev_data, dev, iommu);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
iommu_dev = &iommu->iommu;
|
||||
|
||||
/*
|
||||
* When DMA translation is unavailable return error so the iommu core
|
||||
* won't attempt domain attach for this device, while preserving its
|
||||
* rlookup entry for interrupt remapping.
|
||||
*/
|
||||
if (amd_iommu_pgtable == PD_MODE_NONE) {
|
||||
pr_warn_once("%s: DMA translation not supported by iommu.\n",
|
||||
__func__);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
out_err:
|
||||
return iommu_dev;
|
||||
}
|
||||
|
||||
static void amd_iommu_release_device(struct device *dev)
|
||||
{
|
||||
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
|
||||
struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data);
|
||||
|
||||
if (dev_data->perfopt) {
|
||||
if (WARN_ON(perfopt_put(iommu)))
|
||||
dev_err(dev, "IOMMU%d: failed to clear PerfOpt on release\n",
|
||||
iommu->index);
|
||||
dev_data->perfopt = false;
|
||||
}
|
||||
|
||||
WARN_ON(dev_data->domain);
|
||||
|
||||
@@ -2925,6 +3121,19 @@ static int blocked_domain_attach_device(struct iommu_domain *domain,
|
||||
struct iommu_domain *old)
|
||||
{
|
||||
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
|
||||
struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data);
|
||||
|
||||
/*
|
||||
* blocked_domain is also the .release_domain, so this is the normal
|
||||
* teardown path: drop the reference and clear the flag here too, and
|
||||
* don't fail teardown if the WARN-guarded write doesn't stick.
|
||||
*/
|
||||
if (dev_data->perfopt) {
|
||||
if (WARN_ON(perfopt_put(iommu)))
|
||||
dev_err(dev, "IOMMU%d: failed to clear PerfOpt for blocked domain\n",
|
||||
iommu->index);
|
||||
dev_data->perfopt = false;
|
||||
}
|
||||
|
||||
if (dev_data->domain)
|
||||
detach_device(dev);
|
||||
@@ -2993,6 +3202,9 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev,
|
||||
struct amd_iommu *iommu = get_amd_iommu_from_dev(dev);
|
||||
int ret;
|
||||
|
||||
if (dev_data->perfopt && !pdom_is_in_pt_mode(domain))
|
||||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* Skip attach device to domain if new domain is same as
|
||||
* devices current domain
|
||||
@@ -3137,12 +3349,8 @@ static void amd_iommu_get_resv_regions(struct device *dev,
|
||||
list_add_tail(®ion->list, head);
|
||||
}
|
||||
|
||||
region = iommu_alloc_resv_region(MSI_RANGE_START,
|
||||
MSI_RANGE_END - MSI_RANGE_START + 1,
|
||||
0, IOMMU_RESV_MSI, GFP_KERNEL);
|
||||
if (!region)
|
||||
if (iommu_add_resv_x86_msi_region(head))
|
||||
return;
|
||||
list_add_tail(®ion->list, head);
|
||||
|
||||
if (amd_iommu_ht_range_ignore())
|
||||
return;
|
||||
@@ -3162,6 +3370,26 @@ static bool amd_iommu_is_attach_deferred(struct device *dev)
|
||||
return dev_data->defer_attach;
|
||||
}
|
||||
|
||||
static bool quirks_force_identity_mapping(struct pci_dev *pdev)
|
||||
{
|
||||
int class = pdev->class >> 8;
|
||||
|
||||
/* AMD GPU vendor ID */
|
||||
if (pdev->vendor != PCI_VENDOR_ID_ATI)
|
||||
return false;
|
||||
|
||||
/* GPU class */
|
||||
if (class != PCI_CLASS_DISPLAY_VGA &&
|
||||
class != PCI_CLASS_DISPLAY_OTHER)
|
||||
return false;
|
||||
|
||||
if (pci_upstream_bridge(pdev)->vendor == PCI_VENDOR_ID_ATI)
|
||||
return false;
|
||||
|
||||
/* It is the GPU in an APU, force identity domain */
|
||||
return true;
|
||||
}
|
||||
|
||||
static int amd_iommu_def_domain_type(struct device *dev)
|
||||
{
|
||||
struct iommu_dev_data *dev_data;
|
||||
@@ -3170,20 +3398,23 @@ static int amd_iommu_def_domain_type(struct device *dev)
|
||||
if (!dev_data)
|
||||
return 0;
|
||||
|
||||
if (!dev_is_pci(dev))
|
||||
return 0;
|
||||
|
||||
/* Always use DMA domain for untrusted device */
|
||||
if (dev_is_pci(dev) && to_pci_dev(dev)->untrusted)
|
||||
if (to_pci_dev(dev)->untrusted)
|
||||
return IOMMU_DOMAIN_DMA;
|
||||
|
||||
/*
|
||||
* Do not identity map IOMMUv2 capable devices when:
|
||||
* - memory encryption is active, because some of those devices
|
||||
* (AMD GPUs) don't have the encryption bit in their DMA-mask
|
||||
* and require remapping.
|
||||
* - SNP is enabled, because it prohibits DTE[Mode]=0.
|
||||
*/
|
||||
if (pdev_pasid_supported(dev_data) &&
|
||||
!cc_platform_has(CC_ATTR_MEM_ENCRYPT) &&
|
||||
!amd_iommu_snp_en) {
|
||||
/* Apply device specific quirks */
|
||||
if (quirks_force_identity_mapping(to_pci_dev(dev))) {
|
||||
/*
|
||||
* When memory encryption is active, some of these devices
|
||||
* don't have the encryption bit in their DMA-mask and
|
||||
* require remapping.
|
||||
*/
|
||||
if (cc_platform_has(CC_ATTR_MEM_ENCRYPT))
|
||||
return 0;
|
||||
|
||||
return IOMMU_DOMAIN_IDENTITY;
|
||||
}
|
||||
|
||||
@@ -3902,8 +4133,11 @@ static int irq_remapping_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
out_free_data:
|
||||
for (i--; i >= 0; i--) {
|
||||
irq_data = irq_domain_get_irq_data(domain, virq + i);
|
||||
if (irq_data)
|
||||
kfree(irq_data->chip_data);
|
||||
if (irq_data && irq_data->chip_data) {
|
||||
data = irq_data->chip_data;
|
||||
kfree(data->entry);
|
||||
kfree(data);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < nr_irqs; i++)
|
||||
free_irte(iommu, devid, index + i);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <linux/io-pgtable.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
@@ -267,6 +268,7 @@ struct apple_dart_domain {
|
||||
struct io_pgtable_ops *pgtbl_ops;
|
||||
|
||||
bool finalized;
|
||||
u64 mask;
|
||||
struct mutex init_lock;
|
||||
struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE];
|
||||
|
||||
@@ -285,6 +287,13 @@ struct apple_dart_master_cfg {
|
||||
/* Intersection of DART capabilitles */
|
||||
u32 supports_bypass : 1;
|
||||
|
||||
/*
|
||||
* DMA aperture start and end to be overridden by "iommus"' phandle
|
||||
* args. By default determined by DART's ias but may be outside of it.
|
||||
*/
|
||||
dma_addr_t dma_min;
|
||||
dma_addr_t dma_max;
|
||||
|
||||
struct apple_dart_stream_map stream_maps[MAX_DARTS_PER_DEVICE];
|
||||
};
|
||||
|
||||
@@ -537,7 +546,7 @@ static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain,
|
||||
if (!ops)
|
||||
return 0;
|
||||
|
||||
return ops->iova_to_phys(ops, iova);
|
||||
return ops->iova_to_phys(ops, iova & dart_domain->mask);
|
||||
}
|
||||
|
||||
static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
|
||||
@@ -551,8 +560,8 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
|
||||
if (!ops)
|
||||
return -ENODEV;
|
||||
|
||||
return ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot, gfp,
|
||||
mapped);
|
||||
return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize,
|
||||
pgcount, prot, gfp, mapped);
|
||||
}
|
||||
|
||||
static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
|
||||
@@ -563,7 +572,8 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
|
||||
struct apple_dart_domain *dart_domain = to_dart_domain(domain);
|
||||
struct io_pgtable_ops *ops = dart_domain->pgtbl_ops;
|
||||
|
||||
return ops->unmap_pages(ops, iova, pgsize, pgcount, gather);
|
||||
return ops->unmap_pages(ops, iova & dart_domain->mask, pgsize, pgcount,
|
||||
gather);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -590,6 +600,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
|
||||
{
|
||||
struct apple_dart *dart = cfg->stream_maps[0].dart;
|
||||
struct io_pgtable_cfg pgtbl_cfg;
|
||||
u32 ias = min_t(u32, dart->ias, fls64(cfg->dma_max));
|
||||
int ret = 0;
|
||||
int i, j;
|
||||
|
||||
@@ -610,7 +621,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
|
||||
|
||||
pgtbl_cfg = (struct io_pgtable_cfg){
|
||||
.pgsize_bitmap = dart->pgsize,
|
||||
.ias = dart->ias,
|
||||
.ias = ias,
|
||||
.oas = dart->oas,
|
||||
.coherent_walk = 1,
|
||||
.iommu_dev = dart->dev,
|
||||
@@ -623,10 +634,10 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
|
||||
goto done;
|
||||
}
|
||||
|
||||
dart_domain->mask = DMA_BIT_MASK(pgtbl_cfg.ias);
|
||||
dart_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
|
||||
dart_domain->domain.geometry.aperture_start = 0;
|
||||
dart_domain->domain.geometry.aperture_end =
|
||||
(dma_addr_t)DMA_BIT_MASK(pgtbl_cfg.ias);
|
||||
dart_domain->domain.geometry.aperture_start = cfg->dma_min;
|
||||
dart_domain->domain.geometry.aperture_end = cfg->dma_max;
|
||||
dart_domain->domain.geometry.force_aperture = true;
|
||||
|
||||
dart_domain->finalized = true;
|
||||
@@ -803,20 +814,65 @@ static int apple_dart_of_xlate(struct device *dev,
|
||||
struct platform_device *iommu_pdev = of_find_device_by_node(args->np);
|
||||
struct apple_dart *dart = platform_get_drvdata(iommu_pdev);
|
||||
struct apple_dart *cfg_dart;
|
||||
dma_addr_t dma_max = DMA_BIT_MASK(dart->ias);
|
||||
dma_addr_t dma_min = 0;
|
||||
int i, sid;
|
||||
|
||||
put_device(&iommu_pdev->dev);
|
||||
|
||||
if (args->args_count != 1)
|
||||
if (args->args_count != 1 && args->args_count != 5)
|
||||
return -EINVAL;
|
||||
|
||||
sid = args->args[0];
|
||||
|
||||
if (args->args_count == 5) {
|
||||
dma_addr_t length = ((dma_addr_t)args->args[3] << 32) | args->args[4];
|
||||
|
||||
if (!length)
|
||||
return -EINVAL;
|
||||
|
||||
dma_min = ((dma_addr_t)args->args[1] << 32) | args->args[2];
|
||||
|
||||
if (!IS_ALIGNED(dma_min, dart->pgsize) ||
|
||||
!IS_ALIGNED(length, dart->pgsize)) {
|
||||
dev_err(dev, "Unaligned DMA window %pad, %pad (0x%x)\n",
|
||||
&dma_min, &length, dart->pgsize);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (check_add_overflow(dma_min, length - 1, &dma_max)) {
|
||||
dev_err(dev, "DMA window length (%pad) overflows range for start %pad\n",
|
||||
&length, &dma_min);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure that the DMA window does not exceed the DART's ias.
|
||||
*/
|
||||
if ((dma_min ^ dma_max) & ~DMA_BIT_MASK(dart->ias)) {
|
||||
dev_err(dev, "Invalid DMA window for ias=%d\n",
|
||||
dart->ias);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cfg) {
|
||||
cfg = kzalloc_obj(*cfg);
|
||||
if (!cfg)
|
||||
return -ENOMEM;
|
||||
/* Will be ANDed with DART capabilities */
|
||||
cfg->supports_bypass = true;
|
||||
/* Will be merged with other DARTs to the common range. */
|
||||
cfg->dma_min = dma_min;
|
||||
cfg->dma_max = dma_max;
|
||||
} else {
|
||||
if (dma_min >= cfg->dma_max || cfg->dma_min >= dma_max) {
|
||||
dev_err(dev, "non-overlapping DMA windows: %pad..%pad, %pad..%pad\n",
|
||||
&dma_min, &dma_max,
|
||||
&cfg->dma_min, &cfg->dma_max);
|
||||
return -EINVAL;
|
||||
}
|
||||
cfg->dma_min = max(dma_min, cfg->dma_min);
|
||||
cfg->dma_max = min(dma_max, cfg->dma_max);
|
||||
}
|
||||
dev_iommu_priv_set(dev, cfg);
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,
|
||||
arm_smmu_install_ste_for_dev(master, &ste);
|
||||
arm_smmu_attach_commit(&state);
|
||||
mutex_unlock(&arm_smmu_asid_lock);
|
||||
arm_smmu_attach_release(&state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,16 +140,19 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
|
||||
{
|
||||
struct arm_smmu_domain *smmu_domain =
|
||||
container_of(mn, struct arm_smmu_domain, mmu_notifier);
|
||||
size_t size;
|
||||
struct arm_smmu_tlbi tlbi = {
|
||||
.tgsz_lg2 = smmu_domain->tgsz_lg2,
|
||||
.iova = start,
|
||||
/*
|
||||
* The mm_types defines vm_end as the first byte after the end
|
||||
* address, different from IOMMU subsystem using the last
|
||||
* address of an address range.
|
||||
*/
|
||||
.size = end - start,
|
||||
.iopte_size = PAGE_SIZE,
|
||||
};
|
||||
|
||||
/*
|
||||
* The mm_types defines vm_end as the first byte after the end address,
|
||||
* different from IOMMU subsystem using the last address of an address
|
||||
* range. So do a simple translation here by calculating size correctly.
|
||||
*/
|
||||
size = end - start;
|
||||
|
||||
arm_smmu_domain_inv_range(smmu_domain, start, size, PAGE_SIZE, false);
|
||||
arm_smmu_domain_tlbi(&tlbi, smmu_domain);
|
||||
}
|
||||
|
||||
static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
|
||||
@@ -215,6 +218,13 @@ bool arm_smmu_sva_supported(struct arm_smmu_device *smmu)
|
||||
if (system_supports_haft())
|
||||
feat_mask |= ARM_SMMU_FEAT_HAFT;
|
||||
|
||||
/*
|
||||
* The workaround for ARM_SMMU_OPT_FULL_CONT_RANGE_INV requires range
|
||||
* invalidation support.
|
||||
*/
|
||||
if (smmu->options & ARM_SMMU_OPT_FULL_CONT_RANGE_INV)
|
||||
feat_mask |= ARM_SMMU_FEAT_RANGE_INV;
|
||||
|
||||
if ((smmu->features & feat_mask) != feat_mask)
|
||||
return false;
|
||||
|
||||
@@ -338,6 +348,7 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,
|
||||
* ARM_SMMU_FEAT_RANGE_INV is present
|
||||
*/
|
||||
smmu_domain->domain.pgsize_bitmap = PAGE_SIZE;
|
||||
smmu_domain->tgsz_lg2 = PAGE_SHIFT;
|
||||
smmu_domain->stage = ARM_SMMU_DOMAIN_SVA;
|
||||
smmu_domain->smmu = smmu;
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ struct arm_smmu_test_writer {
|
||||
static struct arm_smmu_ste bypass_ste;
|
||||
static struct arm_smmu_ste abort_ste;
|
||||
static struct arm_smmu_device smmu = {
|
||||
.features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR
|
||||
.features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR |
|
||||
ARM_SMMU_FEAT_RANGE_INV,
|
||||
.options = ARM_SMMU_OPT_FULL_CONT_RANGE_INV,
|
||||
};
|
||||
static struct mm_struct sva_mm = {
|
||||
.pgd = (void *)0xdaedbeefdeadbeefULL,
|
||||
@@ -125,8 +127,8 @@ arm_smmu_v3_test_debug_print_used_bits(struct arm_smmu_entry_writer *writer,
|
||||
{
|
||||
__le64 used_bits[NUM_ENTRY_QWORDS] = {};
|
||||
|
||||
arm_smmu_get_ste_used(ste, used_bits);
|
||||
pr_debug("STE used bits: ");
|
||||
writer->ops->get_used(ste, used_bits);
|
||||
pr_debug("Entry used bits: ");
|
||||
print_hex_dump_debug(" ", DUMP_PREFIX_NONE, 16, 8, used_bits,
|
||||
sizeof(used_bits), false);
|
||||
}
|
||||
@@ -643,8 +645,10 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
|
||||
const int *ids, const int *users,
|
||||
const int *ssids)
|
||||
{
|
||||
KUNIT_EXPECT_EQ(test, invs->num_invs, num_invs);
|
||||
KUNIT_ASSERT_EQ(test, invs->num_invs, num_invs);
|
||||
KUNIT_EXPECT_EQ(test, invs->num_trashes, num_trashes);
|
||||
KUNIT_EXPECT_TRUE(test, invs->has_range_inv);
|
||||
KUNIT_EXPECT_TRUE(test, invs->has_full_cont_range_inv);
|
||||
while (num_invs--) {
|
||||
KUNIT_EXPECT_EQ(test, invs->inv[num_invs].id, ids[num_invs]);
|
||||
KUNIT_EXPECT_EQ(test, READ_ONCE(invs->inv[num_invs].users),
|
||||
@@ -654,38 +658,43 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
|
||||
}
|
||||
|
||||
static struct arm_smmu_invs invs1 = {
|
||||
.max_invs = 3,
|
||||
.num_invs = 3,
|
||||
.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },
|
||||
{ .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
|
||||
{ .type = INV_TYPE_ATS, .id = 3, }, },
|
||||
.inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, },
|
||||
{ .smmu = &smmu, .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 3, }, },
|
||||
};
|
||||
|
||||
static struct arm_smmu_invs invs2 = {
|
||||
.max_invs = 3,
|
||||
.num_invs = 3,
|
||||
.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
|
||||
{ .type = INV_TYPE_ATS, .id = 4, },
|
||||
{ .type = INV_TYPE_ATS, .id = 5, }, },
|
||||
.inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 4, },
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, },
|
||||
};
|
||||
|
||||
static struct arm_smmu_invs invs3 = {
|
||||
.max_invs = 3,
|
||||
.num_invs = 3,
|
||||
.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
|
||||
{ .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
|
||||
{ .type = INV_TYPE_ATS, .id = 6, }, },
|
||||
.inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 6, }, },
|
||||
};
|
||||
|
||||
static struct arm_smmu_invs invs4 = {
|
||||
.max_invs = 3,
|
||||
.num_invs = 3,
|
||||
.inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
|
||||
{ .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
|
||||
{ .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
|
||||
.inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
|
||||
};
|
||||
|
||||
static struct arm_smmu_invs invs5 = {
|
||||
.max_invs = 3,
|
||||
.num_invs = 3,
|
||||
.inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
|
||||
{ .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
|
||||
{ .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
|
||||
.inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
|
||||
{ .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
|
||||
};
|
||||
|
||||
static void arm_smmu_v3_invs_test(struct kunit *test)
|
||||
@@ -704,17 +713,22 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
|
||||
|
||||
/* New array */
|
||||
test_a = arm_smmu_invs_alloc(0);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
|
||||
KUNIT_EXPECT_EQ(test, test_a->num_invs, 0);
|
||||
KUNIT_EXPECT_FALSE(test, test_a->has_range_inv);
|
||||
KUNIT_EXPECT_FALSE(test, test_a->has_full_cont_range_inv);
|
||||
|
||||
/* Test1: merge invs1 (new array) */
|
||||
test_b = arm_smmu_invs_merge(test_a, &invs1);
|
||||
kfree(test_a);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
|
||||
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results1[0]), 0,
|
||||
results1[0], results1[1], results1[2]);
|
||||
|
||||
/* Test2: merge invs2 (new array) */
|
||||
test_a = arm_smmu_invs_merge(test_b, &invs2);
|
||||
kfree(test_b);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
|
||||
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results2[0]), 0,
|
||||
results2[0], results2[1], results2[2]);
|
||||
|
||||
@@ -726,6 +740,7 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
|
||||
/* Test4: merge invs3 (new array) */
|
||||
test_b = arm_smmu_invs_merge(test_a, &invs3);
|
||||
kfree(test_a);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
|
||||
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results4[0]), 0,
|
||||
results4[0], results4[1], results4[2]);
|
||||
|
||||
@@ -737,6 +752,7 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
|
||||
/* Test6: purge test_b (new array) */
|
||||
test_a = arm_smmu_invs_purge(test_b);
|
||||
kfree(test_b);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
|
||||
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results6[0]), 0,
|
||||
results6[0], results6[1], results6[2]);
|
||||
|
||||
@@ -748,12 +764,14 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
|
||||
/* Test8: merge invs4 (new array) */
|
||||
test_b = arm_smmu_invs_merge(test_a, &invs4);
|
||||
kfree(test_a);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
|
||||
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results7[0]), 0,
|
||||
results7[0], results7[1], results7[2]);
|
||||
|
||||
/* Test9: merge invs5 (new array) */
|
||||
test_a = arm_smmu_invs_merge(test_b, &invs5);
|
||||
kfree(test_b);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_a);
|
||||
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results8[0]), 0,
|
||||
results8[0], results8[1], results8[2]);
|
||||
|
||||
@@ -765,6 +783,7 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
|
||||
/* Test11: purge test_a (new array) */
|
||||
test_b = arm_smmu_invs_purge(test_a);
|
||||
kfree(test_a);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_b);
|
||||
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results10[0]), 0,
|
||||
results10[0], results10[1], results10[2]);
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ module_param(disable_msipolling, bool, 0444);
|
||||
MODULE_PARM_DESC(disable_msipolling,
|
||||
"Disable MSI-based polling for CMD_SYNC completion.");
|
||||
|
||||
static unsigned int cmdq_max_n_shift = CMDQ_MAX_SZ_SHIFT;
|
||||
module_param(cmdq_max_n_shift, uint, 0444);
|
||||
MODULE_PARM_DESC(cmdq_max_n_shift,
|
||||
"Cap on the command queue depth, as log2 of the number of entries. Defaults to the hardware maximum; the queue never shrinks below one page. A kdump kernel always uses one page.");
|
||||
|
||||
static const struct iommu_ops arm_smmu_ops;
|
||||
static struct iommu_dirty_ops arm_smmu_dirty_ops;
|
||||
|
||||
@@ -91,6 +96,7 @@ DEFINE_MUTEX(arm_smmu_asid_lock);
|
||||
static struct arm_smmu_option_prop arm_smmu_options[] = {
|
||||
{ ARM_SMMU_OPT_SKIP_PREFETCH, "hisilicon,broken-prefetch-cmd" },
|
||||
{ ARM_SMMU_OPT_PAGE0_REGS_ONLY, "cavium,cn9900-broken-page1-regspace"},
|
||||
{ ARM_SMMU_OPT_OVR_INSTCFG_DATA, "arm,instdata-override"},
|
||||
{ 0, NULL},
|
||||
};
|
||||
|
||||
@@ -181,8 +187,8 @@ static void queue_sync_cons_out(struct arm_smmu_queue *q)
|
||||
|
||||
static void queue_inc_cons(struct arm_smmu_ll_queue *q)
|
||||
{
|
||||
u32 cons = (Q_WRP(q, q->cons) | Q_IDX(q, q->cons)) + 1;
|
||||
q->cons = Q_OVF(q->cons) | Q_WRP(q, cons) | Q_IDX(q, cons);
|
||||
u32 cons = Q_POS(q, q->cons) + 1;
|
||||
q->cons = Q_OVF(q->cons) | Q_POS(q, cons);
|
||||
}
|
||||
|
||||
static void queue_sync_cons_ovf(struct arm_smmu_queue *q)
|
||||
@@ -192,8 +198,7 @@ static void queue_sync_cons_ovf(struct arm_smmu_queue *q)
|
||||
if (likely(Q_OVF(llq->prod) == Q_OVF(llq->cons)))
|
||||
return;
|
||||
|
||||
llq->cons = Q_OVF(llq->prod) | Q_WRP(llq, llq->cons) |
|
||||
Q_IDX(llq, llq->cons);
|
||||
llq->cons = Q_OVF(llq->prod) | Q_POS(llq, llq->cons);
|
||||
queue_sync_cons_out(q);
|
||||
}
|
||||
|
||||
@@ -218,8 +223,8 @@ static int queue_sync_prod_in(struct arm_smmu_queue *q)
|
||||
|
||||
static u32 queue_inc_prod_n(struct arm_smmu_ll_queue *q, int n)
|
||||
{
|
||||
u32 prod = (Q_WRP(q, q->prod) | Q_IDX(q, q->prod)) + n;
|
||||
return Q_OVF(q->prod) | Q_WRP(q, prod) | Q_IDX(q, prod);
|
||||
u32 prod = Q_POS(q, q->prod) + n;
|
||||
return Q_OVF(q->prod) | Q_POS(q, prod);
|
||||
}
|
||||
|
||||
static void queue_poll_init(struct arm_smmu_device *smmu,
|
||||
@@ -1053,6 +1058,19 @@ static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_l,
|
||||
return arm_smmu_inv_cmp(cur_l, &invs_r->inv[next_r]);
|
||||
}
|
||||
|
||||
static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,
|
||||
const struct arm_smmu_inv *inv)
|
||||
{
|
||||
if (arm_smmu_inv_is_ats(inv))
|
||||
invs->has_ats = true;
|
||||
|
||||
if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
|
||||
invs->has_range_inv = true;
|
||||
if (inv->smmu->options & ARM_SMMU_OPT_FULL_CONT_RANGE_INV)
|
||||
invs->has_full_cont_range_inv = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* arm_smmu_invs_for_each_cmp - Iterate over two sorted arrays computing for
|
||||
* arm_smmu_invs_merge() or arm_smmu_invs_unref()
|
||||
@@ -1123,8 +1141,7 @@ struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
|
||||
*/
|
||||
if (new != new_invs->inv)
|
||||
WARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);
|
||||
if (arm_smmu_inv_is_ats(new))
|
||||
new_invs->has_ats = true;
|
||||
arm_smmu_invs_update_caps(new_invs, new);
|
||||
new++;
|
||||
}
|
||||
|
||||
@@ -1234,8 +1251,7 @@ struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs)
|
||||
|
||||
arm_smmu_invs_for_each_entry(invs, i, inv) {
|
||||
new_invs->inv[num_invs] = *inv;
|
||||
if (arm_smmu_inv_is_ats(inv))
|
||||
new_invs->has_ats = true;
|
||||
arm_smmu_invs_update_caps(new_invs, inv);
|
||||
num_invs++;
|
||||
}
|
||||
|
||||
@@ -1271,7 +1287,8 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
|
||||
cpu_to_le64(STRTAB_STE_1_S1DSS | STRTAB_STE_1_S1CIR |
|
||||
STRTAB_STE_1_S1COR | STRTAB_STE_1_S1CSH |
|
||||
STRTAB_STE_1_S1STALLD | STRTAB_STE_1_STRW |
|
||||
STRTAB_STE_1_EATS | STRTAB_STE_1_MEV);
|
||||
STRTAB_STE_1_EATS | STRTAB_STE_1_MEV |
|
||||
STRTAB_STE_1_INSTCFG);
|
||||
used_bits[2] |= cpu_to_le64(STRTAB_STE_2_S2VMID);
|
||||
|
||||
/*
|
||||
@@ -1287,7 +1304,8 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
|
||||
if (cfg & BIT(1)) {
|
||||
used_bits[1] |=
|
||||
cpu_to_le64(STRTAB_STE_1_S2FWB | STRTAB_STE_1_EATS |
|
||||
STRTAB_STE_1_SHCFG | STRTAB_STE_1_MEV);
|
||||
STRTAB_STE_1_SHCFG | STRTAB_STE_1_MEV |
|
||||
STRTAB_STE_1_INSTCFG);
|
||||
used_bits[2] |=
|
||||
cpu_to_le64(STRTAB_STE_2_S2VMID | STRTAB_STE_2_VTCR |
|
||||
STRTAB_STE_2_S2AA64 | STRTAB_STE_2_S2ENDI |
|
||||
@@ -1515,6 +1533,9 @@ static void arm_smmu_write_cd_l1_desc(struct arm_smmu_cdtab_l1 *dst,
|
||||
{
|
||||
u64 val = (l2ptr_dma & CTXDESC_L1_DESC_L2PTR_MASK) | CTXDESC_L1_DESC_V;
|
||||
|
||||
/* Ensure the zero-cleared L2 table is fully visible. */
|
||||
dma_wmb();
|
||||
|
||||
/* The HW has 64 bit atomicity with stores to the L2 CD table */
|
||||
WRITE_ONCE(dst->l2ptr, cpu_to_le64(val));
|
||||
}
|
||||
@@ -1804,6 +1825,9 @@ static void arm_smmu_write_strtab_l1_desc(struct arm_smmu_strtab_l1 *dst,
|
||||
val |= FIELD_PREP(STRTAB_L1_DESC_SPAN, STRTAB_SPLIT + 1);
|
||||
val |= l2ptr_dma & STRTAB_L1_DESC_L2PTR_MASK;
|
||||
|
||||
/* Ensure the new L2 table is fully visible. */
|
||||
dma_wmb();
|
||||
|
||||
/* The HW has 64 bit atomicity with stores to the L2 STE table */
|
||||
WRITE_ONCE(dst->l2ptr, cpu_to_le64(val));
|
||||
}
|
||||
@@ -1900,7 +1924,11 @@ void arm_smmu_make_cdtable_ste(struct arm_smmu_ste *target,
|
||||
STRTAB_STE_1_S1STALLD :
|
||||
0) |
|
||||
FIELD_PREP(STRTAB_STE_1_EATS,
|
||||
ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0));
|
||||
ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0) |
|
||||
FIELD_PREP(STRTAB_STE_1_INSTCFG,
|
||||
smmu->options & ARM_SMMU_OPT_OVR_INSTCFG_DATA ?
|
||||
STRTAB_STE_1_INSTCFG_DATA :
|
||||
STRTAB_STE_1_INSTCFG_INCOMING));
|
||||
|
||||
if ((smmu->features & ARM_SMMU_FEAT_ATTR_TYPES_OVR) &&
|
||||
s1dss == STRTAB_STE_1_S1DSS_BYPASS)
|
||||
@@ -1952,7 +1980,11 @@ void arm_smmu_make_s2_domain_ste(struct arm_smmu_ste *target,
|
||||
|
||||
target->data[1] = cpu_to_le64(
|
||||
FIELD_PREP(STRTAB_STE_1_EATS,
|
||||
ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0));
|
||||
ats_enabled ? STRTAB_STE_1_EATS_TRANS : 0) |
|
||||
FIELD_PREP(STRTAB_STE_1_INSTCFG,
|
||||
smmu->options & ARM_SMMU_OPT_OVR_INSTCFG_DATA ?
|
||||
STRTAB_STE_1_INSTCFG_DATA :
|
||||
STRTAB_STE_1_INSTCFG_INCOMING));
|
||||
|
||||
if (pgtbl_cfg->quirks & IO_PGTABLE_QUIRK_ARM_S2FWB)
|
||||
target->data[1] |= cpu_to_le64(STRTAB_STE_1_S2FWB);
|
||||
@@ -2363,8 +2395,8 @@ static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static struct arm_smmu_cmd
|
||||
arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
|
||||
static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,
|
||||
struct arm_smmu_tlbi *tlbi)
|
||||
{
|
||||
size_t log2_span;
|
||||
size_t span_mask;
|
||||
@@ -2386,8 +2418,8 @@ arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
|
||||
* This has the unpleasant side-effect of invalidating all PASID-tagged
|
||||
* ATC entries within the address range.
|
||||
*/
|
||||
page_start = iova >> inval_grain_shift;
|
||||
page_end = (iova + size - 1) >> inval_grain_shift;
|
||||
page_start = tlbi->iova >> inval_grain_shift;
|
||||
page_end = (tlbi->iova + tlbi->size - 1) >> inval_grain_shift;
|
||||
|
||||
/*
|
||||
* In an ATS Invalidate Request, the address must be aligned on the
|
||||
@@ -2460,126 +2492,231 @@ static void arm_smmu_tlb_inv_context(void *cookie)
|
||||
arm_smmu_domain_inv(smmu_domain);
|
||||
}
|
||||
|
||||
static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
|
||||
struct arm_smmu_cmdq_batch *cmds,
|
||||
struct arm_smmu_cmd *cmd, bool leaf,
|
||||
unsigned long iova, size_t size,
|
||||
size_t granule, size_t pgsize)
|
||||
/*
|
||||
* Check address alignment for TTL hint per SMMUv3 H.a Section 4.4.1. Address
|
||||
* bits below the alignment must be zero, otherwise UNPREDICTABLE.
|
||||
*/
|
||||
static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
|
||||
unsigned int ttl)
|
||||
{
|
||||
unsigned long end = iova + size, num_pages = 0, tg = pgsize;
|
||||
u64 orig_data0 = cmd->data[0];
|
||||
size_t inv_range = granule;
|
||||
u8 ttl = 0, tg_enc = 0;
|
||||
unsigned int pgsz_lg2 = arm_smmu_pt_level_to_lg2sz(tg, 3 - ttl);
|
||||
|
||||
if (WARN_ON_ONCE(!size))
|
||||
return;
|
||||
|
||||
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
|
||||
num_pages = size >> tg;
|
||||
|
||||
/* Convert page size of 12,14,16 (log2) to 1,2,3 */
|
||||
tg_enc = (tg - 10) / 2;
|
||||
|
||||
/*
|
||||
* Determine what level the granule is at. For non-leaf, both
|
||||
* io-pgtable and SVA pass a nominal last-level granule because
|
||||
* they don't know what level(s) actually apply, so ignore that
|
||||
* and leave TTL=0. However for various errata reasons we still
|
||||
* want to use a range command, so avoid the SVA corner case
|
||||
* where both scale and num could be 0 as well.
|
||||
*/
|
||||
if (leaf)
|
||||
ttl = 4 - ((ilog2(granule) - 3) / (tg - 3));
|
||||
else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
|
||||
num_pages++;
|
||||
}
|
||||
|
||||
while (iova < end) {
|
||||
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
|
||||
/*
|
||||
* On each iteration of the loop, the range is 5 bits
|
||||
* worth of the aligned size remaining.
|
||||
* The range in pages is:
|
||||
*
|
||||
* range = (num_pages & (0x1f << __ffs(num_pages)))
|
||||
*/
|
||||
unsigned long scale, num;
|
||||
|
||||
/* Determine the power of 2 multiple number of pages */
|
||||
scale = __ffs(num_pages);
|
||||
|
||||
/* Determine how many chunks of 2^scale size we have */
|
||||
num = (num_pages >> scale) & CMDQ_TLBI_RANGE_NUM_MAX;
|
||||
|
||||
/* Keep the pre-DS 5-bit truncation when scale > 31 */
|
||||
cmd->data[0] = orig_data0 |
|
||||
FIELD_PREP(CMDQ_TLBI_0_NUM, num - 1) |
|
||||
FIELD_PREP(CMDQ_TLBI_0_SCALE, scale & 0x1f);
|
||||
|
||||
/* range is num * 2^scale * pgsize */
|
||||
inv_range = num << (scale + tg);
|
||||
|
||||
/* Clear out the lower order bits for the next iteration */
|
||||
num_pages -= num << scale;
|
||||
}
|
||||
|
||||
/*
|
||||
* IPA has fewer bits than VA, but they are reserved in the
|
||||
* command and something would be very broken if iova had them
|
||||
* set.
|
||||
*/
|
||||
cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
|
||||
FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
|
||||
FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
|
||||
(iova & ~GENMASK_U64(11, 0));
|
||||
|
||||
arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
|
||||
iova += inv_range;
|
||||
}
|
||||
return !(address & GENMASK_U64(pgsz_lg2 - 1, 0));
|
||||
}
|
||||
|
||||
static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
|
||||
size_t granule)
|
||||
struct arm_smmu_range_inv {
|
||||
u64 start_tg;
|
||||
/* Normal integer, not encoded. 0 means 0.*/
|
||||
u64 num;
|
||||
unsigned int scale;
|
||||
};
|
||||
|
||||
static unsigned int arm_smmu_range_inv_calc_scale(u64 num_tg)
|
||||
{
|
||||
size_t max_tlbi_ops;
|
||||
return fls64((num_tg - 1) / (CMDQ_TLBI_RANGE_NUM_MAX + 1));
|
||||
}
|
||||
|
||||
/* 0 size means invalidate all */
|
||||
if (!size || size == SIZE_MAX)
|
||||
return true;
|
||||
static u64 arm_smmu_range_inv_calc_num(u64 num_tg, unsigned int scale)
|
||||
{
|
||||
return DIV_ROUND_UP_ULL(num_tg, 1ULL << scale);
|
||||
}
|
||||
|
||||
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
|
||||
return false;
|
||||
/*
|
||||
* Initialize the smallest range invalidation covering num_tg and ending at
|
||||
* last_tg.
|
||||
*/
|
||||
static struct arm_smmu_range_inv arm_smmu_range_inv_init_end(u64 last_tg,
|
||||
u64 num_tg)
|
||||
{
|
||||
struct arm_smmu_range_inv range_inv = {};
|
||||
|
||||
if (!num_tg)
|
||||
return range_inv;
|
||||
|
||||
range_inv.scale = arm_smmu_range_inv_calc_scale(num_tg);
|
||||
range_inv.num = arm_smmu_range_inv_calc_num(num_tg, range_inv.scale);
|
||||
range_inv.start_tg = last_tg - ((range_inv.num << range_inv.scale) - 1);
|
||||
return range_inv;
|
||||
}
|
||||
|
||||
static void
|
||||
arm_smmu_tlbi_add_range_cmd(struct arm_smmu_tlbi *tlbi,
|
||||
const struct arm_smmu_range_inv *range_inv, u8 ttl,
|
||||
u8 tg_enc)
|
||||
{
|
||||
struct arm_smmu_cmd *cmd = &tlbi->range.cmds[tlbi->range.num_cmds++];
|
||||
unsigned int tgsz_lg2 = tg_enc * 2 + 10;
|
||||
u64 iova = range_inv->start_tg << tgsz_lg2;
|
||||
unsigned int num = range_inv->num - 1;
|
||||
|
||||
/* 16K granule TTL=1 is reserved (Section 4.4.1) */
|
||||
if (WARN_ON(tgsz_lg2 == 14 && ttl == 1))
|
||||
ttl = 0;
|
||||
|
||||
/* Verify address alignment for the TTL hint */
|
||||
if (ttl && !arm_smmu_ttl_addr_aligned(iova, tgsz_lg2, ttl))
|
||||
ttl = 0;
|
||||
|
||||
/*
|
||||
* Borrowed from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h,
|
||||
* this is used as a threshold to replace "size_opcode" commands with a
|
||||
* single "nsize_opcode" command, when SMMU doesn't implement the range
|
||||
* invalidation feature, where there can be too many per-granule TLBIs,
|
||||
* resulting in a soft lockup.
|
||||
* SMMUv3 H.a Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is Reserved
|
||||
* and causes CERROR_ILL. Single tg uses NUM=0, SCALE=0 with a TTL hint
|
||||
* to target only the exact leaf entry.
|
||||
*
|
||||
* For a single tg invalidation a 0 TTL can come from places like the
|
||||
* SVA path that don't have enough information to get a TTL, or as a
|
||||
* side of effect of the splitting.
|
||||
*
|
||||
* A single-TG invalidation cannot reach this point if it is part of a
|
||||
* CONT group, so it is safe to transform it into a single invalidation.
|
||||
* The ARM_SMMU_OPT_FULL_CONT_RANGE_INV errata does not apply.
|
||||
*/
|
||||
max_tlbi_ops = 1 << (ilog2(granule) - 3);
|
||||
return size >= max_tlbi_ops * granule;
|
||||
if (!num && !range_inv->scale && !ttl)
|
||||
tg_enc = 0;
|
||||
|
||||
cmd->data[0] = FIELD_PREP(CMDQ_TLBI_0_NUM, num) |
|
||||
FIELD_PREP(CMDQ_TLBI_0_SCALE, range_inv->scale);
|
||||
cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
|
||||
FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
|
||||
FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) | iova;
|
||||
}
|
||||
|
||||
/* Used by non INV_TYPE_ATS* invalidations */
|
||||
static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
|
||||
struct arm_smmu_cmdq_batch *cmds,
|
||||
struct arm_smmu_cmd *cmd,
|
||||
bool leaf,
|
||||
unsigned long iova, size_t size,
|
||||
unsigned int granule)
|
||||
/*
|
||||
* Generate up to two range TLBI command payloads covering [iova, iova+size).
|
||||
* Sets use_full_inv if the range is too large to represent.
|
||||
*
|
||||
* Normally the first range invalidation is the largest representable span which
|
||||
* does not exceed the requested range. If necessary, the second range
|
||||
* invalidation is the smallest representable range covering the remainder and
|
||||
* is anchored at the end. Any excess coverage from the second range
|
||||
* invalidation overlaps the first instead of exceeding the requested range.
|
||||
*
|
||||
* For SVA on an invs containing an SMMU with ARM_SMMU_OPT_FULL_CONT_RANGE_INV,
|
||||
* produce only a single range invalidation and overinvalidate so any potential CONT is
|
||||
* covered by one command.
|
||||
*/
|
||||
static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi,
|
||||
bool single_range_inv)
|
||||
{
|
||||
if (arm_smmu_inv_size_too_big(inv->smmu, size, granule)) {
|
||||
struct arm_smmu_cmd nsize_cmd = *cmd;
|
||||
u8 tgsz_lg2 = tlbi->tgsz_lg2;
|
||||
struct arm_smmu_range_inv first = { .start_tg = tlbi->iova >>
|
||||
tgsz_lg2 };
|
||||
u64 last_tg = (tlbi->iova + tlbi->size - 1) >> tgsz_lg2;
|
||||
u64 num_tg = last_tg - first.start_tg + 1;
|
||||
u8 tg_enc = (tgsz_lg2 - 10) / 2;
|
||||
struct arm_smmu_range_inv trail;
|
||||
u8 ttl = 0;
|
||||
|
||||
u64p_replace_bits(&nsize_cmd.data[0], inv->nsize_opcode,
|
||||
CMDQ_0_OP);
|
||||
arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, &nsize_cmd);
|
||||
/*
|
||||
* Determine what level the granule is at. For non-leaf, both io-pgtable
|
||||
* and SVA pass a nominal last-level granule because they don't know
|
||||
* what level(s) actually apply, so leave TTL=0.
|
||||
*/
|
||||
if (tlbi->leaf_only)
|
||||
ttl = 4 - ((ilog2(tlbi->iopte_size) - 3) / (tgsz_lg2 - 3));
|
||||
|
||||
/*
|
||||
* The spec defines the invalidated range as:
|
||||
* Range = ((NUM+1) * 2^SCALE) * Translation_Granule_Size
|
||||
* NUM is 5 bits, so (NUM+1) covers 1..32 granules. Find the smallest
|
||||
* SCALE at which a single command could cover num_tg.
|
||||
*
|
||||
* Unlike other IOMMUs the spec has no alignment requirement on the
|
||||
* address beyond alignment to tg (so long as TTL=0).
|
||||
*/
|
||||
first.scale = arm_smmu_range_inv_calc_scale(num_tg);
|
||||
if (first.scale > 31) {
|
||||
/* Range too large for a single command do full invalidation */
|
||||
tlbi->range.use_full_inv = true;
|
||||
return;
|
||||
}
|
||||
|
||||
arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, leaf,
|
||||
iova, size, granule, inv->pgsize);
|
||||
if (single_range_inv) {
|
||||
/*
|
||||
* Produce a single invalidation by rounding up and disabling
|
||||
* the trailer.
|
||||
*/
|
||||
first.num = arm_smmu_range_inv_calc_num(num_tg, first.scale);
|
||||
trail.num = 0;
|
||||
} else {
|
||||
/*
|
||||
* Produce two invalidations by rounding down and adding a
|
||||
* second trailing range invalidation anchored at the end.
|
||||
*/
|
||||
first.num = num_tg >> first.scale;
|
||||
trail = arm_smmu_range_inv_init_end(
|
||||
last_tg, num_tg - ((u64)first.num << first.scale));
|
||||
}
|
||||
arm_smmu_tlbi_add_range_cmd(tlbi, &first, ttl, tg_enc);
|
||||
|
||||
if (trail.num)
|
||||
arm_smmu_tlbi_add_range_cmd(tlbi, &trail, ttl, tg_enc);
|
||||
}
|
||||
|
||||
/*
|
||||
* One TLBI command per IOTLB entry, assuming the entries are all at least
|
||||
* iopte_granule sized. Sets use_full_inv if too many commands would be needed
|
||||
* which indicates too high a latency. The threshold is similar to MAX_DVM_OPS
|
||||
* in arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
|
||||
*/
|
||||
static void arm_smmu_tlbi_calc_single(struct arm_smmu_tlbi *tlbi)
|
||||
{
|
||||
unsigned long num_ops = tlbi->size / tlbi->iopte_size;
|
||||
|
||||
if (!num_ops || num_ops > 512) {
|
||||
tlbi->single.use_full_inv = true;
|
||||
return;
|
||||
}
|
||||
tlbi->single.num = num_ops;
|
||||
}
|
||||
|
||||
static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,
|
||||
struct arm_smmu_cmdq_batch *cmds,
|
||||
struct arm_smmu_cmd *cmd)
|
||||
{
|
||||
u64p_replace_bits(&cmd->data[0], inv->nsize_opcode, CMDQ_0_OP);
|
||||
arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Used by non INV_TYPE_ATS* invalidations. Returns true if it fell back to
|
||||
* full invalidation using nsize_opcode.
|
||||
*/
|
||||
static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
|
||||
struct arm_smmu_cmdq_batch *cmds,
|
||||
struct arm_smmu_cmd *cmd,
|
||||
struct arm_smmu_tlbi *tlbi)
|
||||
{
|
||||
u64 iova = tlbi->iova;
|
||||
unsigned int i;
|
||||
|
||||
if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
|
||||
if (tlbi->range.use_full_inv) {
|
||||
arm_smmu_inv_all_cmd(inv, cmds, cmd);
|
||||
return true;
|
||||
}
|
||||
for (i = 0; i < tlbi->range.num_cmds; i++) {
|
||||
struct arm_smmu_cmd range_cmd = tlbi->range.cmds[i];
|
||||
|
||||
range_cmd.data[0] |= cmd->data[0];
|
||||
range_cmd.data[1] |= cmd->data[1];
|
||||
arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds,
|
||||
&range_cmd);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tlbi->single.use_full_inv) {
|
||||
arm_smmu_inv_all_cmd(inv, cmds, cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = 0; i < tlbi->single.num; i++) {
|
||||
cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
|
||||
(iova & ~GENMASK_U64(11, 0));
|
||||
iova += tlbi->iopte_size;
|
||||
arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
|
||||
@@ -2598,10 +2735,10 @@ static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
|
||||
return false;
|
||||
}
|
||||
|
||||
static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
|
||||
unsigned long iova, size_t size,
|
||||
unsigned int granule, bool leaf)
|
||||
static void arm_smmu_domain_tlbi_inv(struct arm_smmu_tlbi *tlbi,
|
||||
struct arm_smmu_invs *invs)
|
||||
{
|
||||
struct arm_smmu_inv *used_s12_vmall = NULL;
|
||||
struct arm_smmu_cmdq_batch cmds = {};
|
||||
struct arm_smmu_inv *cur;
|
||||
struct arm_smmu_inv *end;
|
||||
@@ -2630,18 +2767,24 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
|
||||
case INV_TYPE_S1_ASID:
|
||||
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
|
||||
cur->id, 0);
|
||||
arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
|
||||
iova, size, granule);
|
||||
arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
|
||||
break;
|
||||
case INV_TYPE_S2_VMID:
|
||||
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
|
||||
0, cur->id);
|
||||
arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
|
||||
iova, size, granule);
|
||||
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode, 0,
|
||||
cur->id);
|
||||
if (arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi))
|
||||
used_s12_vmall = cur + 1;
|
||||
break;
|
||||
case INV_TYPE_S2_VMID_S1_CLEAR:
|
||||
/* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
|
||||
if (arm_smmu_inv_size_too_big(cur->smmu, size, granule))
|
||||
/*
|
||||
* S2_VMID used CMDQ_OP_TLBI_S12_VMALL which already
|
||||
* flushed S1 entries. These two types always come in
|
||||
* pairs and arm_smmu_inv_cmp() ensures that they are
|
||||
* consecutive in the list for the same SMMU. There may
|
||||
* be several pairings so check this is paired with the
|
||||
* one that did the full invalidation.
|
||||
*/
|
||||
if (used_s12_vmall == cur)
|
||||
break;
|
||||
arm_smmu_cmdq_batch_add_cmd(
|
||||
smmu, &cmds,
|
||||
@@ -2652,7 +2795,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
|
||||
arm_smmu_cmdq_batch_add_cmd(
|
||||
smmu, &cmds,
|
||||
arm_smmu_atc_inv_to_cmd(cur->id, cur->ssid,
|
||||
iova, size));
|
||||
tlbi));
|
||||
break;
|
||||
case INV_TYPE_ATS_FULL:
|
||||
arm_smmu_cmdq_batch_add_cmd(
|
||||
@@ -2679,12 +2822,20 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
|
||||
}
|
||||
}
|
||||
|
||||
void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
|
||||
unsigned long iova, size_t size,
|
||||
unsigned int granule, bool leaf)
|
||||
/*
|
||||
* Perform the invalidation with the parameters described by tlbi on everything
|
||||
* caching for smmu_domain. tlbi is an on-stack structure that contains both the
|
||||
* input description and working memory for this function it must be discarded
|
||||
* after this returns.
|
||||
*/
|
||||
void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi,
|
||||
struct arm_smmu_domain *smmu_domain)
|
||||
{
|
||||
struct arm_smmu_invs *invs;
|
||||
|
||||
if (!tlbi->single.use_full_inv)
|
||||
arm_smmu_tlbi_calc_single(tlbi);
|
||||
|
||||
/*
|
||||
* An invalidation request must follow some IOPTE change and then load
|
||||
* an invalidation array. In the meantime, a domain attachment mutates
|
||||
@@ -2702,7 +2853,7 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
|
||||
*
|
||||
* [CPU0] | [CPU1]
|
||||
* change IOPTE on new domain: |
|
||||
* arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs()
|
||||
* arm_smmu_domain_tlbi() { | arm_smmu_install_new_domain_invs()
|
||||
* smp_mb(); // ensures IOPTE | arm_smmu_install_ste_for_dev {
|
||||
* // seen by SMMU | dma_wmb(); // ensures invs update
|
||||
* // load the updated invs | // before updating STE
|
||||
@@ -2715,6 +2866,20 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
|
||||
rcu_read_lock();
|
||||
invs = rcu_dereference(smmu_domain->invs);
|
||||
|
||||
/*
|
||||
* Only precompute range invalidation commands when they will be used.
|
||||
* The invs generation ensures this matches the instances being
|
||||
* invalidated.
|
||||
*/
|
||||
if (invs->has_range_inv) {
|
||||
if (!tlbi->range.use_full_inv) {
|
||||
arm_smmu_tlbi_calc_range(
|
||||
tlbi,
|
||||
smmu_domain->stage == ARM_SMMU_DOMAIN_SVA &&
|
||||
invs->has_full_cont_range_inv);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Avoid locking unless ATS is being used. No ATC invalidation can be
|
||||
* going on after a domain is detached.
|
||||
@@ -2723,10 +2888,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
|
||||
unsigned long flags;
|
||||
|
||||
read_lock_irqsave(&invs->rwlock, flags);
|
||||
__arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
|
||||
arm_smmu_domain_tlbi_inv(tlbi, invs);
|
||||
read_unlock_irqrestore(&invs->rwlock, flags);
|
||||
} else {
|
||||
__arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
|
||||
arm_smmu_domain_tlbi_inv(tlbi, invs);
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
@@ -2742,12 +2907,23 @@ static void arm_smmu_tlb_inv_page_nosync(struct iommu_iotlb_gather *gather,
|
||||
iommu_iotlb_gather_add_page(domain, gather, iova, granule);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by io-pgtable-arm.c for each single table level it wants to remove.
|
||||
* size is the size of the table level and granule is the tg in bytes. This must
|
||||
* clear the walk cache and any leaves within the range.
|
||||
*/
|
||||
static void arm_smmu_tlb_inv_walk(unsigned long iova, size_t size,
|
||||
size_t granule, void *cookie)
|
||||
{
|
||||
struct arm_smmu_domain *smmu_domain = cookie;
|
||||
struct arm_smmu_tlbi tlbi = {
|
||||
.tgsz_lg2 = smmu_domain->tgsz_lg2,
|
||||
.iova = iova,
|
||||
.size = size,
|
||||
.iopte_size = 1 << smmu_domain->tgsz_lg2,
|
||||
};
|
||||
|
||||
arm_smmu_domain_inv_range(smmu_domain, iova, size, granule, false);
|
||||
arm_smmu_domain_tlbi(&tlbi, smmu_domain);
|
||||
}
|
||||
|
||||
static const struct iommu_flush_ops arm_smmu_flush_ops = {
|
||||
@@ -2933,6 +3109,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
|
||||
return -ENOMEM;
|
||||
|
||||
smmu_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
|
||||
smmu_domain->tgsz_lg2 = __ffs(pgtbl_cfg.pgsize_bitmap);
|
||||
smmu_domain->domain.geometry.aperture_end = (1UL << pgtbl_cfg.ias) - 1;
|
||||
smmu_domain->domain.geometry.force_aperture = true;
|
||||
if (enable_dirty && smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
|
||||
@@ -3172,15 +3349,13 @@ static void arm_smmu_disable_iopf(struct arm_smmu_master *master,
|
||||
|
||||
static struct arm_smmu_inv *
|
||||
arm_smmu_master_build_inv(struct arm_smmu_master *master,
|
||||
enum arm_smmu_inv_type type, u32 id, ioasid_t ssid,
|
||||
size_t pgsize)
|
||||
enum arm_smmu_inv_type type, u32 id, ioasid_t ssid)
|
||||
{
|
||||
struct arm_smmu_invs *build_invs = master->build_invs;
|
||||
struct arm_smmu_inv *cur, inv = {
|
||||
.smmu = master->smmu,
|
||||
.type = type,
|
||||
.id = id,
|
||||
.pgsize = pgsize,
|
||||
};
|
||||
|
||||
if (WARN_ON(build_invs->num_invs >= build_invs->max_invs))
|
||||
@@ -3232,28 +3407,24 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
|
||||
ioasid_t ssid, struct arm_smmu_domain *smmu_domain)
|
||||
{
|
||||
const bool nesting = smmu_domain->nest_parent;
|
||||
size_t pgsize = 0, i;
|
||||
size_t i;
|
||||
|
||||
iommu_group_mutex_assert(master->dev);
|
||||
|
||||
master->build_invs->num_invs = 0;
|
||||
|
||||
/* Range-based invalidation requires the leaf pgsize for calculation */
|
||||
if (master->smmu->features & ARM_SMMU_FEAT_RANGE_INV)
|
||||
pgsize = __ffs(smmu_domain->domain.pgsize_bitmap);
|
||||
|
||||
switch (smmu_domain->stage) {
|
||||
case ARM_SMMU_DOMAIN_SVA:
|
||||
case ARM_SMMU_DOMAIN_S1:
|
||||
if (!arm_smmu_master_build_inv(master, INV_TYPE_S1_ASID,
|
||||
smmu_domain->cd.asid,
|
||||
IOMMU_NO_PASID, pgsize))
|
||||
IOMMU_NO_PASID))
|
||||
return NULL;
|
||||
break;
|
||||
case ARM_SMMU_DOMAIN_S2:
|
||||
if (!arm_smmu_master_build_inv(master, INV_TYPE_S2_VMID,
|
||||
smmu_domain->s2_cfg.vmid,
|
||||
IOMMU_NO_PASID, pgsize))
|
||||
IOMMU_NO_PASID))
|
||||
return NULL;
|
||||
break;
|
||||
default:
|
||||
@@ -3265,7 +3436,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
|
||||
if (nesting) {
|
||||
if (!arm_smmu_master_build_inv(
|
||||
master, INV_TYPE_S2_VMID_S1_CLEAR,
|
||||
smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID, 0))
|
||||
smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -3276,7 +3447,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
|
||||
*/
|
||||
if (!arm_smmu_master_build_inv(
|
||||
master, nesting ? INV_TYPE_ATS_FULL : INV_TYPE_ATS,
|
||||
master->streams[i].id, ssid, 0))
|
||||
master->streams[i].id, ssid))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -3285,9 +3456,9 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
|
||||
return master->build_invs;
|
||||
}
|
||||
|
||||
static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
|
||||
struct iommu_domain *domain,
|
||||
ioasid_t ssid)
|
||||
static struct arm_smmu_master_domain *
|
||||
arm_smmu_remove_master_domain(struct arm_smmu_master *master,
|
||||
struct iommu_domain *domain, ioasid_t ssid)
|
||||
{
|
||||
struct arm_smmu_domain *smmu_domain = to_smmu_domain_devices(domain);
|
||||
struct arm_smmu_master_domain *master_domain;
|
||||
@@ -3295,7 +3466,7 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
|
||||
unsigned long flags;
|
||||
|
||||
if (!smmu_domain)
|
||||
return;
|
||||
return NULL;
|
||||
|
||||
if (domain->type == IOMMU_DOMAIN_NESTED)
|
||||
nested_ats_flush = to_smmu_nested_domain(domain)->enable_ats;
|
||||
@@ -3310,8 +3481,24 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
|
||||
}
|
||||
spin_unlock_irqrestore(&smmu_domain->devices_lock, flags);
|
||||
|
||||
/* arm_smmu_attach_release() will free it */
|
||||
return master_domain;
|
||||
}
|
||||
|
||||
/* Release the old master_domain detached by arm_smmu_remove_master_domain() */
|
||||
void arm_smmu_attach_release(struct arm_smmu_attach_state *state)
|
||||
{
|
||||
struct arm_smmu_master_domain *master_domain = state->old_master_domain;
|
||||
struct arm_smmu_master *master = state->master;
|
||||
|
||||
iommu_group_mutex_assert(master->dev);
|
||||
|
||||
if (!master_domain)
|
||||
return;
|
||||
|
||||
arm_smmu_disable_iopf(master, master_domain);
|
||||
kfree(master_domain);
|
||||
state->old_master_domain = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -3609,7 +3796,8 @@ void arm_smmu_attach_commit(struct arm_smmu_attach_state *state)
|
||||
arm_smmu_atc_inv_master(master, IOMMU_NO_PASID);
|
||||
}
|
||||
|
||||
arm_smmu_remove_master_domain(master, state->old_domain, state->ssid);
|
||||
state->old_master_domain = arm_smmu_remove_master_domain(
|
||||
master, state->old_domain, state->ssid);
|
||||
arm_smmu_install_old_domain_invs(state);
|
||||
master->ats_enabled = state->ats_enabled;
|
||||
}
|
||||
@@ -3684,6 +3872,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
|
||||
|
||||
arm_smmu_attach_commit(&state);
|
||||
mutex_unlock(&arm_smmu_asid_lock);
|
||||
arm_smmu_attach_release(&state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3766,8 +3955,10 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
|
||||
|
||||
mutex_lock(&arm_smmu_asid_lock);
|
||||
ret = arm_smmu_attach_prepare(&state, &smmu_domain->domain);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
if (ret) {
|
||||
mutex_unlock(&arm_smmu_asid_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't want to obtain to the asid_lock too early, so fix up the
|
||||
@@ -3781,10 +3972,9 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
|
||||
arm_smmu_update_ste(master, sid_domain, state.ats_enabled);
|
||||
|
||||
arm_smmu_attach_commit(&state);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&arm_smmu_asid_lock);
|
||||
return ret;
|
||||
arm_smmu_attach_release(&state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
|
||||
@@ -3804,9 +3994,11 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
|
||||
arm_smmu_clear_cd(master, pasid);
|
||||
if (master->ats_enabled)
|
||||
arm_smmu_atc_inv_master(master, pasid);
|
||||
arm_smmu_remove_master_domain(master, &smmu_domain->domain, pasid);
|
||||
state.old_master_domain = arm_smmu_remove_master_domain(
|
||||
master, &smmu_domain->domain, pasid);
|
||||
arm_smmu_install_old_domain_invs(&state);
|
||||
mutex_unlock(&arm_smmu_asid_lock);
|
||||
arm_smmu_attach_release(&state);
|
||||
|
||||
/*
|
||||
* When the last user of the CD table goes away downgrade the STE back
|
||||
@@ -3869,6 +4061,7 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
|
||||
arm_smmu_install_ste_for_dev(master, ste);
|
||||
arm_smmu_attach_commit(&state);
|
||||
mutex_unlock(&arm_smmu_asid_lock);
|
||||
arm_smmu_attach_release(&state);
|
||||
|
||||
/*
|
||||
* This has to be done after removing the master from the
|
||||
@@ -4025,13 +4218,18 @@ static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
|
||||
struct iommu_iotlb_gather *gather)
|
||||
{
|
||||
struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
|
||||
struct arm_smmu_tlbi tlbi = {
|
||||
.tgsz_lg2 = smmu_domain->tgsz_lg2,
|
||||
.iova = gather->start,
|
||||
.size = gather->end - gather->start + 1,
|
||||
.iopte_size = gather->pgsize,
|
||||
.leaf_only = true,
|
||||
};
|
||||
|
||||
if (!gather->pgsize)
|
||||
return;
|
||||
|
||||
arm_smmu_domain_inv_range(smmu_domain, gather->start,
|
||||
gather->end - gather->start + 1,
|
||||
gather->pgsize, true);
|
||||
arm_smmu_domain_tlbi(&tlbi, smmu_domain);
|
||||
}
|
||||
|
||||
static phys_addr_t
|
||||
@@ -4122,6 +4320,13 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
|
||||
sizeof(master->streams[0]), arm_smmu_stream_id_cmp,
|
||||
NULL);
|
||||
|
||||
/*
|
||||
* Clear after sorting: RB_CLEAR_NODE() records the node's own address,
|
||||
* which sort_nonatomic() invalidates by relocating the entries.
|
||||
*/
|
||||
for (i = 0; i < fwspec->num_ids; i++)
|
||||
RB_CLEAR_NODE(&master->streams[i].node);
|
||||
|
||||
mutex_lock(&smmu->streams_mutex);
|
||||
for (i = 0; i < fwspec->num_ids; i++) {
|
||||
struct arm_smmu_stream *new_stream = &master->streams[i];
|
||||
@@ -4154,7 +4359,9 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
|
||||
|
||||
if (ret) {
|
||||
for (i--; i >= 0; i--)
|
||||
rb_erase(&master->streams[i].node, &smmu->streams);
|
||||
if (!RB_EMPTY_NODE(&master->streams[i].node))
|
||||
rb_erase(&master->streams[i].node,
|
||||
&smmu->streams);
|
||||
kfree(master->streams);
|
||||
kfree(master->build_invs);
|
||||
}
|
||||
@@ -4174,7 +4381,8 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
|
||||
|
||||
mutex_lock(&smmu->streams_mutex);
|
||||
for (i = 0; i < fwspec->num_ids; i++)
|
||||
rb_erase(&master->streams[i].node, &smmu->streams);
|
||||
if (!RB_EMPTY_NODE(&master->streams[i].node))
|
||||
rb_erase(&master->streams[i].node, &smmu->streams);
|
||||
mutex_unlock(&smmu->streams_mutex);
|
||||
|
||||
kfree(master->streams);
|
||||
@@ -4412,6 +4620,56 @@ static struct iommu_dirty_ops arm_smmu_dirty_ops = {
|
||||
};
|
||||
|
||||
/* Probing and initialisation functions */
|
||||
|
||||
/**
|
||||
* arm_smmu_queue_max_n_shift() - pick the log2 depth of a queue
|
||||
* @hw_max_n_shift: log2 depth the hardware advertises in IDR1
|
||||
* @ent_sz_shift: log2 of the queue entry size in bytes
|
||||
* @limit_n_shift: log2 depth to cap the queue at
|
||||
*
|
||||
* A kdump capture kernel gets one page worth of entries whatever the limit.
|
||||
*
|
||||
* @limit_n_shift is floored at one page, because coherent DMA is page
|
||||
* granular: a shallower queue occupies the same memory as one that fills the
|
||||
* page, and arm_smmu_init_one_queue() stops shrinking at a page too.
|
||||
*/
|
||||
static u32 arm_smmu_queue_max_n_shift(u32 hw_max_n_shift, u32 ent_sz_shift,
|
||||
u32 limit_n_shift)
|
||||
{
|
||||
u32 floor_n_shift = PAGE_SHIFT - ent_sz_shift;
|
||||
|
||||
if (is_kdump_kernel())
|
||||
return min(hw_max_n_shift, floor_n_shift);
|
||||
|
||||
limit_n_shift = max(limit_n_shift, floor_n_shift);
|
||||
return min(hw_max_n_shift, limit_n_shift);
|
||||
}
|
||||
|
||||
static inline u32 arm_smmu_evtq_max_n_shift(u32 hw_max_n_shift)
|
||||
{
|
||||
/* Capped to ensure natural alignment */
|
||||
return arm_smmu_queue_max_n_shift(hw_max_n_shift, EVTQ_ENT_SZ_SHIFT,
|
||||
EVTQ_MAX_SZ_SHIFT);
|
||||
}
|
||||
|
||||
static inline u32 arm_smmu_priq_max_n_shift(u32 hw_max_n_shift)
|
||||
{
|
||||
/* Capped to ensure natural alignment */
|
||||
return arm_smmu_queue_max_n_shift(hw_max_n_shift, PRIQ_ENT_SZ_SHIFT,
|
||||
PRIQ_MAX_SZ_SHIFT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Command queues are also allocated by the Tegra241 CMDQV for its VCMDQs, which
|
||||
* need the same depth decision.
|
||||
*/
|
||||
u32 arm_smmu_cmdq_max_n_shift(u32 hw_max_n_shift)
|
||||
{
|
||||
/* Capped to ensure natural alignment */
|
||||
return arm_smmu_queue_max_n_shift(hw_max_n_shift, CMDQ_ENT_SZ_SHIFT,
|
||||
min(CMDQ_MAX_SZ_SHIFT, cmdq_max_n_shift));
|
||||
}
|
||||
|
||||
int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
|
||||
struct arm_smmu_queue *q, void __iomem *page,
|
||||
unsigned long prod_off, unsigned long cons_off,
|
||||
@@ -5009,12 +5267,21 @@ static void arm_smmu_device_iidr_probe(struct arm_smmu_device *smmu)
|
||||
/* Arm errata 2268618, 2812531 */
|
||||
smmu->features &= ~ARM_SMMU_FEAT_NESTING;
|
||||
}
|
||||
/* Arm errata 3777127 */
|
||||
smmu->options |= ARM_SMMU_OPT_FULL_CONT_RANGE_INV;
|
||||
break;
|
||||
case IIDR_PRODUCTID_ARM_MMU_L1:
|
||||
case IIDR_PRODUCTID_ARM_MMU_S3:
|
||||
/* Arm errata 3878312/3995052 */
|
||||
/* Arm errata 3878312 */
|
||||
smmu->features &= ~ARM_SMMU_FEAT_BTM;
|
||||
break;
|
||||
case IIDR_PRODUCTID_ARM_MMU_S3:
|
||||
/* Arm errata 3995052 */
|
||||
smmu->features &= ~ARM_SMMU_FEAT_BTM;
|
||||
/* Arm errata 3673557 */
|
||||
if (variant < 1 || (variant == 1 && revision < 1))
|
||||
smmu->options |=
|
||||
ARM_SMMU_OPT_FULL_CONT_RANGE_INV;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -5155,9 +5422,14 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
|
||||
if (reg & IDR1_ATTR_TYPES_OVR)
|
||||
smmu->features |= ARM_SMMU_FEAT_ATTR_TYPES_OVR;
|
||||
|
||||
/* Queue sizes, capped to ensure natural alignment */
|
||||
smmu->cmdq.q.llq.max_n_shift = min_t(u32, CMDQ_MAX_SZ_SHIFT,
|
||||
FIELD_GET(IDR1_CMDQS, reg));
|
||||
if ((smmu->options & ARM_SMMU_OPT_OVR_INSTCFG_DATA) &&
|
||||
!(reg & IDR1_ATTR_PERMS_OVR)) {
|
||||
dev_err(smmu->dev, "Inst/Data attribute override not supported\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
smmu->cmdq.q.llq.max_n_shift =
|
||||
arm_smmu_cmdq_max_n_shift(FIELD_GET(IDR1_CMDQS, reg));
|
||||
if (smmu->cmdq.q.llq.max_n_shift <= ilog2(CMDQ_BATCH_ENTRIES)) {
|
||||
/*
|
||||
* We don't support splitting up batches, so one batch of
|
||||
@@ -5170,10 +5442,10 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
smmu->evtq.q.llq.max_n_shift = min_t(u32, EVTQ_MAX_SZ_SHIFT,
|
||||
FIELD_GET(IDR1_EVTQS, reg));
|
||||
smmu->priq.q.llq.max_n_shift = min_t(u32, PRIQ_MAX_SZ_SHIFT,
|
||||
FIELD_GET(IDR1_PRIQS, reg));
|
||||
smmu->evtq.q.llq.max_n_shift =
|
||||
arm_smmu_evtq_max_n_shift(FIELD_GET(IDR1_EVTQS, reg));
|
||||
smmu->priq.q.llq.max_n_shift =
|
||||
arm_smmu_priq_max_n_shift(FIELD_GET(IDR1_PRIQS, reg));
|
||||
|
||||
/* SID/SSID sizes */
|
||||
smmu->ssid_bits = FIELD_GET(IDR1_SSIDSIZE, reg);
|
||||
@@ -5633,7 +5905,7 @@ static void arm_smmu_device_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct arm_smmu_device *smmu = platform_get_drvdata(pdev);
|
||||
|
||||
arm_smmu_device_disable(smmu);
|
||||
arm_smmu_disable_action(smmu);
|
||||
}
|
||||
|
||||
static const struct of_device_id arm_smmu_of_match[] = {
|
||||
|
||||
@@ -52,6 +52,7 @@ struct arm_vsmmu;
|
||||
#define IDR1_QUEUES_PRESET (1 << 29)
|
||||
#define IDR1_REL (1 << 28)
|
||||
#define IDR1_ATTR_TYPES_OVR (1 << 27)
|
||||
#define IDR1_ATTR_PERMS_OVR (1 << 26)
|
||||
#define IDR1_CMDQS GENMASK(25, 21)
|
||||
#define IDR1_EVTQS GENMASK(20, 16)
|
||||
#define IDR1_PRIQS GENMASK(15, 11)
|
||||
@@ -187,6 +188,8 @@ struct arm_vsmmu;
|
||||
|
||||
#define Q_IDX(llq, p) ((p) & ((1 << (llq)->max_n_shift) - 1))
|
||||
#define Q_WRP(llq, p) ((p) & (1 << (llq)->max_n_shift))
|
||||
/* A position is Q_WRP | Q_IDX, wrapping at twice the queue capacity */
|
||||
#define Q_POS(llq, p) (Q_WRP(llq, p) | Q_IDX(llq, p))
|
||||
#define Q_OVERFLOW_FLAG (1U << 31)
|
||||
#define Q_OVF(p) ((p) & Q_OVERFLOW_FLAG)
|
||||
#define Q_ENT(q, p) ((q)->base + \
|
||||
@@ -285,6 +288,10 @@ static inline u32 arm_smmu_strtab_l2_idx(u32 sid)
|
||||
#define STRTAB_STE_1_SHCFG GENMASK_ULL(45, 44)
|
||||
#define STRTAB_STE_1_SHCFG_INCOMING 1UL
|
||||
|
||||
#define STRTAB_STE_1_INSTCFG GENMASK_ULL(51, 50)
|
||||
#define STRTAB_STE_1_INSTCFG_INCOMING 0UL
|
||||
#define STRTAB_STE_1_INSTCFG_DATA 2UL
|
||||
|
||||
#define STRTAB_STE_2_S2VMID GENMASK_ULL(15, 0)
|
||||
#define STRTAB_STE_2_VTCR GENMASK_ULL(50, 32)
|
||||
#define STRTAB_STE_2_VTCR_S2T0SZ GENMASK_ULL(5, 0)
|
||||
@@ -736,10 +743,9 @@ struct arm_smmu_inv {
|
||||
u8 size_opcode;
|
||||
u8 nsize_opcode;
|
||||
u32 id; /* ASID or VMID or SID */
|
||||
union {
|
||||
size_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */
|
||||
u32 ssid; /* INV_TYPE_ATS */
|
||||
};
|
||||
|
||||
/* Only used by INV_TYPE_ATS */
|
||||
u32 ssid;
|
||||
|
||||
int users; /* users=0 to mark as a trash to be purged */
|
||||
};
|
||||
@@ -759,6 +765,9 @@ static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)
|
||||
* Must not be greater than @num_invs
|
||||
* @rwlock: optional rwlock to fence ATS operations
|
||||
* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
|
||||
* @has_range_inv: flag if any entry's SMMU supports range invalidation
|
||||
* @has_full_cont_range_inv: flag if any entry's SMMU requires the CONT range
|
||||
* invalidation workaround
|
||||
* @rcu: rcu head for kfree_rcu()
|
||||
* @inv: flexible invalidation array
|
||||
*
|
||||
@@ -788,6 +797,8 @@ struct arm_smmu_invs {
|
||||
size_t num_trashes;
|
||||
rwlock_t rwlock;
|
||||
bool has_ats;
|
||||
bool has_range_inv;
|
||||
bool has_full_cont_range_inv;
|
||||
struct rcu_head rcu;
|
||||
struct arm_smmu_inv inv[] __counted_by(max_invs);
|
||||
};
|
||||
@@ -805,6 +816,40 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
|
||||
return new_invs;
|
||||
}
|
||||
|
||||
/* Generic page-table level 0 is the leaf-only level. */
|
||||
static inline unsigned int arm_smmu_pt_level_to_lg2sz(unsigned int tgsz_lg2,
|
||||
unsigned int level)
|
||||
{
|
||||
return tgsz_lg2 + (tgsz_lg2 - ilog2(sizeof(u64))) * level;
|
||||
}
|
||||
|
||||
static inline unsigned int arm_smmu_pt_lg2sz_to_level(unsigned int tgsz_lg2,
|
||||
unsigned int lg2sz)
|
||||
{
|
||||
return (lg2sz - tgsz_lg2) / (tgsz_lg2 - ilog2(sizeof(u64)));
|
||||
}
|
||||
|
||||
struct arm_smmu_tlbi {
|
||||
unsigned long iova;
|
||||
size_t size;
|
||||
/* page or block size of the leaf iopte */
|
||||
unsigned int iopte_size;
|
||||
/* Base Translation Granule of the page table */
|
||||
u8 tgsz_lg2;
|
||||
bool leaf_only;
|
||||
|
||||
struct {
|
||||
bool use_full_inv;
|
||||
u16 num;
|
||||
} single;
|
||||
|
||||
struct {
|
||||
bool use_full_inv;
|
||||
u8 num_cmds;
|
||||
struct arm_smmu_cmd cmds[2];
|
||||
} range;
|
||||
};
|
||||
|
||||
struct arm_smmu_evtq {
|
||||
struct arm_smmu_queue q;
|
||||
struct iopf_queue *iopf;
|
||||
@@ -934,6 +979,12 @@ struct arm_smmu_device {
|
||||
#define ARM_SMMU_OPT_MSIPOLL (1 << 2)
|
||||
#define ARM_SMMU_OPT_CMDQ_FORCE_SYNC (1 << 3)
|
||||
#define ARM_SMMU_OPT_TEGRA241_CMDQV (1 << 4)
|
||||
#define ARM_SMMU_OPT_OVR_INSTCFG_DATA (1 << 5)
|
||||
/*
|
||||
* Range invalidation is mandatory and one range invalidation must fully span an
|
||||
* invalidated CONT
|
||||
*/
|
||||
#define ARM_SMMU_OPT_FULL_CONT_RANGE_INV (1 << 6)
|
||||
u32 options;
|
||||
|
||||
struct arm_smmu_cmdq cmdq;
|
||||
@@ -1049,6 +1100,7 @@ struct arm_smmu_domain {
|
||||
spinlock_t devices_lock;
|
||||
bool enforce_cache_coherency : 1;
|
||||
bool nest_parent : 1;
|
||||
u8 tgsz_lg2;
|
||||
|
||||
struct mmu_notifier mmu_notifier;
|
||||
};
|
||||
@@ -1154,17 +1206,24 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
|
||||
struct arm_smmu_domain *smmu_domain, ioasid_t pasid,
|
||||
struct arm_smmu_cd *cd, struct iommu_domain *old);
|
||||
|
||||
void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
|
||||
unsigned long iova, size_t size,
|
||||
unsigned int granule, bool leaf);
|
||||
void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi,
|
||||
struct arm_smmu_domain *smmu_domain);
|
||||
|
||||
static inline void arm_smmu_domain_inv(struct arm_smmu_domain *smmu_domain)
|
||||
{
|
||||
arm_smmu_domain_inv_range(smmu_domain, 0, 0, 0, false);
|
||||
/* Prefilled for invalidate all */
|
||||
struct arm_smmu_tlbi tlbi = {
|
||||
.tgsz_lg2 = smmu_domain->tgsz_lg2,
|
||||
.single.use_full_inv = true,
|
||||
.range.use_full_inv = true,
|
||||
};
|
||||
|
||||
arm_smmu_domain_tlbi(&tlbi, smmu_domain);
|
||||
}
|
||||
|
||||
void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,
|
||||
struct arm_smmu_cmdq *cmdq);
|
||||
u32 arm_smmu_cmdq_max_n_shift(u32 hw_max_n_shift);
|
||||
int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
|
||||
struct arm_smmu_queue *q, void __iomem *page,
|
||||
unsigned long prod_off, unsigned long cons_off,
|
||||
@@ -1204,12 +1263,14 @@ struct arm_smmu_attach_state {
|
||||
struct arm_smmu_vmaster *vmaster;
|
||||
struct arm_smmu_inv_state old_domain_invst;
|
||||
struct arm_smmu_inv_state new_domain_invst;
|
||||
struct arm_smmu_master_domain *old_master_domain;
|
||||
bool ats_enabled;
|
||||
};
|
||||
|
||||
int arm_smmu_attach_prepare(struct arm_smmu_attach_state *state,
|
||||
struct iommu_domain *new_domain);
|
||||
void arm_smmu_attach_commit(struct arm_smmu_attach_state *state);
|
||||
void arm_smmu_attach_release(struct arm_smmu_attach_state *state);
|
||||
void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
|
||||
const struct arm_smmu_ste *target);
|
||||
|
||||
|
||||
@@ -663,7 +663,7 @@ static int tegra241_vcmdq_alloc_smmu_cmdq(struct tegra241_vcmdq *vcmdq)
|
||||
/* Cap queue size to SMMU's IDR1.CMDQS and ensure natural alignment */
|
||||
regval = readl_relaxed(smmu->base + ARM_SMMU_IDR1);
|
||||
q->llq.max_n_shift =
|
||||
min_t(u32, CMDQ_MAX_SZ_SHIFT, FIELD_GET(IDR1_CMDQS, regval));
|
||||
arm_smmu_cmdq_max_n_shift(FIELD_GET(IDR1_CMDQS, regval));
|
||||
|
||||
/* Use the common helper to init the VCMDQ, and then... */
|
||||
ret = arm_smmu_init_one_queue(smmu, q, vcmdq->page0,
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* IOMMU driver for BCM2712 TLB cache
|
||||
*
|
||||
* Copyright (c) 2023 Raspberry Pi Ltd.
|
||||
* Copyright (c) 2026 Daniel Drake
|
||||
*
|
||||
* The BCM2712 IOMMUC is a centralized TLB which accelerates address translation
|
||||
* across the SoC's IOMMU devices. If an address mapping is not found in the
|
||||
* IOMMU's local TLB cache, then this IOMMUC is consulted. The IOMMUC must be
|
||||
* explicitly invalidated when modifying or unmapping IOMMU page tables.
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include "bcm2712-iommu-cache.h"
|
||||
|
||||
struct bcm2712_iommu_cache {
|
||||
struct device *dev;
|
||||
spinlock_t hw_lock;
|
||||
void __iomem *reg_base;
|
||||
};
|
||||
|
||||
#define MMUC_CONTROL_ENABLE BIT(0)
|
||||
#define MMUC_CONTROL_FLUSH BIT(1)
|
||||
#define MMUC_CONTROL_FLUSHING BIT(2)
|
||||
|
||||
void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
scoped_guard(spinlock_irqsave, &cache->hw_lock) {
|
||||
writel(MMUC_CONTROL_ENABLE | MMUC_CONTROL_FLUSH,
|
||||
cache->reg_base);
|
||||
|
||||
ret = readl_poll_timeout_atomic(cache->reg_base, val,
|
||||
!(val & MMUC_CONTROL_FLUSHING),
|
||||
0, 50);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_err_ratelimited(cache->dev, "cache flush timed out\n");
|
||||
}
|
||||
|
||||
static int bcm2712_iommu_cache_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct bcm2712_iommu_cache *cache;
|
||||
|
||||
cache = devm_kzalloc(&pdev->dev, sizeof(*cache), GFP_KERNEL);
|
||||
if (!cache)
|
||||
return -ENOMEM;
|
||||
|
||||
cache->dev = &pdev->dev;
|
||||
spin_lock_init(&cache->hw_lock);
|
||||
|
||||
cache->reg_base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(cache->reg_base))
|
||||
return PTR_ERR(cache->reg_base);
|
||||
|
||||
platform_set_drvdata(pdev, cache);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id bcm2712_iommu_cache_of_match[] = {
|
||||
{ .compatible = "brcm,bcm2712-iommuc" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
static struct platform_driver bcm2712_iommu_cache_driver = {
|
||||
.probe = bcm2712_iommu_cache_probe,
|
||||
.driver = {
|
||||
.name = "bcm2712-iommu-cache",
|
||||
.of_match_table = bcm2712_iommu_cache_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(bcm2712_iommu_cache_driver);
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef _BCM2712_IOMMU_CACHE_H
|
||||
#define _BCM2712_IOMMU_CACHE_H
|
||||
|
||||
struct bcm2712_iommu_cache;
|
||||
|
||||
void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,550 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* IOMMU driver for Broadcom BCM2712
|
||||
*
|
||||
* Copyright (c) 2023-2025 Raspberry Pi Ltd.
|
||||
* Copyright (c) 2026 Daniel Drake
|
||||
*
|
||||
* Each BCM2712 IOMMU has multiple devices hardwired into it, whose
|
||||
* DMA transactions all route through the IOMMU. There is no stream ID tagging
|
||||
* or any other kind of segmentation to differentiate between requests from
|
||||
* different devices. It is also not possible to toggle a specific device
|
||||
* between iommu-mapped and bypass modes.
|
||||
*
|
||||
* The hardware supports the simultaneous configuration of a bypass window
|
||||
* (where memory accesses operate in identity mode) and a translation aperture
|
||||
* (supporting page mapping). While combining these two would allow for
|
||||
* GART-like operation, this driver's paging domain implementation configures
|
||||
* only the translation aperture (starting at IOVA 0) offering a degree of
|
||||
* memory protection.
|
||||
*
|
||||
* The page table format is a two-level format handled by generic_pt/bcm2712.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/generic_pt/iommu.h>
|
||||
|
||||
#include "bcm2712-iommu-cache.h"
|
||||
#include "dma-iommu.h"
|
||||
#include "iommu-pages.h"
|
||||
|
||||
/* BCM2712 IOMMU is organized around 4Kbyte pages */
|
||||
#define IOMMU_PAGE_SHIFT 12
|
||||
#define IOMMU_PAGE_SIZE (1ul << IOMMU_PAGE_SHIFT)
|
||||
/* A PTE is 4 bytes */
|
||||
#define PTE_SIZE_SHIFT 2
|
||||
/* L1/L2 table sizing (IOMMU hardware pages): 1024 entries per page */
|
||||
#define PTES_PER_IOPG_SHIFT (IOMMU_PAGE_SHIFT - PTE_SIZE_SHIFT)
|
||||
|
||||
#define MMMU_CTRL_OFFSET 0x00
|
||||
#define MMMU_CTRL_CAP_EXCEEDED BIT(27)
|
||||
#define MMMU_CTRL_CAP_EXCEEDED_ABORT_EN BIT(26)
|
||||
#define MMMU_CTRL_CAP_EXCEEDED_INT_EN BIT(25)
|
||||
#define MMMU_CTRL_CAP_EXCEEDED_EXCEPTION_EN BIT(24)
|
||||
#define MMMU_CTRL_PT_INVALID BIT(20)
|
||||
#define MMMU_CTRL_PT_INVALID_ABORT_EN BIT(19)
|
||||
#define MMMU_CTRL_PT_INVALID_INT_EN BIT(18)
|
||||
#define MMMU_CTRL_PT_INVALID_EXCEPTION_EN BIT(17)
|
||||
#define MMMU_CTRL_PT_INVALID_EN BIT(16)
|
||||
#define MMMU_CTRL_WRITE_VIOLATION BIT(12)
|
||||
#define MMMU_CTRL_WRITE_VIOLATION_ABORT_EN BIT(11)
|
||||
#define MMMU_CTRL_WRITE_VIOLATION_INT_EN BIT(10)
|
||||
#define MMMU_CTRL_WRITE_VIOLATION_EXCEPTION_EN BIT(9)
|
||||
#define MMMU_CTRL_BYPASS BIT(8)
|
||||
#define MMMU_CTRL_TLB_CLEARING BIT(7)
|
||||
#define MMMU_CTRL_STATS_CLEAR BIT(3)
|
||||
#define MMMU_CTRL_TLB_CLEAR BIT(2)
|
||||
#define MMMU_CTRL_STATS_ENABLE BIT(1)
|
||||
#define MMMU_CTRL_ENABLE BIT(0)
|
||||
|
||||
#define MMMU_CTRL_OPERATING_FLAGS (\
|
||||
MMMU_CTRL_CAP_EXCEEDED_ABORT_EN | \
|
||||
MMMU_CTRL_PT_INVALID_ABORT_EN | \
|
||||
MMMU_CTRL_PT_INVALID_EN | \
|
||||
MMMU_CTRL_WRITE_VIOLATION_ABORT_EN | \
|
||||
MMMU_CTRL_STATS_ENABLE | \
|
||||
MMMU_CTRL_ENABLE)
|
||||
|
||||
#define MMMU_PT_PA_BASE_OFFSET 0x04
|
||||
|
||||
#define MMMU_ADDR_CAP_OFFSET 0x14
|
||||
#define MMMU_ADDR_CAP_ENABLE BIT(31)
|
||||
#define ADDR_CAP_SHIFT ilog2(SZ_256M)
|
||||
|
||||
#define MMMU_SHOOT_DOWN_OFFSET 0x18
|
||||
#define MMMU_SHOOT_DOWN_SHOOTING BIT(31)
|
||||
#define MMMU_SHOOT_DOWN_SHOOT BIT(30)
|
||||
|
||||
#define MMMU_BYPASS_START_OFFSET 0x1c
|
||||
#define MMMU_BYPASS_START_ENABLE BIT(31)
|
||||
|
||||
#define MMMU_BYPASS_END_OFFSET 0x20
|
||||
#define MMMU_BYPASS_END_ENABLE BIT(31)
|
||||
|
||||
#define MMMU_MISC_OFFSET 0x24
|
||||
#define MMMU_MISC_SINGLE_TABLE BIT(31)
|
||||
|
||||
#define MMMU_ILLEGAL_ADR_OFFSET 0x30
|
||||
#define MMMU_ILLEGAL_ADR_ENABLE BIT(31)
|
||||
|
||||
#define MMMU_DEBUG_INFO_OFFSET 0x38
|
||||
#define MMMU_DEBUG_INFO_VERSION_MASK 0x0000000Fu
|
||||
#define MMMU_DEBUG_INFO_VA_WIDTH_MASK 0x000000F0u
|
||||
#define MMMU_DEBUG_INFO_PA_WIDTH_MASK 0x00000F00u
|
||||
#define MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK 0x000FF000u
|
||||
#define MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK 0x0FF00000u
|
||||
#define MMMU_DEBUG_INFO_BYPASS_4M BIT(28)
|
||||
#define MMMU_DEBUG_INFO_BYPASS BIT(29)
|
||||
|
||||
struct bcm2712_iommu {
|
||||
struct device *dev;
|
||||
struct iommu_device iommu;
|
||||
struct bcm2712_iommu_domain *domain;
|
||||
struct bcm2712_iommu_cache *cache;
|
||||
void __iomem *reg_base;
|
||||
spinlock_t hw_lock;
|
||||
size_t bigpage_size;
|
||||
size_t superpage_size;
|
||||
};
|
||||
|
||||
struct bcm2712_iommu_domain {
|
||||
union {
|
||||
struct iommu_domain base;
|
||||
struct pt_iommu_bcm2712 pt;
|
||||
};
|
||||
struct bcm2712_iommu *mmu;
|
||||
void *default_page;
|
||||
};
|
||||
|
||||
static inline void bcm2712_iommu_writel(struct bcm2712_iommu *mmu,
|
||||
unsigned int offset, u32 val)
|
||||
{
|
||||
writel(val, mmu->reg_base + offset);
|
||||
}
|
||||
|
||||
static inline u32 bcm2712_iommu_readl(struct bcm2712_iommu *mmu,
|
||||
unsigned int offset)
|
||||
{
|
||||
return readl(mmu->reg_base + offset);
|
||||
}
|
||||
|
||||
static struct bcm2712_iommu_domain *
|
||||
to_bcm2712_domain(struct iommu_domain *domain)
|
||||
{
|
||||
return container_of(domain, struct bcm2712_iommu_domain, base);
|
||||
}
|
||||
|
||||
static void bcm2712_iommu_init(struct bcm2712_iommu *mmu)
|
||||
{
|
||||
unsigned int bigpage_width, superpage_width;
|
||||
u32 u = bcm2712_iommu_readl(mmu, MMMU_DEBUG_INFO_OFFSET);
|
||||
u32 pa_width = FIELD_GET(MMMU_DEBUG_INFO_PA_WIDTH_MASK, u);
|
||||
|
||||
dev_dbg(mmu->dev, "DEBUG_INFO = 0x%08x\n", u);
|
||||
WARN_ON(FIELD_GET(MMMU_DEBUG_INFO_VERSION_MASK, u) < 4 ||
|
||||
FIELD_GET(MMMU_DEBUG_INFO_VA_WIDTH_MASK, u) < 6 ||
|
||||
pa_width < 6 || !(u & MMMU_DEBUG_INFO_BYPASS));
|
||||
|
||||
dma_set_mask_and_coherent(mmu->dev, DMA_BIT_MASK(pa_width + 30u));
|
||||
|
||||
bigpage_width = FIELD_GET(MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK, u);
|
||||
if (bigpage_width)
|
||||
mmu->bigpage_size = IOMMU_PAGE_SIZE << bigpage_width;
|
||||
|
||||
superpage_width = FIELD_GET(MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK, u);
|
||||
if (superpage_width)
|
||||
mmu->superpage_size = IOMMU_PAGE_SIZE << superpage_width;
|
||||
|
||||
/* Disable MMU and clear sticky flags */
|
||||
bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET,
|
||||
MMMU_CTRL_CAP_EXCEEDED | MMMU_CTRL_PT_INVALID |
|
||||
MMMU_CTRL_WRITE_VIOLATION |
|
||||
MMMU_CTRL_STATS_CLEAR);
|
||||
|
||||
/* Put MMU into 2-level mode */
|
||||
bcm2712_iommu_writel(mmu, MMMU_MISC_OFFSET,
|
||||
bcm2712_iommu_readl(mmu, MMMU_MISC_OFFSET) &
|
||||
~MMMU_MISC_SINGLE_TABLE);
|
||||
}
|
||||
|
||||
static int bcm2712_iommu_identity_attach(struct iommu_domain *identity_domain,
|
||||
struct device *dev,
|
||||
struct iommu_domain *old)
|
||||
{
|
||||
struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
|
||||
|
||||
guard(spinlock_irqsave)(&mmu->hw_lock);
|
||||
bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET, 0);
|
||||
mmu->domain = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct iommu_domain bcm2712_identity_domain = {
|
||||
.type = IOMMU_DOMAIN_IDENTITY,
|
||||
.ops = &(const struct iommu_domain_ops) {
|
||||
.attach_dev = bcm2712_iommu_identity_attach,
|
||||
},
|
||||
};
|
||||
|
||||
static int bcm2712_iommu_clear_and_enable(struct bcm2712_iommu *mmu)
|
||||
__must_hold(&mmu->hw_lock)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
bcm2712_iommu_cache_flush(mmu->cache);
|
||||
bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET,
|
||||
MMMU_CTRL_OPERATING_FLAGS | MMMU_CTRL_TLB_CLEAR);
|
||||
return readl_poll_timeout_atomic(mmu->reg_base + MMMU_CTRL_OFFSET, val,
|
||||
!(val & MMMU_CTRL_TLB_CLEARING), 0,
|
||||
50);
|
||||
}
|
||||
|
||||
|
||||
static int bcm2712_iommu_attach_dev(struct iommu_domain *domain,
|
||||
struct device *dev,
|
||||
struct iommu_domain *old)
|
||||
{
|
||||
struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
|
||||
struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
|
||||
struct pt_iommu_bcm2712_hw_info info;
|
||||
u32 default_page_pfn;
|
||||
int ret = 0;
|
||||
|
||||
/* all attached devices must belong to the same IOMMU instance */
|
||||
if (mydomain->mmu != mmu)
|
||||
return -EINVAL;
|
||||
|
||||
scoped_guard(spinlock_irqsave, &mmu->hw_lock) {
|
||||
if (mmu->domain == mydomain)
|
||||
break;
|
||||
|
||||
/* Configure translation aperture */
|
||||
bcm2712_iommu_writel(mmu, MMMU_ADDR_CAP_OFFSET,
|
||||
MMMU_ADDR_CAP_ENABLE |
|
||||
(domain->geometry.aperture_end >>
|
||||
ADDR_CAP_SHIFT));
|
||||
|
||||
/*
|
||||
* When the IOMMU handles a request, it adds the
|
||||
* PT_PA_BASE_OFFSET to (IOVA>>32) to calculate the PFN of the
|
||||
* corresponding L1 directory page.
|
||||
* IOVA bits [31:22] are then used to fetch the L1 descriptor
|
||||
* within (which in turn points to the L2 table).
|
||||
* This clever logic would allow for a L1 table larger than 4kb
|
||||
* (and hence a larger aperture).
|
||||
*/
|
||||
pt_iommu_bcm2712_hw_info(&mydomain->pt, &info);
|
||||
bcm2712_iommu_writel(mmu, MMMU_PT_PA_BASE_OFFSET,
|
||||
info.pt_base >> IOMMU_PAGE_SHIFT);
|
||||
|
||||
/* Disable bypass window */
|
||||
bcm2712_iommu_writel(mmu, MMMU_BYPASS_START_OFFSET, 0);
|
||||
bcm2712_iommu_writel(mmu, MMMU_BYPASS_END_OFFSET, 0);
|
||||
|
||||
/*
|
||||
* A default (error) page is used to catch illegal reads/writes.
|
||||
*/
|
||||
default_page_pfn = virt_to_phys(mydomain->default_page) >>
|
||||
IOMMU_PAGE_SHIFT;
|
||||
bcm2712_iommu_writel(mmu, MMMU_ILLEGAL_ADR_OFFSET,
|
||||
MMMU_ILLEGAL_ADR_ENABLE |
|
||||
default_page_pfn);
|
||||
|
||||
ret = bcm2712_iommu_clear_and_enable(mmu);
|
||||
if (ret) {
|
||||
bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET, 0);
|
||||
mmu->domain = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
mmu->domain = mydomain;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_err_ratelimited(mmu->dev,
|
||||
"TLB clear timed out during attach\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bcm2712_iommu_shootdown_range(struct bcm2712_iommu *mmu,
|
||||
unsigned long iova, size_t size)
|
||||
__must_hold(&mmu->hw_lock)
|
||||
{
|
||||
unsigned long start_pfn = iova >> IOMMU_PAGE_SHIFT;
|
||||
unsigned long end_pfn = (iova + size - 1) >> IOMMU_PAGE_SHIFT;
|
||||
unsigned long pfn;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
/* Shootdown register deals with 4 pages at a time */
|
||||
for (pfn = round_down(start_pfn, 4); pfn <= end_pfn; pfn += 4) {
|
||||
bcm2712_iommu_writel(mmu, MMMU_SHOOT_DOWN_OFFSET,
|
||||
MMMU_SHOOT_DOWN_SHOOT | pfn);
|
||||
ret = readl_poll_timeout_atomic(
|
||||
mmu->reg_base + MMMU_SHOOT_DOWN_OFFSET, val,
|
||||
!(val & MMMU_SHOOT_DOWN_SHOOTING), 0, 50);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm2712_iommu_sync_range(struct iommu_domain *domain,
|
||||
unsigned long iova, size_t size)
|
||||
{
|
||||
struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
|
||||
struct bcm2712_iommu *mmu = mydomain->mmu;
|
||||
int ret = 0;
|
||||
|
||||
if (!size)
|
||||
return 0;
|
||||
|
||||
scoped_guard(spinlock_irqsave, &mmu->hw_lock) {
|
||||
if (mmu->domain != mydomain)
|
||||
return 0;
|
||||
|
||||
/* If invalidating more than 1MB, just do a full TLB clear */
|
||||
if (size >= SZ_1M) {
|
||||
ret = bcm2712_iommu_clear_and_enable(mmu);
|
||||
} else {
|
||||
bcm2712_iommu_cache_flush(mmu->cache);
|
||||
ret = bcm2712_iommu_shootdown_range(mmu, iova, size);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_err_ratelimited(mmu->dev,
|
||||
"TLB sync timed out (size=%#zx)\n", size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bcm2712_iommu_sync(struct iommu_domain *domain,
|
||||
struct iommu_iotlb_gather *gather)
|
||||
{
|
||||
bcm2712_iommu_sync_range(domain, gather->start,
|
||||
gather->end - gather->start + 1);
|
||||
iommu_put_pages_list(&gather->freelist);
|
||||
}
|
||||
|
||||
static int bcm2712_iommu_sync_map(struct iommu_domain *domain,
|
||||
unsigned long iova, size_t size)
|
||||
{
|
||||
return bcm2712_iommu_sync_range(domain, iova, size);
|
||||
}
|
||||
|
||||
static void bcm2712_iommu_sync_all(struct iommu_domain *domain)
|
||||
{
|
||||
size_t aperture_size = domain->geometry.aperture_end -
|
||||
domain->geometry.aperture_start + 1;
|
||||
|
||||
bcm2712_iommu_sync_range(domain, domain->geometry.aperture_start,
|
||||
aperture_size);
|
||||
}
|
||||
|
||||
static void bcm2712_iommu_domain_free(struct iommu_domain *domain)
|
||||
{
|
||||
struct bcm2712_iommu_domain *mydomain = to_bcm2712_domain(domain);
|
||||
struct bcm2712_iommu *mmu = mydomain->mmu;
|
||||
|
||||
scoped_guard(spinlock_irqsave, &mmu->hw_lock) {
|
||||
if (mmu->domain == mydomain) {
|
||||
bcm2712_iommu_writel(mmu, MMMU_CTRL_OFFSET, 0);
|
||||
mmu->domain = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
pt_iommu_deinit(&mydomain->pt.iommu);
|
||||
if (mydomain->default_page)
|
||||
iommu_free_pages(mydomain->default_page);
|
||||
kfree(mydomain);
|
||||
}
|
||||
|
||||
static const struct iommu_domain_ops bcm2712_paging_domain_ops = {
|
||||
IOMMU_PT_DOMAIN_OPS(bcm2712),
|
||||
.attach_dev = bcm2712_iommu_attach_dev,
|
||||
.iotlb_sync = bcm2712_iommu_sync,
|
||||
.iotlb_sync_map = bcm2712_iommu_sync_map,
|
||||
.flush_iotlb_all = bcm2712_iommu_sync_all,
|
||||
.free = bcm2712_iommu_domain_free,
|
||||
};
|
||||
|
||||
static struct iommu_domain *bcm2712_iommu_domain_alloc(struct device *dev)
|
||||
{
|
||||
struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
|
||||
struct bcm2712_iommu_domain *domain;
|
||||
struct pt_iommu_bcm2712_cfg cfg = {};
|
||||
int ret;
|
||||
|
||||
domain = kzalloc_obj(*domain);
|
||||
if (!domain)
|
||||
return NULL;
|
||||
|
||||
domain->mmu = mmu;
|
||||
domain->pt.iommu.iommu_device = mmu->dev;
|
||||
cfg.common.features = BIT(PT_FEAT_DMA_INCOHERENT);
|
||||
|
||||
/* Bigpage and superpage sizes are typically 64K and 1M, but may vary */
|
||||
if (mmu->bigpage_size)
|
||||
cfg.bigpage_lg2 = ilog2(mmu->bigpage_size);
|
||||
if (mmu->superpage_size)
|
||||
cfg.superpage_lg2 = ilog2(mmu->superpage_size);
|
||||
|
||||
/* 2-level format: 10-bit L1 + 10-bit L2 + 12-bit page offset */
|
||||
cfg.common.hw_max_vasz_lg2 =
|
||||
(2 * PTES_PER_IOPG_SHIFT) + IOMMU_PAGE_SHIFT;
|
||||
|
||||
/* PTEs encode a 25-bit output address PFN */
|
||||
cfg.common.hw_max_oasz_lg2 = 25 + IOMMU_PAGE_SHIFT;
|
||||
|
||||
ret = pt_iommu_bcm2712_init(&domain->pt, &cfg, GFP_KERNEL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* Set up a default (error) page used to catch illegal reads/writes */
|
||||
domain->default_page = iommu_alloc_pages_sz(GFP_KERNEL, PAGE_SIZE);
|
||||
if (!domain->default_page)
|
||||
goto err;
|
||||
|
||||
domain->base.ops = &bcm2712_paging_domain_ops;
|
||||
return &domain->base;
|
||||
|
||||
err:
|
||||
bcm2712_iommu_domain_free(&domain->base);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct bcm2712_iommu *
|
||||
bcm2712_iommu_get_by_fwnode(struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct device *dev __free(put_device) =
|
||||
bus_find_device_by_fwnode(&platform_bus_type, fwnode);
|
||||
|
||||
return dev ? dev_get_drvdata(dev) : NULL;
|
||||
}
|
||||
|
||||
static struct iommu_device *bcm2712_iommu_probe_device(struct device *dev)
|
||||
{
|
||||
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
|
||||
struct bcm2712_iommu *mmu;
|
||||
|
||||
if (!fwspec || !fwspec->iommu_fwnode)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
mmu = bcm2712_iommu_get_by_fwnode(fwspec->iommu_fwnode);
|
||||
if (!mmu)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
dev_iommu_priv_set(dev, mmu);
|
||||
|
||||
return &mmu->iommu;
|
||||
}
|
||||
|
||||
static int bcm2712_iommu_of_xlate(struct device *dev,
|
||||
const struct of_phandle_args *args)
|
||||
{
|
||||
return iommu_fwspec_add_ids(dev, args->args, 0);
|
||||
}
|
||||
|
||||
static const struct iommu_ops bcm2712_iommu_ops = {
|
||||
.identity_domain = &bcm2712_identity_domain,
|
||||
.domain_alloc_paging = bcm2712_iommu_domain_alloc,
|
||||
.probe_device = bcm2712_iommu_probe_device,
|
||||
.device_group = generic_single_device_group,
|
||||
.get_resv_regions = iommu_dma_get_resv_regions,
|
||||
.of_xlate = bcm2712_iommu_of_xlate,
|
||||
};
|
||||
|
||||
static const struct of_device_id bcm2712_iommu_of_match[] = {
|
||||
{ .compatible = "brcm,bcm2712-iommu" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static int bcm2712_iommu_init_cache(struct bcm2712_iommu *mmu,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
struct device_node *cache_np __free(device_node) =
|
||||
of_parse_phandle(dev->of_node, "brcm,iommu-cache", 0);
|
||||
if (!cache_np)
|
||||
return dev_err_probe(dev, -ENOENT,
|
||||
"missing brcm,iommu-cache property\n");
|
||||
|
||||
struct platform_device *cache_pdev __free(platform_device_put) =
|
||||
of_find_device_by_node(cache_np);
|
||||
if (!cache_pdev)
|
||||
return dev_err_probe(dev, -EPROBE_DEFER,
|
||||
"waiting for cache device\n");
|
||||
|
||||
mmu->cache = platform_get_drvdata(cache_pdev);
|
||||
if (!mmu->cache)
|
||||
return dev_err_probe(dev, -EPROBE_DEFER,
|
||||
"waiting for cache driver probe\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm2712_iommu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct bcm2712_iommu *mmu;
|
||||
int ret;
|
||||
|
||||
mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL);
|
||||
if (!mmu)
|
||||
return -ENOMEM;
|
||||
|
||||
mmu->dev = dev;
|
||||
spin_lock_init(&mmu->hw_lock);
|
||||
|
||||
mmu->reg_base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(mmu->reg_base))
|
||||
return PTR_ERR(mmu->reg_base);
|
||||
|
||||
ret = bcm2712_iommu_init_cache(mmu, pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, mmu);
|
||||
bcm2712_iommu_init(mmu);
|
||||
|
||||
ret = iommu_device_sysfs_add(&mmu->iommu, dev, NULL, "%s",
|
||||
dev_name(dev));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = iommu_device_register(&mmu->iommu, &bcm2712_iommu_ops, dev);
|
||||
if (ret) {
|
||||
iommu_device_sysfs_remove(&mmu->iommu);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver bcm2712_iommu_driver = {
|
||||
.driver = {
|
||||
.name = "bcm2712-iommu",
|
||||
.of_match_table = bcm2712_iommu_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = bcm2712_iommu_probe,
|
||||
};
|
||||
builtin_platform_driver(bcm2712_iommu_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Daniel Drake <dan@reactivated.net>");
|
||||
MODULE_DESCRIPTION("Broadcom BCM2712 IOMMU driver");
|
||||
+20
-12
@@ -37,6 +37,7 @@
|
||||
|
||||
#include "dma-iommu.h"
|
||||
#include "iommu-pages.h"
|
||||
#include "iommu-priv.h"
|
||||
|
||||
struct iommu_dma_msi_page {
|
||||
struct list_head list;
|
||||
@@ -1477,6 +1478,11 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
|
||||
sg_dma_len(s) = s_length;
|
||||
s->offset -= s_iova_off;
|
||||
s_length = iova_align(iovad, s_length + s_iova_off);
|
||||
if (overflows_type(s_length, s->length)) {
|
||||
ret = -EOVERFLOW;
|
||||
|
||||
goto out_restore_sg;
|
||||
}
|
||||
s->length = s_length;
|
||||
|
||||
/*
|
||||
@@ -1493,7 +1499,20 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
|
||||
* time through here (i.e. before it has a meaningful value).
|
||||
*/
|
||||
if (pad_len && pad_len < s_length - 1) {
|
||||
prev->length += pad_len;
|
||||
unsigned int new_prev_len;
|
||||
|
||||
/*
|
||||
* For large mappings spanning multiple GBs we
|
||||
* may not be able to fit all needed padding into
|
||||
* sg->length.
|
||||
*/
|
||||
if (check_add_overflow(prev->length, pad_len, &new_prev_len)) {
|
||||
ret = -EOVERFLOW;
|
||||
|
||||
goto out_restore_sg;
|
||||
}
|
||||
|
||||
prev->length = new_prev_len;
|
||||
iova_len += pad_len;
|
||||
}
|
||||
|
||||
@@ -2163,12 +2182,6 @@ out_err:
|
||||
dev_clear_dma_iommu(dev);
|
||||
}
|
||||
|
||||
static bool has_msi_cookie(const struct iommu_domain *domain)
|
||||
{
|
||||
return domain && (domain->cookie_type == IOMMU_COOKIE_DMA_IOVA ||
|
||||
domain->cookie_type == IOMMU_COOKIE_DMA_MSI);
|
||||
}
|
||||
|
||||
static size_t cookie_msi_granule(const struct iommu_domain *domain)
|
||||
{
|
||||
switch (domain->cookie_type) {
|
||||
@@ -2250,11 +2263,6 @@ int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
|
||||
struct device *dev = msi_desc_to_dev(desc);
|
||||
const struct iommu_dma_msi_page *msi_page;
|
||||
|
||||
if (!has_msi_cookie(domain)) {
|
||||
msi_desc_set_iommu_msi_iova(desc, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
iommu_group_mutex_assert(dev);
|
||||
msi_page = iommu_dma_get_msi_page(dev, msi_addr, domain);
|
||||
if (!msi_page)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#define DEBUG
|
||||
#endif
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
@@ -143,6 +144,10 @@ static u32 lv2ent_offset(sysmmu_iova_t iova)
|
||||
#define CTRL_VM_FAULT_MODE_STALL BIT(3)
|
||||
#define CAPA0_CAPA1_EXIST BIT(11)
|
||||
#define CAPA1_VCR_ENABLED BIT(14)
|
||||
#define CAPA1_NO_BLOCK_MODE BIT(15)
|
||||
#define FAULT_INFO_AXID GENMASK(15, 0)
|
||||
#define FAULT_INFO_AXLEN GENMASK(19, 16)
|
||||
#define FAULT_INFO_WRITE BIT(20)
|
||||
|
||||
/* common registers */
|
||||
#define REG_MMU_CTRL 0x000
|
||||
@@ -193,6 +198,7 @@ struct sysmmu_fault {
|
||||
sysmmu_iova_t addr; /* IOVA address that caused fault */
|
||||
const char *name; /* human readable fault name */
|
||||
unsigned int type; /* fault type for report_iommu_fault() */
|
||||
u32 info; /* raw transaction info (v7+ only) */
|
||||
};
|
||||
|
||||
struct sysmmu_v1_fault_info {
|
||||
@@ -306,6 +312,7 @@ struct sysmmu_drvdata {
|
||||
|
||||
/* v7 fields */
|
||||
bool has_vcr; /* virtual machine control register */
|
||||
bool no_block; /* BLOCK mode not implemented */
|
||||
};
|
||||
|
||||
#define SYSMMU_REG(data, reg) ((data)->sfrbase + (data)->variant->reg)
|
||||
@@ -358,7 +365,8 @@ static int exynos_sysmmu_v7_get_fault_info(struct sysmmu_drvdata *data,
|
||||
|
||||
fault->addr = readl(SYSMMU_REG(data, fault_va));
|
||||
fault->name = sysmmu_v7_fault_names[itype % 4];
|
||||
fault->type = (info & BIT(20)) ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ;
|
||||
fault->type = (info & FAULT_INFO_WRITE) ? IOMMU_FAULT_WRITE : IOMMU_FAULT_READ;
|
||||
fault->info = info;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -511,6 +519,7 @@ static void __sysmmu_get_vcr(struct sysmmu_drvdata *data)
|
||||
u32 capa1 = readl(data->sfrbase + REG_V7_CAPA1);
|
||||
|
||||
data->has_vcr = capa1 & CAPA1_VCR_ENABLED;
|
||||
data->no_block = capa1 & CAPA1_NO_BLOCK_MODE;
|
||||
}
|
||||
|
||||
static void __sysmmu_get_version(struct sysmmu_drvdata *data)
|
||||
@@ -555,6 +564,12 @@ static void show_fault_information(struct sysmmu_drvdata *data,
|
||||
dev_name(data->master),
|
||||
fault->type == IOMMU_FAULT_READ ? "READ" : "WRITE",
|
||||
fault->name, fault->addr);
|
||||
/* AxID identifies the issuing port inside the master */
|
||||
if (data->variant->fault_info)
|
||||
dev_err(data->sysmmu, "transaction info %#010x: AxID %#lx, AxLEN %lu\n",
|
||||
fault->info,
|
||||
FIELD_GET(FAULT_INFO_AXID, fault->info),
|
||||
FIELD_GET(FAULT_INFO_AXLEN, fault->info));
|
||||
dev_dbg(data->sysmmu, "Page table base: %pa\n", &data->pgtable);
|
||||
ent = section_entry(phys_to_virt(data->pgtable), fault->addr);
|
||||
dev_dbg(data->sysmmu, "\tLv1 entry: %#x\n", *ent);
|
||||
@@ -652,7 +667,12 @@ static void __sysmmu_enable(struct sysmmu_drvdata *data)
|
||||
__sysmmu_enable_clocks(data);
|
||||
|
||||
spin_lock_irqsave(&data->lock, flags);
|
||||
writel(CTRL_BLOCK, data->sfrbase + REG_MMU_CTRL);
|
||||
/*
|
||||
* On no-block hardware CTRL_BLOCK acts as a plain enable; keep the
|
||||
* MMU disabled until it is fully programmed.
|
||||
*/
|
||||
if (!data->no_block)
|
||||
writel(CTRL_BLOCK, data->sfrbase + REG_MMU_CTRL);
|
||||
__sysmmu_init_config(data);
|
||||
__sysmmu_set_ptbase(data, data->pgtable);
|
||||
__sysmmu_enable_vid(data);
|
||||
@@ -677,7 +697,14 @@ static void sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data,
|
||||
spin_lock_irqsave(&data->lock, flags);
|
||||
if (data->active && data->version >= MAKE_MMU_VER(3, 3)) {
|
||||
clk_enable(data->clk_master);
|
||||
if (sysmmu_block(data)) {
|
||||
/*
|
||||
* No-block hardware accepts invalidation writes while the
|
||||
* MMU is running; it is also v7+, so flush-all covers the
|
||||
* FLPD cache.
|
||||
*/
|
||||
if (data->no_block) {
|
||||
__sysmmu_tlb_invalidate(data);
|
||||
} else if (sysmmu_block(data)) {
|
||||
if (data->version >= MAKE_MMU_VER(5, 0))
|
||||
__sysmmu_tlb_invalidate(data);
|
||||
else
|
||||
@@ -713,7 +740,9 @@ static void sysmmu_tlb_invalidate_entry(struct sysmmu_drvdata *data,
|
||||
if (MMU_MAJ_VER(data->version) == 2)
|
||||
num_inv = min_t(unsigned int, size / SPAGE_SIZE, 64);
|
||||
|
||||
if (sysmmu_block(data)) {
|
||||
if (data->no_block) {
|
||||
__sysmmu_tlb_invalidate_entry(data, iova, num_inv);
|
||||
} else if (sysmmu_block(data)) {
|
||||
__sysmmu_tlb_invalidate_entry(data, iova, num_inv);
|
||||
sysmmu_unblock(data);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ CONFIG_IOMMU_PT_AMDV1=y
|
||||
CONFIG_IOMMU_PT_VTDSS=y
|
||||
CONFIG_IOMMU_PT_RISCV64=y
|
||||
CONFIG_IOMMU_PT_X86_64=y
|
||||
CONFIG_IOMMU_PT_BCM2712=y
|
||||
CONFIG_IOMMU_PT_KUNIT_TEST=y
|
||||
|
||||
CONFIG_IOMMUFD=y
|
||||
|
||||
@@ -72,6 +72,15 @@ config IOMMU_PT_X86_64
|
||||
|
||||
Selected automatically by an IOMMU driver that uses this format.
|
||||
|
||||
config IOMMU_PT_BCM2712
|
||||
tristate "IOMMU page table for Broadcom BCM2712"
|
||||
help
|
||||
iommu_domain implementation for the Broadcom BCM2712 IOMMU found on
|
||||
Raspberry Pi 5. It supports 4K page sizes over a 2-level page table
|
||||
format.
|
||||
|
||||
Selected automatically by an IOMMU driver that uses this format.
|
||||
|
||||
config IOMMU_PT_KUNIT_TEST
|
||||
tristate "IOMMU Page Table KUnit Test" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT
|
||||
@@ -79,6 +88,7 @@ config IOMMU_PT_KUNIT_TEST
|
||||
depends on IOMMU_PT_RISCV64 || !IOMMU_PT_RISCV64
|
||||
depends on IOMMU_PT_X86_64 || !IOMMU_PT_X86_64
|
||||
depends on IOMMU_PT_VTDSS || !IOMMU_PT_VTDSS
|
||||
depends on IOMMU_PT_BCM2712 || !IOMMU_PT_BCM2712
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
Enable kunit tests for GENERIC_PT and IOMMU_PT that covers all the
|
||||
|
||||
@@ -9,6 +9,8 @@ iommu_pt_fmt-$(CONFIG_IOMMU_PT_RISCV64) += riscv64
|
||||
|
||||
iommu_pt_fmt-$(CONFIG_IOMMU_PT_X86_64) += x86_64
|
||||
|
||||
iommu_pt_fmt-$(CONFIG_IOMMU_PT_BCM2712) += bcm2712
|
||||
|
||||
IOMMU_PT_KUNIT_TEST :=
|
||||
define create_format
|
||||
obj-$(2) += iommu_$(1).o
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2026 Daniel Drake
|
||||
*
|
||||
* BCM2712 IOMMU simple two level page table
|
||||
*/
|
||||
#ifndef __GENERIC_PT_FMT_BCM2712_H
|
||||
#define __GENERIC_PT_FMT_BCM2712_H
|
||||
|
||||
#include "defs_bcm2712.h"
|
||||
#include "../pt_defs.h"
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
enum {
|
||||
/* Hardware provides a two-level page table */
|
||||
PT_MAX_TOP_LEVEL = 1,
|
||||
|
||||
/* Hardware page size is strictly 4kb */
|
||||
PT_GRANULE_LG2SZ = ilog2(SZ_4K),
|
||||
PT_TABLEMEM_LG2SZ = PT_GRANULE_LG2SZ,
|
||||
|
||||
/* Table entries and leaf entries are 32 bits */
|
||||
PT_ITEM_WORD_SIZE = sizeof(pt_bcm2712_entry_t),
|
||||
|
||||
/* Leaf entries encode a 25-bit PFN */
|
||||
PT_MAX_OUTPUT_ADDRESS_LG2 = 25 + PT_GRANULE_LG2SZ,
|
||||
|
||||
/*
|
||||
* For simplicity, only manage mappings within an address space of
|
||||
* exactly 4GB. This is because our Level 1 directory page will be 4kb
|
||||
* in the smallest case, permitting 1024 entries pointing at Level 0
|
||||
* pages, each permitting 4mb of mapped memory.
|
||||
*/
|
||||
PT_MAX_VA_ADDRESS_LG2 = ilog2(SZ_4G),
|
||||
|
||||
/* Level 1 base address is programmed as a 32-bit PFN */
|
||||
PT_TOP_PHYS_MASK = GENMASK_ULL(31 + PT_GRANULE_LG2SZ, PT_GRANULE_LG2SZ),
|
||||
};
|
||||
|
||||
/* PTE bits */
|
||||
enum {
|
||||
BCM2712PT_VALID = BIT(28),
|
||||
BCM2712PT_WRITE = BIT(29),
|
||||
BCM2712PT_PAGESIZE = GENMASK(31, 30),
|
||||
BCM2712PT_PFN = GENMASK(24, 0),
|
||||
};
|
||||
|
||||
#define common_to_bcm2712pt(common_ptr) \
|
||||
container_of_const(common_ptr, struct pt_bcm2712, common)
|
||||
#define to_bcm2712pt(pts) common_to_bcm2712pt((pts)->range->common)
|
||||
|
||||
static inline pt_oaddr_t bcm2712pt_table_pa(const struct pt_state *pts)
|
||||
{
|
||||
return oalog2_mul(FIELD_GET(BCM2712PT_PFN, pts->entry),
|
||||
PT_GRANULE_LG2SZ);
|
||||
}
|
||||
#define pt_table_pa bcm2712pt_table_pa
|
||||
#define pt_item_oa bcm2712pt_table_pa
|
||||
|
||||
static inline bool bcm2712pt_can_have_leaf(const struct pt_state *pts)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#define pt_can_have_leaf bcm2712pt_can_have_leaf
|
||||
|
||||
/* 4MB pages are installed at level 1, everything else at level 0 */
|
||||
static inline unsigned int bcm2712pt_pgsz_lg2_to_level(struct pt_common *common,
|
||||
unsigned int pgsize_lg2)
|
||||
{
|
||||
return pgsize_lg2 == ilog2(SZ_4M) ? 1 : 0;
|
||||
}
|
||||
#define pt_pgsz_lg2_to_level bcm2712pt_pgsz_lg2_to_level
|
||||
|
||||
static inline pt_vaddr_t bcm2712pt_possible_sizes(const struct pt_state *pts)
|
||||
{
|
||||
struct pt_bcm2712 *table = common_to_bcm2712pt(pts->range->common);
|
||||
|
||||
if (pts->level == 1)
|
||||
return SZ_4M;
|
||||
|
||||
return SZ_4K |
|
||||
(table->bigpage_lg2 ? BIT_ULL(table->bigpage_lg2) : 0) |
|
||||
(table->superpage_lg2 ? BIT_ULL(table->superpage_lg2) : 0);
|
||||
}
|
||||
#define pt_possible_sizes bcm2712pt_possible_sizes
|
||||
|
||||
static inline unsigned int
|
||||
bcm2712pt_entry_num_contig_lg2(const struct pt_state *pts)
|
||||
{
|
||||
struct pt_bcm2712 *table = common_to_bcm2712pt(pts->range->common);
|
||||
u32 pgsz = FIELD_GET(BCM2712PT_PAGESIZE, pts->entry);
|
||||
|
||||
if (pts->level != 0 || !(pts->entry & BCM2712PT_VALID))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Superpage/bigpage contiguous mapping (really just hinting) is handled
|
||||
* via PAGESIZE bits on each leaf entry.
|
||||
*/
|
||||
if (pgsz == 2 && table->superpage_lg2)
|
||||
return table->superpage_lg2 - PT_GRANULE_LG2SZ;
|
||||
else if (pgsz == 1 && table->bigpage_lg2)
|
||||
return table->bigpage_lg2 - PT_GRANULE_LG2SZ;
|
||||
return 0;
|
||||
}
|
||||
#define pt_entry_num_contig_lg2 bcm2712pt_entry_num_contig_lg2
|
||||
|
||||
static inline unsigned int bcm2712pt_num_items_lg2(const struct pt_state *pts)
|
||||
{
|
||||
return PT_TABLEMEM_LG2SZ - ilog2(PT_ITEM_WORD_SIZE);
|
||||
}
|
||||
#define pt_num_items_lg2 bcm2712pt_num_items_lg2
|
||||
|
||||
static inline enum pt_entry_type bcm2712pt_load_entry_raw(struct pt_state *pts)
|
||||
{
|
||||
const pt_bcm2712_entry_t *tablep =
|
||||
pt_cur_table(pts, pt_bcm2712_entry_t);
|
||||
|
||||
pts->entry = READ_ONCE(tablep[pts->index]);
|
||||
if (!(pts->entry & BCM2712PT_VALID))
|
||||
return PT_ENTRY_EMPTY;
|
||||
|
||||
if (pts->level == 1) {
|
||||
if (FIELD_GET(BCM2712PT_PAGESIZE, pts->entry) == 3)
|
||||
return PT_ENTRY_OA;
|
||||
return PT_ENTRY_TABLE;
|
||||
}
|
||||
|
||||
return PT_ENTRY_OA;
|
||||
}
|
||||
#define pt_load_entry_raw bcm2712pt_load_entry_raw
|
||||
|
||||
static inline void
|
||||
bcm2712pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
|
||||
unsigned int oasz_lg2,
|
||||
const struct pt_write_attrs *attrs)
|
||||
{
|
||||
pt_bcm2712_entry_t *tablep = pt_cur_table(pts, pt_bcm2712_entry_t);
|
||||
pt_bcm2712_entry_t entry;
|
||||
|
||||
if (!pt_check_install_leaf_args(pts, oa, oasz_lg2))
|
||||
return;
|
||||
|
||||
entry = BCM2712PT_VALID | attrs->descriptor_bits |
|
||||
FIELD_PREP(BCM2712PT_PFN, oalog2_div(oa, PT_GRANULE_LG2SZ));
|
||||
|
||||
if (pts->level == 1) {
|
||||
/* Level 1 hugepage (4MB) */
|
||||
entry |= FIELD_PREP(BCM2712PT_PAGESIZE, 3);
|
||||
WRITE_ONCE(tablep[pts->index], entry);
|
||||
pts->entry = entry;
|
||||
return;
|
||||
}
|
||||
|
||||
if (oasz_lg2 == PT_GRANULE_LG2SZ) {
|
||||
WRITE_ONCE(tablep[pts->index], entry);
|
||||
pts->entry = entry;
|
||||
} else {
|
||||
struct pt_bcm2712 *table =
|
||||
common_to_bcm2712pt(pts->range->common);
|
||||
u32 *end;
|
||||
|
||||
tablep += pts->index;
|
||||
end = tablep + log2_to_int(oasz_lg2 - PT_GRANULE_LG2SZ);
|
||||
|
||||
/*
|
||||
* Leaf entries can contain hints indicating bigpage/superpage
|
||||
* contiguous mappings to permit TLB optimization
|
||||
*/
|
||||
if (oasz_lg2 == table->superpage_lg2)
|
||||
entry |= FIELD_PREP(BCM2712PT_PAGESIZE, 2);
|
||||
else if (oasz_lg2 == table->bigpage_lg2)
|
||||
entry |= FIELD_PREP(BCM2712PT_PAGESIZE, 1);
|
||||
|
||||
pts->entry = entry;
|
||||
for (; tablep != end; tablep++, entry++)
|
||||
WRITE_ONCE(*tablep, entry);
|
||||
}
|
||||
}
|
||||
#define pt_install_leaf_entry bcm2712pt_install_leaf_entry
|
||||
|
||||
static inline bool bcm2712pt_install_table(struct pt_state *pts,
|
||||
pt_oaddr_t table_pa,
|
||||
const struct pt_write_attrs *attrs)
|
||||
{
|
||||
pt_bcm2712_entry_t entry =
|
||||
BCM2712PT_VALID |
|
||||
FIELD_PREP(BCM2712PT_PFN,
|
||||
oalog2_div(table_pa, PT_GRANULE_LG2SZ));
|
||||
|
||||
return pt_table_install32(pts, entry);
|
||||
}
|
||||
#define pt_install_table bcm2712pt_install_table
|
||||
|
||||
static inline void bcm2712pt_attr_from_entry(const struct pt_state *pts,
|
||||
struct pt_write_attrs *attrs)
|
||||
{
|
||||
attrs->descriptor_bits = pts->entry & BCM2712PT_WRITE;
|
||||
}
|
||||
#define pt_attr_from_entry bcm2712pt_attr_from_entry
|
||||
|
||||
static inline unsigned int bcm2712pt_max_sw_bit(struct pt_common *common)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
#define pt_max_sw_bit bcm2712pt_max_sw_bit
|
||||
|
||||
static inline u32 bcm2712pt_sw_bit(unsigned int bitnr)
|
||||
{
|
||||
if (__builtin_constant_p(bitnr) && bitnr > 2)
|
||||
BUILD_BUG();
|
||||
if (PT_WARN_ON(bitnr > 2))
|
||||
return 0;
|
||||
|
||||
/* Bits 27, 26 and 25 are believed spare */
|
||||
return BIT(27 - bitnr);
|
||||
}
|
||||
#define pt_sw_bit bcm2712pt_sw_bit
|
||||
|
||||
/* --- iommu */
|
||||
#include <linux/generic_pt/iommu.h>
|
||||
#include <linux/iommu.h>
|
||||
|
||||
#define pt_iommu_table pt_iommu_bcm2712
|
||||
|
||||
/* The common struct is in the per-format common struct */
|
||||
static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table)
|
||||
{
|
||||
return &container_of(iommu_table, struct pt_iommu_table, iommu)
|
||||
->bcm2712pt.common;
|
||||
}
|
||||
|
||||
static inline struct pt_iommu *iommu_from_common(struct pt_common *common)
|
||||
{
|
||||
return &container_of(common, struct pt_iommu_table, bcm2712pt.common)
|
||||
->iommu;
|
||||
}
|
||||
|
||||
static inline int bcm2712_pt_iommu_set_prot(struct pt_common *common,
|
||||
struct pt_write_attrs *attrs,
|
||||
unsigned int iommu_prot)
|
||||
{
|
||||
attrs->descriptor_bits = 0;
|
||||
if (iommu_prot & IOMMU_WRITE)
|
||||
attrs->descriptor_bits |= BCM2712PT_WRITE;
|
||||
return 0;
|
||||
}
|
||||
#define pt_iommu_set_prot bcm2712_pt_iommu_set_prot
|
||||
|
||||
static inline int bcm2712_pt_fmt_init(struct pt_iommu_table *fmt_table,
|
||||
const struct pt_iommu_bcm2712_cfg *cfg)
|
||||
{
|
||||
fmt_table->bcm2712pt.bigpage_lg2 = cfg->bigpage_lg2;
|
||||
fmt_table->bcm2712pt.superpage_lg2 = cfg->superpage_lg2;
|
||||
|
||||
pt_top_set_level(&fmt_table->bcm2712pt.common, PT_MAX_TOP_LEVEL);
|
||||
return 0;
|
||||
}
|
||||
#define pt_iommu_fmt_init bcm2712_pt_fmt_init
|
||||
|
||||
static inline void
|
||||
bcm2712pt_iommu_fmt_hw_info(struct pt_iommu_bcm2712 *table,
|
||||
const struct pt_range *top_range,
|
||||
struct pt_iommu_bcm2712_hw_info *info)
|
||||
{
|
||||
info->pt_base = virt_to_phys(top_range->top_table);
|
||||
PT_WARN_ON(info->pt_base & ~PT_TOP_PHYS_MASK);
|
||||
}
|
||||
#define pt_iommu_fmt_hw_info bcm2712pt_iommu_fmt_hw_info
|
||||
|
||||
#if defined(GENERIC_PT_KUNIT)
|
||||
static const struct pt_iommu_bcm2712_cfg bcm2712_kunit_fmt_cfgs[] = {
|
||||
[0] = {
|
||||
.common.hw_max_vasz_lg2 = 32,
|
||||
.common.hw_max_oasz_lg2 = 37,
|
||||
.bigpage_lg2 = 16,
|
||||
.superpage_lg2 = 20,
|
||||
},
|
||||
};
|
||||
#define kunit_fmt_cfgs bcm2712_kunit_fmt_cfgs
|
||||
enum { KUNIT_FMT_FEATURES = 0 };
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef __GENERIC_PT_FMT_DEFS_BCM2712_H
|
||||
#define __GENERIC_PT_FMT_DEFS_BCM2712_H
|
||||
|
||||
#include <linux/generic_pt/common.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
typedef u32 pt_bcm2712_entry_t;
|
||||
typedef u64 pt_vaddr_t;
|
||||
typedef u64 pt_oaddr_t;
|
||||
|
||||
struct bcm2712pt_write_attrs {
|
||||
pt_bcm2712_entry_t descriptor_bits;
|
||||
gfp_t gfp;
|
||||
};
|
||||
#define pt_write_attrs bcm2712pt_write_attrs
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#define PT_FMT bcm2712
|
||||
#define PT_SUPPORTED_FEATURES BIT(PT_FEAT_DMA_INCOHERENT)
|
||||
|
||||
#include <linux/generic_pt/iommu.h>
|
||||
#include "iommu_template.h"
|
||||
@@ -403,7 +403,7 @@ static void test_pgsize_boundary(struct kunit *test)
|
||||
priv->smallest_pgsz != SZ_4K)
|
||||
kunit_skip(test, "Format does not have the required range");
|
||||
|
||||
do_map(test, 0xfef80000, 0x208b95d000, 0xfef9ffff - 0xfef80000 + 1);
|
||||
do_map(test, 0xfef80000, 0x8b95d000, 0xfef9ffff - 0xfef80000 + 1);
|
||||
}
|
||||
|
||||
/* See https://lore.kernel.org/r/20250826143816.38686-1-eugkoira@amazon.com */
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# Hyper-V para-virtualized IOMMU driver for Linux guests
|
||||
config HYPERV_PVIOMMU
|
||||
bool "Microsoft Hyper-V para-virtualized IOMMU driver for Linux guests"
|
||||
depends on X86_64 && HYPERV && PCI
|
||||
select IOMMU_DMA
|
||||
select GENERIC_PT
|
||||
select IOMMU_PT
|
||||
select IOMMU_PT_X86_64
|
||||
default HYPERV
|
||||
help
|
||||
Para-virtualized IOMMU driver for Linux guests running on
|
||||
Microsoft Hyper-V. Provides DMA remapping and IOTLB
|
||||
flush support to enable DMA isolation for PCI devices
|
||||
assigned to the guest.
|
||||
@@ -1,2 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_IRQ_REMAP) += hv-irq-remap-x86.o
|
||||
obj-$(CONFIG_HYPERV_PVIOMMU) += hv-iommu-guest.o
|
||||
@@ -0,0 +1,682 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* Hyper-V para-virtualized IOMMU driver for Linux guests.
|
||||
*
|
||||
* Copyright (C) 2024-2026 Microsoft, Inc.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "Hyper-V pvIOMMU: " fmt
|
||||
#define dev_fmt(fmt) pr_fmt(fmt)
|
||||
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/dma-map-ops.h>
|
||||
#include <linux/generic_pt/iommu.h>
|
||||
#include <linux/pci-ats.h>
|
||||
|
||||
#include <asm/iommu.h>
|
||||
#include <asm/hypervisor.h>
|
||||
#include <asm/mshyperv.h>
|
||||
|
||||
#include "iommu.h"
|
||||
#include "../iommu-pages.h"
|
||||
|
||||
static struct hv_iommu_dev *hv_iommu_device;
|
||||
|
||||
/*
|
||||
* Identity and blocking domains are static singletons: identity is a 1:1
|
||||
* passthrough with no page table, blocking rejects all DMA. Neither holds
|
||||
* per-IOMMU state, so one instance suffices even with multiple vIOMMUs.
|
||||
*/
|
||||
static const struct iommu_domain_ops hv_iommu_identity_domain_ops;
|
||||
static const struct iommu_domain_ops hv_iommu_blocking_domain_ops;
|
||||
static struct iommu_ops hv_iommu_ops;
|
||||
|
||||
static struct hv_iommu_domain hv_identity_domain = {
|
||||
.domain = {
|
||||
.type = IOMMU_DOMAIN_IDENTITY,
|
||||
.ops = &hv_iommu_identity_domain_ops,
|
||||
.owner = &hv_iommu_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct hv_iommu_domain hv_blocking_domain = {
|
||||
.domain = {
|
||||
.type = IOMMU_DOMAIN_BLOCKED,
|
||||
.ops = &hv_iommu_blocking_domain_ops,
|
||||
.owner = &hv_iommu_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static inline bool hv_iommu_present(u64 cap)
|
||||
{
|
||||
return cap & HV_IOMMU_CAP_PRESENT;
|
||||
}
|
||||
|
||||
static inline bool hv_iommu_s1_domain_supported(u64 cap)
|
||||
{
|
||||
return cap & HV_IOMMU_CAP_S1;
|
||||
}
|
||||
|
||||
static inline bool hv_iommu_5lvl_supported(u64 cap)
|
||||
{
|
||||
return cap & HV_IOMMU_CAP_S1_5LVL;
|
||||
}
|
||||
|
||||
static inline bool hv_iommu_ats_supported(u64 cap)
|
||||
{
|
||||
return cap & HV_IOMMU_CAP_ATS;
|
||||
}
|
||||
|
||||
static int hv_create_device_domain(struct hv_iommu_domain *hv_domain)
|
||||
{
|
||||
int ret;
|
||||
u64 status;
|
||||
unsigned long flags;
|
||||
struct hv_input_create_device_domain *input;
|
||||
|
||||
ret = ida_alloc_range(&hv_iommu_device->domain_ids,
|
||||
hv_iommu_device->first_domain,
|
||||
hv_iommu_device->last_domain, GFP_KERNEL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
hv_domain->device_domain.partition_id = HV_PARTITION_ID_SELF;
|
||||
hv_domain->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S1;
|
||||
hv_domain->device_domain.domain_id.id = ret;
|
||||
hv_domain->hv_iommu = hv_iommu_device;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
memset(input, 0, sizeof(*input));
|
||||
input->device_domain = hv_domain->device_domain;
|
||||
input->create_device_domain_flags.forward_progress_required = 1;
|
||||
input->create_device_domain_flags.inherit_owning_vtl = 0;
|
||||
status = hv_do_hypercall(HVCALL_CREATE_DEVICE_DOMAIN, input, NULL);
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (!hv_result_success(status)) {
|
||||
hv_status_err(status, "HVCALL_CREATE_DEVICE_DOMAIN failed\n");
|
||||
ida_free(&hv_iommu_device->domain_ids,
|
||||
hv_domain->device_domain.domain_id.id);
|
||||
}
|
||||
|
||||
return hv_result_to_errno(status);
|
||||
}
|
||||
|
||||
static void hv_delete_device_domain(struct hv_iommu_domain *hv_domain)
|
||||
{
|
||||
u64 status;
|
||||
unsigned long flags;
|
||||
struct hv_input_delete_device_domain *input;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
memset(input, 0, sizeof(*input));
|
||||
input->device_domain = hv_domain->device_domain;
|
||||
status = hv_do_hypercall(HVCALL_DELETE_DEVICE_DOMAIN, input, NULL);
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (!hv_result_success(status))
|
||||
hv_status_err(status, "HVCALL_DELETE_DEVICE_DOMAIN failed\n");
|
||||
|
||||
ida_free(&hv_domain->hv_iommu->domain_ids,
|
||||
hv_domain->device_domain.domain_id.id);
|
||||
}
|
||||
|
||||
static int
|
||||
hv_configure_device_domain(struct hv_iommu_domain *hv_domain,
|
||||
const struct hv_device_domain_settings *settings)
|
||||
{
|
||||
u64 status;
|
||||
unsigned long flags;
|
||||
struct hv_input_configure_device_domain *input;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
memset(input, 0, sizeof(*input));
|
||||
input->device_domain = hv_domain->device_domain;
|
||||
input->settings = *settings;
|
||||
status = hv_do_hypercall(HVCALL_CONFIGURE_DEVICE_DOMAIN, input, NULL);
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (!hv_result_success(status))
|
||||
hv_status_err(status,
|
||||
"HVCALL_CONFIGURE_DEVICE_DOMAIN failed\n");
|
||||
|
||||
return hv_result_to_errno(status);
|
||||
}
|
||||
|
||||
static int
|
||||
hv_create_configure_device_domain(struct hv_iommu_domain *hv_domain,
|
||||
const struct hv_device_domain_settings *settings)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hv_create_device_domain(hv_domain);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hv_configure_device_domain(hv_domain, settings);
|
||||
if (ret)
|
||||
hv_delete_device_domain(hv_domain);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool hv_iommu_capable(struct device *dev, enum iommu_cap cap)
|
||||
{
|
||||
return cap == IOMMU_CAP_CACHE_COHERENCY;
|
||||
}
|
||||
|
||||
static void hv_flush_device_domain(struct hv_iommu_domain *hv_domain)
|
||||
{
|
||||
u64 status;
|
||||
unsigned long flags;
|
||||
struct hv_input_flush_device_domain *input;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
memset(input, 0, sizeof(*input));
|
||||
input->device_domain = hv_domain->device_domain;
|
||||
status = hv_do_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN, input, NULL);
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
WARN_ONCE(!hv_result_success(status),
|
||||
"HVCALL_FLUSH_DEVICE_DOMAIN failed: %#llx (%s)\n",
|
||||
status, hv_result_to_string(status));
|
||||
}
|
||||
|
||||
static int hv_iommu_attach_dev(struct iommu_domain *domain, struct device *dev,
|
||||
struct iommu_domain *old)
|
||||
{
|
||||
u64 status;
|
||||
u32 prefix;
|
||||
unsigned long flags;
|
||||
struct pci_dev *pdev;
|
||||
struct hv_input_attach_device_domain *input;
|
||||
struct hv_iommu_domain *hv_domain = to_hv_iommu_domain(domain);
|
||||
int pci_domain;
|
||||
int ret;
|
||||
|
||||
pdev = to_pci_dev(dev);
|
||||
dev_dbg(dev, "attaching to domain %d\n",
|
||||
hv_domain->device_domain.domain_id.id);
|
||||
|
||||
pci_domain = pci_domain_nr(pdev->bus);
|
||||
ret = hv_pci_lookup_dev_id(pci_domain, &prefix);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"no logical device ID registered for PCI domain %04x\n",
|
||||
pci_domain);
|
||||
return ret;
|
||||
}
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
memset(input, 0, sizeof(*input));
|
||||
input->device_domain = hv_domain->device_domain;
|
||||
input->device_id.as_uint64 = (u64)prefix | PCI_FUNC(pdev->devfn);
|
||||
status = hv_do_hypercall(HVCALL_ATTACH_DEVICE_DOMAIN, input, NULL);
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (!hv_result_success(status) &&
|
||||
hv_result(status) != HV_STATUS_DEVICE_ALREADY_IN_DOMAIN) {
|
||||
hv_status_err(status, "HVCALL_ATTACH_DEVICE_DOMAIN failed\n");
|
||||
return hv_result_to_errno(status);
|
||||
}
|
||||
|
||||
if (domain != &hv_blocking_domain.domain &&
|
||||
!pdev->ats_enabled &&
|
||||
hv_iommu_ats_supported(hv_domain->hv_iommu->cap) &&
|
||||
pci_ats_supported(pdev))
|
||||
pci_enable_ats(pdev, PAGE_SHIFT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hv_iommu_blocking_attach_dev(struct iommu_domain *domain,
|
||||
struct device *dev,
|
||||
struct iommu_domain *old)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
int ret;
|
||||
|
||||
if (pdev->ats_enabled)
|
||||
pci_disable_ats(pdev);
|
||||
|
||||
ret = hv_iommu_attach_dev(domain, dev, old);
|
||||
|
||||
/*
|
||||
* Attaching to the blocking domain only asks the hypervisor to
|
||||
* disable translation and IOPF for the device, so it cannot fail
|
||||
* unless there is a driver or hypervisor bug. Return the hypercall
|
||||
* status rather than 0 so that a failure on the DMA ownership claim
|
||||
* path (VFIO/iommufd) fails the claim instead of leaving the device
|
||||
* unblocked. WARN since such a failure indicates a bug.
|
||||
*/
|
||||
WARN_ON(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
hv_iommu_get_dev_property(struct device *dev, u32 code,
|
||||
struct hv_output_get_logical_device_property *prop)
|
||||
{
|
||||
u64 status;
|
||||
u32 prefix;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
struct hv_input_get_logical_device_property *input;
|
||||
struct hv_output_get_logical_device_property *output;
|
||||
|
||||
ret = hv_pci_lookup_dev_id(pci_domain_nr(pdev->bus), &prefix);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
/*
|
||||
* The combined input and output fit in the per-CPU input page,
|
||||
* so no separate output page is needed.
|
||||
*/
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
output = (struct hv_output_get_logical_device_property *)(input + 1);
|
||||
memset(input, 0, sizeof(*input));
|
||||
input->partition_id = HV_PARTITION_ID_SELF;
|
||||
input->logical_device_id = (u64)prefix | PCI_FUNC(pdev->devfn);
|
||||
input->code = code;
|
||||
status = hv_do_hypercall(HVCALL_GET_LOGICAL_DEVICE_PROPERTY,
|
||||
input, output);
|
||||
*prop = *output;
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (!hv_result_success(status))
|
||||
hv_status_err(status,
|
||||
"HVCALL_GET_LOGICAL_DEVICE_PROPERTY failed\n");
|
||||
|
||||
return hv_result_to_errno(status);
|
||||
}
|
||||
|
||||
static struct iommu_device *hv_iommu_probe_device(struct device *dev)
|
||||
{
|
||||
struct hv_output_get_logical_device_property property = {0};
|
||||
int ret;
|
||||
|
||||
if (!dev_is_pci(dev))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
ret = hv_iommu_get_dev_property(dev, HV_LOGICAL_DEVICE_PROPERTY_PVIOMMU,
|
||||
&property);
|
||||
if (ret || !(property.device_iommu & HV_DEVICE_IOMMU_ENABLED))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return &hv_iommu_device->iommu;
|
||||
}
|
||||
|
||||
static struct iommu_group *hv_iommu_device_group(struct device *dev)
|
||||
{
|
||||
if (!dev_is_pci(dev))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return pci_device_group(dev);
|
||||
}
|
||||
|
||||
static int __init hv_initialize_static_domains(void)
|
||||
{
|
||||
/*
|
||||
* Clearing translation_enabled bypasses stage-1 translation, so DMA
|
||||
* addresses are used directly as GPAs. Hyper-V requires paging and
|
||||
* blocked domains to keep translation_enabled set.
|
||||
*/
|
||||
const struct hv_device_domain_settings identity_settings = {
|
||||
.flags.translation_enabled = 0,
|
||||
};
|
||||
const struct hv_device_domain_settings blocked_settings = {
|
||||
.flags = {
|
||||
.translation_enabled = 1,
|
||||
.blocked = 1,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
|
||||
/* Default stage-1 identity domain */
|
||||
ret = hv_create_configure_device_domain(&hv_identity_domain,
|
||||
&identity_settings);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Default stage-1 blocked domain */
|
||||
ret = hv_create_configure_device_domain(&hv_blocking_domain,
|
||||
&blocked_settings);
|
||||
if (ret)
|
||||
goto delete_identity_domain;
|
||||
|
||||
return 0;
|
||||
|
||||
delete_identity_domain:
|
||||
hv_delete_device_domain(&hv_identity_domain);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void hv_iommu_get_resv_regions(struct device *dev,
|
||||
struct list_head *head)
|
||||
{
|
||||
iommu_add_resv_x86_msi_region(head);
|
||||
}
|
||||
|
||||
static void hv_iommu_flush_iotlb_all(struct iommu_domain *domain)
|
||||
{
|
||||
hv_flush_device_domain(to_hv_iommu_domain(domain));
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the minimal power-of-two aligned range that covers [start, end]
|
||||
* (end is inclusive). Returns a single (page_number, page_mask_shift)
|
||||
* descriptor that may over-flush when the range is not naturally aligned.
|
||||
*/
|
||||
static void
|
||||
hv_iommu_calc_flush_range(unsigned long start, unsigned long end,
|
||||
union hv_iommu_flush_va *va)
|
||||
{
|
||||
unsigned long aligned_start;
|
||||
unsigned int sz_lg2;
|
||||
|
||||
sz_lg2 = max(fls_long(start ^ end), HV_HYP_PAGE_SHIFT);
|
||||
|
||||
/*
|
||||
* A valid IOVA range shall not span bit 63. Use the maximum mask
|
||||
* so the host can safely perform a full flush.
|
||||
*/
|
||||
if (WARN_ON_ONCE(sz_lg2 == BITS_PER_LONG))
|
||||
aligned_start = 0;
|
||||
else
|
||||
aligned_start =
|
||||
start & GENMASK(BITS_PER_LONG - 1, sz_lg2);
|
||||
|
||||
va->page_number = aligned_start >> HV_HYP_PAGE_SHIFT;
|
||||
va->page_mask_shift = sz_lg2 - HV_HYP_PAGE_SHIFT;
|
||||
}
|
||||
|
||||
static void hv_flush_device_domain_list(struct hv_iommu_domain *hv_domain,
|
||||
struct iommu_iotlb_gather *iotlb_gather)
|
||||
{
|
||||
u64 status;
|
||||
unsigned long flags;
|
||||
struct hv_input_flush_device_domain_list *input;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
/* Clear the fixed header and the single range entry. */
|
||||
memset(input, 0, struct_size(input, iova_list, 1));
|
||||
|
||||
input->device_domain = hv_domain->device_domain;
|
||||
input->flags |= HV_FLUSH_DEVICE_DOMAIN_LIST_IOMMU_FORMAT;
|
||||
hv_iommu_calc_flush_range(iotlb_gather->start, iotlb_gather->end,
|
||||
&input->iova_list[0]);
|
||||
|
||||
status = hv_do_rep_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN_LIST,
|
||||
1, 0, input, NULL);
|
||||
|
||||
if (WARN_ONCE(!hv_result_success(status),
|
||||
"HVCALL_FLUSH_DEVICE_DOMAIN_LIST failed: %#llx (%s)\n",
|
||||
status, hv_result_to_string(status))) {
|
||||
/* Page-selective flush failed, fall back to full flush. */
|
||||
struct hv_input_flush_device_domain *flush_all = (void *)input;
|
||||
|
||||
memset(flush_all, 0, sizeof(*flush_all));
|
||||
flush_all->device_domain = hv_domain->device_domain;
|
||||
status = hv_do_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN,
|
||||
flush_all, NULL);
|
||||
WARN(!hv_result_success(status),
|
||||
"HVCALL_FLUSH_DEVICE_DOMAIN fallback also failed: %#llx (%s)\n",
|
||||
status, hv_result_to_string(status));
|
||||
}
|
||||
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static void hv_iommu_iotlb_sync(struct iommu_domain *domain,
|
||||
struct iommu_iotlb_gather *iotlb_gather)
|
||||
{
|
||||
hv_flush_device_domain_list(to_hv_iommu_domain(domain), iotlb_gather);
|
||||
|
||||
/*
|
||||
* The hypercall also invalidates non-leaf page-walk caches, so
|
||||
* detached page-table pages can now be freed.
|
||||
*/
|
||||
iommu_put_pages_list(&iotlb_gather->freelist);
|
||||
}
|
||||
|
||||
static void hv_iommu_paging_domain_free(struct iommu_domain *domain)
|
||||
{
|
||||
struct hv_iommu_domain *hv_domain = to_hv_iommu_domain(domain);
|
||||
|
||||
/* Free all remaining mappings */
|
||||
pt_iommu_deinit(&hv_domain->pt_iommu);
|
||||
|
||||
hv_delete_device_domain(hv_domain);
|
||||
|
||||
kfree(hv_domain);
|
||||
}
|
||||
|
||||
static const struct iommu_domain_ops hv_iommu_identity_domain_ops = {
|
||||
.attach_dev = hv_iommu_attach_dev,
|
||||
};
|
||||
|
||||
static const struct iommu_domain_ops hv_iommu_blocking_domain_ops = {
|
||||
.attach_dev = hv_iommu_blocking_attach_dev,
|
||||
};
|
||||
|
||||
static const struct iommu_domain_ops hv_iommu_paging_domain_ops = {
|
||||
.attach_dev = hv_iommu_attach_dev,
|
||||
IOMMU_PT_DOMAIN_OPS(x86_64),
|
||||
.flush_iotlb_all = hv_iommu_flush_iotlb_all,
|
||||
.iotlb_sync = hv_iommu_iotlb_sync,
|
||||
.free = hv_iommu_paging_domain_free,
|
||||
};
|
||||
|
||||
static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct hv_iommu_domain *hv_domain;
|
||||
struct pt_iommu_x86_64_hw_info pt_info;
|
||||
struct pt_iommu_x86_64_cfg cfg = {};
|
||||
struct hv_device_domain_settings settings = {
|
||||
.flags = {
|
||||
.translation_enabled = 1,
|
||||
},
|
||||
};
|
||||
|
||||
hv_domain = kzalloc_obj(*hv_domain, GFP_KERNEL);
|
||||
if (!hv_domain)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
hv_domain->pt_iommu.nid = dev_to_node(dev);
|
||||
|
||||
cfg.common.hw_max_vasz_lg2 = hv_iommu_device->max_iova_width;
|
||||
cfg.common.hw_max_oasz_lg2 = 52;
|
||||
cfg.common.features |= BIT(PT_FEAT_FLUSH_RANGE);
|
||||
/*
|
||||
* Hyper-V S1 domains use a 4-level root for IOVA widths up to
|
||||
* 48 bits. A 5-level root is used only for wider apertures when
|
||||
* HV_IOMMU_CAP_S1_5LVL is advertised.
|
||||
*/
|
||||
cfg.top_level = (hv_iommu_device->max_iova_width > 48) ? 4 : 3;
|
||||
|
||||
ret = pt_iommu_x86_64_init(&hv_domain->pt_iommu_x86_64, &cfg, GFP_KERNEL);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
/* Constrain to page sizes the hypervisor supports */
|
||||
hv_domain->domain.pgsize_bitmap &= hv_iommu_device->pgsize_bitmap;
|
||||
|
||||
hv_domain->domain.ops = &hv_iommu_paging_domain_ops;
|
||||
|
||||
pt_iommu_x86_64_hw_info(&hv_domain->pt_iommu_x86_64, &pt_info);
|
||||
settings.page_table_root = pt_info.gcr3_pt;
|
||||
settings.flags.first_stage_paging_mode = pt_info.levels == 5;
|
||||
|
||||
ret = hv_create_configure_device_domain(hv_domain, &settings);
|
||||
if (ret)
|
||||
goto err_pt_deinit;
|
||||
|
||||
return &hv_domain->domain;
|
||||
|
||||
err_pt_deinit:
|
||||
pt_iommu_deinit(&hv_domain->pt_iommu);
|
||||
err_free:
|
||||
kfree(hv_domain);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static struct iommu_ops hv_iommu_ops = {
|
||||
.capable = hv_iommu_capable,
|
||||
.domain_alloc_paging = hv_iommu_domain_alloc_paging,
|
||||
.probe_device = hv_iommu_probe_device,
|
||||
.device_group = hv_iommu_device_group,
|
||||
.get_resv_regions = hv_iommu_get_resv_regions,
|
||||
.owner = THIS_MODULE,
|
||||
.identity_domain = &hv_identity_domain.domain,
|
||||
.blocked_domain = &hv_blocking_domain.domain,
|
||||
.release_domain = &hv_blocking_domain.domain,
|
||||
};
|
||||
|
||||
static u64 hv_iommu_detect(struct hv_output_get_iommu_capabilities *cap)
|
||||
{
|
||||
u64 status;
|
||||
unsigned long flags;
|
||||
struct hv_input_get_iommu_capabilities *input;
|
||||
struct hv_output_get_iommu_capabilities *output;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
/*
|
||||
* The combined input and output fit in the per-CPU input page,
|
||||
* so no separate output page is needed.
|
||||
*/
|
||||
input = *this_cpu_ptr(hyperv_pcpu_input_arg);
|
||||
output = (struct hv_output_get_iommu_capabilities *)(input + 1);
|
||||
memset(input, 0, sizeof(*input));
|
||||
input->partition_id = HV_PARTITION_ID_SELF;
|
||||
status = hv_do_hypercall(HVCALL_GET_IOMMU_CAPABILITIES, input, output);
|
||||
*cap = *output;
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void __init
|
||||
hv_init_iommu_device(struct hv_iommu_dev *hv_iommu,
|
||||
struct hv_output_get_iommu_capabilities *hv_iommu_cap)
|
||||
{
|
||||
ida_init(&hv_iommu->domain_ids);
|
||||
|
||||
hv_iommu->cap = hv_iommu_cap->iommu_cap;
|
||||
hv_iommu->max_iova_width = hv_iommu_cap->max_iova_width;
|
||||
if (!hv_iommu_5lvl_supported(hv_iommu->cap) &&
|
||||
hv_iommu->max_iova_width > 48) {
|
||||
pr_info("5-level paging not supported, limiting iova width to 48.\n");
|
||||
hv_iommu->max_iova_width = 48;
|
||||
}
|
||||
|
||||
hv_iommu->first_domain = HV_DEVICE_DOMAIN_ID_DEFAULT + 1;
|
||||
hv_iommu->last_domain = HV_DEVICE_DOMAIN_ID_NULL - 1;
|
||||
hv_iommu->pgsize_bitmap = hv_iommu_cap->pgsize_bitmap;
|
||||
hv_iommu_device = hv_iommu;
|
||||
}
|
||||
|
||||
int __init hv_iommu_init(void)
|
||||
{
|
||||
u64 status;
|
||||
int ret = 0;
|
||||
struct hv_iommu_dev *hv_iommu = NULL;
|
||||
struct hv_output_get_iommu_capabilities hv_iommu_cap = {0};
|
||||
|
||||
if (no_iommu || iommu_detected)
|
||||
return -ENODEV;
|
||||
|
||||
if (!hv_is_hyperv_initialized())
|
||||
return -ENODEV;
|
||||
|
||||
status = hv_iommu_detect(&hv_iommu_cap);
|
||||
if (!hv_result_success(status)) {
|
||||
if (hv_result(status) == HV_STATUS_INVALID_HYPERCALL_CODE)
|
||||
return -ENODEV;
|
||||
|
||||
hv_status_err(status, "HVCALL_GET_IOMMU_CAPABILITIES failed\n");
|
||||
return hv_result_to_errno(status);
|
||||
}
|
||||
|
||||
if (!hv_iommu_present(hv_iommu_cap.iommu_cap))
|
||||
return -ENODEV;
|
||||
|
||||
if (!hv_iommu_s1_domain_supported(hv_iommu_cap.iommu_cap)) {
|
||||
pr_err("stage-1 translation not supported: cap=%#llx\n",
|
||||
hv_iommu_cap.iommu_cap);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Require the base page size. The domain page-size bitmap is later
|
||||
* restricted to the sizes supported by both iommupt and Hyper-V.
|
||||
*/
|
||||
if (!(hv_iommu_cap.pgsize_bitmap & PAGE_SIZE)) {
|
||||
pr_err("unsupported page sizes: pgsize_bitmap=0x%llx\n",
|
||||
hv_iommu_cap.pgsize_bitmap);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
iommu_detected = 1;
|
||||
pci_request_acs();
|
||||
|
||||
hv_iommu = kzalloc_obj(*hv_iommu, GFP_KERNEL);
|
||||
if (!hv_iommu)
|
||||
return -ENOMEM;
|
||||
|
||||
hv_init_iommu_device(hv_iommu, &hv_iommu_cap);
|
||||
|
||||
ret = hv_initialize_static_domains();
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
ret = iommu_device_sysfs_add(&hv_iommu->iommu, NULL, NULL, "%s", "hv-iommu");
|
||||
if (ret) {
|
||||
pr_err("iommu_device_sysfs_add failed: %d\n", ret);
|
||||
goto err_delete_static_domains;
|
||||
}
|
||||
|
||||
ret = iommu_device_register(&hv_iommu->iommu, &hv_iommu_ops, NULL);
|
||||
if (ret) {
|
||||
pr_err("iommu_device_register failed: %d\n", ret);
|
||||
goto err_sysfs_remove;
|
||||
}
|
||||
|
||||
pr_info("initialized: %u-bit IOVA aperture, page-size bitmap %#llx\n",
|
||||
hv_iommu->max_iova_width, hv_iommu->pgsize_bitmap);
|
||||
return 0;
|
||||
|
||||
err_sysfs_remove:
|
||||
iommu_device_sysfs_remove(&hv_iommu->iommu);
|
||||
err_delete_static_domains:
|
||||
hv_delete_device_domain(&hv_blocking_domain);
|
||||
hv_delete_device_domain(&hv_identity_domain);
|
||||
err_free:
|
||||
kfree(hv_iommu);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* Hyper-V IOMMU driver.
|
||||
*
|
||||
* Copyright (C) 2024-2026, Microsoft, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HYPERV_IOMMU_H
|
||||
#define _HYPERV_IOMMU_H
|
||||
|
||||
struct hv_iommu_dev {
|
||||
struct iommu_device iommu;
|
||||
struct ida domain_ids;
|
||||
|
||||
/* Device configuration */
|
||||
u8 max_iova_width;
|
||||
u8 max_pasid_width;
|
||||
u64 cap;
|
||||
u64 pgsize_bitmap;
|
||||
|
||||
u64 first_domain;
|
||||
u64 last_domain;
|
||||
};
|
||||
|
||||
struct hv_iommu_domain {
|
||||
union {
|
||||
struct iommu_domain domain;
|
||||
struct pt_iommu pt_iommu;
|
||||
struct pt_iommu_x86_64 pt_iommu_x86_64;
|
||||
};
|
||||
struct hv_iommu_dev *hv_iommu;
|
||||
struct hv_input_device_domain device_domain;
|
||||
u64 pgsize_bitmap;
|
||||
};
|
||||
|
||||
PT_IOMMU_CHECK_DOMAIN(struct hv_iommu_domain, pt_iommu, domain);
|
||||
PT_IOMMU_CHECK_DOMAIN(struct hv_iommu_domain, pt_iommu_x86_64.iommu, domain);
|
||||
|
||||
#define to_hv_iommu_domain(d) \
|
||||
container_of(d, struct hv_iommu_domain, domain)
|
||||
|
||||
#endif /* _HYPERV_IOMMU_H */
|
||||
+60
-10
@@ -1344,6 +1344,49 @@ static void qi_dump_fault(struct intel_iommu *iommu, u32 fault)
|
||||
(unsigned long long)desc->qw1);
|
||||
}
|
||||
|
||||
static int qi_drain_remaining_descs(struct intel_iommu *iommu, int head,
|
||||
int wait_index)
|
||||
{
|
||||
struct q_inval *qi = iommu->qi;
|
||||
int shift = qi_shift(iommu);
|
||||
struct qi_desc wait_desc = {};
|
||||
struct qi_desc nop_desc = {};
|
||||
cycles_t start;
|
||||
|
||||
nop_desc.qw0 = QI_IWD_FENCE | QI_IWD_TYPE;
|
||||
|
||||
wait_desc.qw0 = QI_IWD_STATUS_DATA(QI_DONE) | QI_IWD_PRQ_DRAIN |
|
||||
QI_IWD_STATUS_WRITE | QI_IWD_FENCE | QI_IWD_TYPE;
|
||||
wait_desc.qw1 = virt_to_phys(&qi->desc_status[wait_index]);
|
||||
|
||||
while (head != wait_index) {
|
||||
memcpy(qi->desc + (head << shift), &nop_desc, 1 << shift);
|
||||
head = (head + 1) % QI_LENGTH;
|
||||
}
|
||||
|
||||
WRITE_ONCE(qi->desc_status[wait_index], QI_IN_USE);
|
||||
memcpy(qi->desc + (wait_index << shift), &wait_desc, 1 << shift);
|
||||
|
||||
/*
|
||||
* Order the descriptor rewrites before the writel() that restarts
|
||||
* the fetch engine.
|
||||
*/
|
||||
wmb();
|
||||
|
||||
writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
|
||||
|
||||
start = get_cycles();
|
||||
while (READ_ONCE(qi->desc_status[wait_index]) != QI_DONE) {
|
||||
if (DMAR_OPERATION_TIMEOUT < (get_cycles() - start)) {
|
||||
pr_err("Timeout draining invalidation queue after IQE\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
|
||||
{
|
||||
u32 fault;
|
||||
@@ -1366,18 +1409,25 @@ static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
|
||||
* is cleared.
|
||||
*/
|
||||
if (fault & DMA_FSTS_IQE) {
|
||||
int head_idx, ret;
|
||||
|
||||
head = readl(iommu->reg + DMAR_IQH_REG);
|
||||
if ((head >> shift) == index) {
|
||||
struct qi_desc *desc = qi->desc + head;
|
||||
head_idx = (head >> shift) % QI_LENGTH;
|
||||
|
||||
/*
|
||||
* The faulting descriptor can be anywhere within the current
|
||||
* submission's range [index, wait_index]. Since the queue is
|
||||
* circular, this submission may wrap around QI_LENGTH
|
||||
* (index > wait_index in that case), so check both the
|
||||
* non-wrapped and wrapped cases of the range.
|
||||
*/
|
||||
if (index <= wait_index ?
|
||||
(head_idx >= index && head_idx <= wait_index) :
|
||||
(head_idx >= index || head_idx <= wait_index)) {
|
||||
ret = qi_drain_remaining_descs(iommu, head_idx, wait_index);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* desc->qw2 and desc->qw3 are either reserved or
|
||||
* used by software as private data. We won't print
|
||||
* out these two qw's for security consideration.
|
||||
*/
|
||||
memcpy(desc, qi->desc + (wait_index << shift),
|
||||
1 << shift);
|
||||
writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
|
||||
pr_info("Invalidation Queue Error (IQE) cleared\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
+138
-29
@@ -39,8 +39,6 @@
|
||||
#define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA)
|
||||
#define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e)
|
||||
|
||||
#define IOAPIC_RANGE_START (0xfee00000)
|
||||
#define IOAPIC_RANGE_END (0xfeefffff)
|
||||
#define IOVA_START_ADDR (0x1000)
|
||||
|
||||
#define DEFAULT_DOMAIN_ADDRESS_WIDTH 57
|
||||
@@ -1452,12 +1450,103 @@ static void intel_iommu_init_qi(struct intel_iommu *iommu)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reserve a domain ID inherited from the previous kernel so that it is not
|
||||
* handed out again while the copied translation structures are still live.
|
||||
*
|
||||
* Returns 0 when the ID is reserved, was already reserved, or cannot be
|
||||
* re-assigned, and a negative errno for a genuine allocation failure.
|
||||
*/
|
||||
static int reserve_domain_id(struct intel_iommu *iommu, int did)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (did < 0 || did >= iommu->max_domain_id)
|
||||
return 0;
|
||||
|
||||
ret = ida_alloc_range(&iommu->domain_ida, did, did, GFP_KERNEL);
|
||||
/*
|
||||
* Devices sharing a domain share its ID, so the same ID is seen in
|
||||
* more than one context entry; -ENOSPC merely reports that it is
|
||||
* already reserved. On success the allocated ID is returned, which
|
||||
* is not an error either.
|
||||
*/
|
||||
if (ret == -ENOSPC || ret >= 0)
|
||||
return 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reserve the domain IDs used by a scalable mode context entry copied from
|
||||
* the previous kernel.
|
||||
*/
|
||||
static int copy_pasid_table_dids(struct intel_iommu *iommu, struct context_entry *ce)
|
||||
{
|
||||
struct pasid_dir_entry *dir;
|
||||
unsigned long dir_size;
|
||||
phys_addr_t dir_phys;
|
||||
int ret = 0;
|
||||
int i, j;
|
||||
|
||||
dir_phys = ce->lo & VTD_PAGE_MASK;
|
||||
if (!dir_phys)
|
||||
return 0;
|
||||
|
||||
dir_size = get_pasid_dir_size(ce);
|
||||
dir = memremap(dir_phys, dir_size * sizeof(*dir), MEMREMAP_WB);
|
||||
if (!dir)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < dir_size; i++) {
|
||||
struct pasid_entry *table;
|
||||
phys_addr_t table_phys;
|
||||
|
||||
if (!pasid_pde_is_present(&dir[i]))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Do not use get_pasid_table_from_pde(); that returns a
|
||||
* phys_to_virt() pointer, which is not valid for memory
|
||||
* owned by the previous kernel.
|
||||
*/
|
||||
table_phys = READ_ONCE(dir[i].val) & PDE_PFN_MASK;
|
||||
if (!table_phys)
|
||||
continue;
|
||||
|
||||
/* A PASID table is one page: PASID_TBL_ENTRIES * 64 bytes. */
|
||||
table = memremap(table_phys, PAGE_SIZE, MEMREMAP_WB);
|
||||
if (!table) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (j = 0; j < PASID_TBL_ENTRIES; j++) {
|
||||
if (!pasid_pte_is_present(&table[j]))
|
||||
continue;
|
||||
|
||||
ret = reserve_domain_id(iommu, pasid_get_domain_id(&table[j]));
|
||||
if (ret) {
|
||||
memunmap(table);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
memunmap(table);
|
||||
}
|
||||
|
||||
out:
|
||||
memunmap(dir);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int copy_context_table(struct intel_iommu *iommu,
|
||||
struct root_entry *old_re,
|
||||
struct context_entry **tbl,
|
||||
int bus, bool ext)
|
||||
{
|
||||
int tbl_idx, tbl_slot = 0, idx, devfn, ret = 0, did;
|
||||
int tbl_idx, tbl_slot = 0, idx, devfn, ret = 0;
|
||||
struct context_entry *new_ce = NULL, ce;
|
||||
struct context_entry *old_ce = NULL;
|
||||
struct root_entry re;
|
||||
@@ -1519,10 +1608,20 @@ static int copy_context_table(struct intel_iommu *iommu,
|
||||
|
||||
if (!context_present(&ce))
|
||||
continue;
|
||||
|
||||
did = context_domain_id(&ce);
|
||||
if (did >= 0 && did < iommu->max_domain_id)
|
||||
ida_alloc_range(&iommu->domain_ida, did, did, GFP_KERNEL);
|
||||
/*
|
||||
* The context entry only holds a domain ID in legacy mode.
|
||||
* In scalable mode the IDs are in the PASID table entries.
|
||||
*/
|
||||
if (ext)
|
||||
ret = copy_pasid_table_dids(iommu, &ce);
|
||||
else
|
||||
ret = reserve_domain_id(iommu, context_domain_id(&ce));
|
||||
if (ret) {
|
||||
/* Not yet published through @tbl, so free it here. */
|
||||
iommu_free_pages(new_ce);
|
||||
new_ce = NULL;
|
||||
goto out_unmap;
|
||||
}
|
||||
|
||||
set_context_copied(iommu, bus, devfn);
|
||||
new_ce[idx] = ce;
|
||||
@@ -1591,7 +1690,7 @@ static int copy_translation_tables(struct intel_iommu *iommu)
|
||||
if (ret) {
|
||||
pr_err("%s: Failed to copy context table for bus %d\n",
|
||||
iommu->name, bus);
|
||||
continue;
|
||||
goto err_free_ctxt_tbls;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1623,11 +1722,27 @@ static int copy_translation_tables(struct intel_iommu *iommu)
|
||||
memunmap(old_rt);
|
||||
return 0;
|
||||
|
||||
err_free_ctxt_tbls:
|
||||
/*
|
||||
* None of these tables have been linked into iommu->root_entry yet,
|
||||
* so they are unreachable and must be freed here.
|
||||
*/
|
||||
for (bus = 0; bus < ctxt_table_entries; bus++)
|
||||
iommu_free_pages(ctxt_tbls[bus]);
|
||||
kfree(ctxt_tbls);
|
||||
out_unmap:
|
||||
memunmap(old_rt);
|
||||
err_free_bitmap:
|
||||
bitmap_free(iommu->copied_tables);
|
||||
iommu->copied_tables = NULL;
|
||||
|
||||
/*
|
||||
* Only reservations taken from the old context entries can be in the
|
||||
* ida at this point; no domain has been allocated on this IOMMU yet.
|
||||
* ida_destroy() empties it and leaves it ready for reuse.
|
||||
*/
|
||||
ida_destroy(&iommu->domain_ida);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -2782,9 +2897,7 @@ static int blocking_domain_attach_dev(struct iommu_domain *domain,
|
||||
struct device *dev,
|
||||
struct iommu_domain *old)
|
||||
{
|
||||
struct device_domain_info *info = dev_iommu_priv_get(dev);
|
||||
|
||||
iopf_for_domain_remove(info->domain ? &info->domain->domain : NULL, dev);
|
||||
iopf_for_domain_remove(old, dev);
|
||||
device_block_translation(dev);
|
||||
return 0;
|
||||
}
|
||||
@@ -2911,10 +3024,10 @@ static unsigned int compute_vasz_lg2_ss(struct intel_iommu *iommu,
|
||||
*top_level = 4;
|
||||
return min(57, mgaw);
|
||||
} else if (mgaw > 39 && sagaw >= BIT(2)) {
|
||||
*top_level = 3 + ffs(sagaw >> 3);
|
||||
*top_level = 2 + ffs(sagaw >> 2);
|
||||
return min(48, mgaw);
|
||||
} else if (mgaw > 30 && sagaw >= BIT(1)) {
|
||||
*top_level = 2 + ffs(sagaw >> 2);
|
||||
*top_level = 1 + ffs(sagaw >> 1);
|
||||
return min(39, mgaw);
|
||||
}
|
||||
return 0;
|
||||
@@ -3412,7 +3525,6 @@ static void intel_iommu_get_resv_regions(struct device *device,
|
||||
struct list_head *head)
|
||||
{
|
||||
int prot = DMA_PTE_READ | DMA_PTE_WRITE;
|
||||
struct iommu_resv_region *reg;
|
||||
struct dmar_rmrr_unit *rmrr;
|
||||
struct device *i_dev;
|
||||
int i;
|
||||
@@ -3448,6 +3560,7 @@ static void intel_iommu_get_resv_regions(struct device *device,
|
||||
#ifdef CONFIG_INTEL_IOMMU_FLOPPY_WA
|
||||
if (dev_is_pci(device)) {
|
||||
struct pci_dev *pdev = to_pci_dev(device);
|
||||
struct iommu_resv_region *reg;
|
||||
|
||||
if ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) {
|
||||
reg = iommu_alloc_resv_region(0, 1UL << 24, prot,
|
||||
@@ -3459,12 +3572,7 @@ static void intel_iommu_get_resv_regions(struct device *device,
|
||||
}
|
||||
#endif /* CONFIG_INTEL_IOMMU_FLOPPY_WA */
|
||||
|
||||
reg = iommu_alloc_resv_region(IOAPIC_RANGE_START,
|
||||
IOAPIC_RANGE_END - IOAPIC_RANGE_START + 1,
|
||||
0, IOMMU_RESV_MSI, GFP_KERNEL);
|
||||
if (!reg)
|
||||
return;
|
||||
list_add_tail(®->list, head);
|
||||
iommu_add_resv_x86_msi_region(head);
|
||||
}
|
||||
|
||||
static struct iommu_group *intel_iommu_device_group(struct device *dev)
|
||||
@@ -3870,6 +3978,14 @@ static int identity_domain_attach_dev(struct iommu_domain *domain,
|
||||
if (dev_is_real_dma_subdevice(dev))
|
||||
return 0;
|
||||
|
||||
if (sm_supported(iommu))
|
||||
ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID);
|
||||
else
|
||||
ret = device_setup_pass_through(dev);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The identity domain has no iopf_handler, so no IOPF reference is
|
||||
* taken for it. The reference held by the old domain must still be
|
||||
@@ -3877,16 +3993,9 @@ static int identity_domain_attach_dev(struct iommu_domain *domain,
|
||||
* not affect the IOPF reference count.
|
||||
*/
|
||||
iopf_for_domain_remove(old, dev);
|
||||
info->domain_attached = true;
|
||||
|
||||
if (sm_supported(iommu))
|
||||
ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID);
|
||||
else
|
||||
ret = device_setup_pass_through(dev);
|
||||
|
||||
if (!ret)
|
||||
info->domain_attached = true;
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int identity_domain_set_dev_pasid(struct iommu_domain *domain,
|
||||
|
||||
@@ -1105,12 +1105,20 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
|
||||
unsigned int size_order,
|
||||
struct qi_desc *desc)
|
||||
{
|
||||
unsigned long mask = 1UL << (VTD_PAGE_SHIFT + size_order - 1);
|
||||
|
||||
desc->qw0 = QI_DEV_EIOTLB_PASID(pasid) | QI_DEV_EIOTLB_SID(sid) |
|
||||
QI_DEV_EIOTLB_QDEP(qdep) | QI_DEIOTLB_TYPE |
|
||||
QI_DEV_IOTLB_PFSID(pfsid);
|
||||
|
||||
/*
|
||||
* The widest range the descriptor can express is a full flush, encoded
|
||||
* by making bit 63 the least significant zero bit of ADDR, that is
|
||||
* @size_order == 64 - VTD_PAGE_SHIFT. Bound @size_order there so that
|
||||
* a caller passing something larger cannot produce an out-of-range
|
||||
* shift below.
|
||||
*/
|
||||
if (size_order > 64 - VTD_PAGE_SHIFT)
|
||||
size_order = 64 - VTD_PAGE_SHIFT;
|
||||
|
||||
/*
|
||||
* If S bit is 0, we only flush a single page. If S bit is set,
|
||||
* The least significant zero bit indicates the invalidation address
|
||||
@@ -1120,7 +1128,8 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
|
||||
* Max Invs Pending (MIP) is set to 0 for now until we have DIT in
|
||||
* ECAP.
|
||||
*/
|
||||
if (!IS_ALIGNED(addr, VTD_PAGE_SIZE << size_order))
|
||||
if (size_order < 64 - VTD_PAGE_SHIFT &&
|
||||
!IS_ALIGNED(addr, BIT_ULL(VTD_PAGE_SHIFT + size_order)))
|
||||
pr_warn_ratelimited("Invalidate non-aligned address %llx, order %d\n",
|
||||
addr, size_order);
|
||||
|
||||
@@ -1136,7 +1145,7 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
|
||||
desc->qw1 |= GENMASK_ULL(size_order + VTD_PAGE_SHIFT - 1,
|
||||
VTD_PAGE_SHIFT);
|
||||
/* Clear size_order bit to indicate size */
|
||||
desc->qw1 &= ~mask;
|
||||
desc->qw1 &= ~BIT_ULL(VTD_PAGE_SHIFT + size_order - 1);
|
||||
/* Set the S bit to indicate flushing more than 1 page */
|
||||
desc->qw1 |= QI_DEV_EIOTLB_SIZE;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ static int intel_nested_attach_dev(struct iommu_domain *domain,
|
||||
if (ret)
|
||||
goto disable_iopf;
|
||||
|
||||
iopf_for_domain_remove(old, dev);
|
||||
|
||||
info->domain = dmar_domain;
|
||||
info->domain_attached = true;
|
||||
spin_lock_irqsave(&dmar_domain->lock, flags);
|
||||
|
||||
@@ -30,6 +30,10 @@ static inline const struct iommu_ops *iommu_fwspec_ops(struct iommu_fwspec *fwsp
|
||||
|
||||
void iommu_fwspec_free(struct device *dev);
|
||||
|
||||
int iommu_deferred_attach(struct device *dev, struct iommu_domain *domain);
|
||||
|
||||
struct iommu_domain *iommu_get_dma_domain(struct device *dev);
|
||||
|
||||
int iommu_device_register_bus(struct iommu_device *iommu,
|
||||
const struct iommu_ops *ops,
|
||||
const struct bus_type *bus,
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "iommu-priv.h"
|
||||
|
||||
static DEFINE_MUTEX(iommu_sva_lock);
|
||||
static bool iommu_sva_present;
|
||||
static LIST_HEAD(iommu_sva_mms);
|
||||
static struct iommu_domain *iommu_sva_domain_alloc(struct device *dev,
|
||||
struct mm_struct *mm);
|
||||
@@ -137,11 +136,9 @@ struct iommu_sva *iommu_sva_bind_device(struct device *dev, struct mm_struct *mm
|
||||
goto out_free_domain;
|
||||
domain->users = 1;
|
||||
|
||||
if (list_empty(&iommu_mm->sva_domains)) {
|
||||
if (list_empty(&iommu_sva_mms))
|
||||
iommu_sva_present = true;
|
||||
if (list_empty(&iommu_mm->sva_domains))
|
||||
list_add(&iommu_mm->mm_list_elm, &iommu_sva_mms);
|
||||
}
|
||||
|
||||
list_add(&domain->next, &iommu_mm->sva_domains);
|
||||
out:
|
||||
refcount_set(&handle->users, 1);
|
||||
@@ -182,11 +179,8 @@ void iommu_sva_unbind_device(struct iommu_sva *handle)
|
||||
iommu_detach_device_pasid(domain, dev, iommu_mm->pasid);
|
||||
if (--domain->users == 0) {
|
||||
list_del(&domain->next);
|
||||
if (list_empty(&iommu_mm->sva_domains)) {
|
||||
if (list_empty(&iommu_mm->sva_domains))
|
||||
list_del(&iommu_mm->mm_list_elm);
|
||||
if (list_empty(&iommu_sva_mms))
|
||||
iommu_sva_present = false;
|
||||
}
|
||||
|
||||
iommu_domain_free(domain);
|
||||
}
|
||||
@@ -334,8 +328,6 @@ void iommu_sva_invalidate_kva_range(unsigned long start, unsigned long end)
|
||||
struct iommu_mm_data *iommu_mm;
|
||||
|
||||
guard(mutex)(&iommu_sva_lock);
|
||||
if (!iommu_sva_present)
|
||||
return;
|
||||
|
||||
list_for_each_entry(iommu_mm, &iommu_sva_mms, mm_list_elm)
|
||||
mmu_notifier_arch_invalidate_secondary_tlbs(iommu_mm->mm, start, end);
|
||||
|
||||
@@ -4243,6 +4243,10 @@ int iommu_dma_prepare_msi(struct msi_desc *desc, phys_addr_t msi_addr)
|
||||
if (!group)
|
||||
return 0;
|
||||
|
||||
ret = iommu_deferred_attach(dev, iommu_get_domain_for_dev(dev));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&group->mutex);
|
||||
/* An IDENTITY domain must pass through */
|
||||
if (group->domain && group->domain->type != IOMMU_DOMAIN_IDENTITY) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <linux/acpi_rimt.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/crash_dump.h>
|
||||
#include <linux/dma-map-ops.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/iopoll.h>
|
||||
@@ -1444,6 +1445,16 @@ static struct iommu_group *riscv_iommu_device_group(struct device *dev)
|
||||
return generic_device_group(dev);
|
||||
}
|
||||
|
||||
static bool riscv_iommu_capable(struct device *dev, enum iommu_cap cap)
|
||||
{
|
||||
switch (cap) {
|
||||
case IOMMU_CAP_CACHE_COHERENCY:
|
||||
return dev_is_dma_coherent(dev);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int riscv_iommu_of_xlate(struct device *dev, const struct of_phandle_args *args)
|
||||
{
|
||||
return iommu_fwspec_add_ids(dev, args->args, 1);
|
||||
@@ -1505,6 +1516,7 @@ static void riscv_iommu_release_device(struct device *dev)
|
||||
|
||||
static const struct iommu_ops riscv_iommu_ops = {
|
||||
.of_xlate = riscv_iommu_of_xlate,
|
||||
.capable = riscv_iommu_capable,
|
||||
.identity_domain = &riscv_iommu_identity_domain,
|
||||
.blocked_domain = &riscv_iommu_blocking_domain,
|
||||
.release_domain = &riscv_iommu_blocking_domain,
|
||||
|
||||
@@ -641,10 +641,7 @@ static void hv_irq_retarget_interrupt(struct irq_data *data)
|
||||
params->int_entry.source = HV_INTERRUPT_SOURCE_MSI;
|
||||
params->int_entry.msi_entry.address.as_uint32 = int_desc->address & 0xffffffff;
|
||||
params->int_entry.msi_entry.data.as_uint32 = int_desc->data;
|
||||
params->device_id = (hbus->hdev->dev_instance.b[5] << 24) |
|
||||
(hbus->hdev->dev_instance.b[4] << 16) |
|
||||
(hbus->hdev->dev_instance.b[7] << 8) |
|
||||
(hbus->hdev->dev_instance.b[6] & 0xf8) |
|
||||
params->device_id = hv_build_logical_dev_id_prefix(hbus->hdev) |
|
||||
PCI_FUNC(pdev->devfn);
|
||||
params->int_target.vector = hv_msi_get_int_vector(data);
|
||||
|
||||
@@ -3742,6 +3739,7 @@ static int hv_pci_probe(struct hv_device *hdev,
|
||||
struct hv_pcibus_device *hbus;
|
||||
int ret, dom;
|
||||
u16 dom_req;
|
||||
u32 prefix;
|
||||
char *name;
|
||||
|
||||
bridge = devm_pci_alloc_host_bridge(&hdev->device, 0);
|
||||
@@ -3884,13 +3882,22 @@ static int hv_pci_probe(struct hv_device *hdev,
|
||||
|
||||
hbus->state = hv_pcibus_probed;
|
||||
|
||||
ret = create_root_hv_pci_bus(hbus);
|
||||
/* Register the bus before scanning any devices on it. */
|
||||
prefix = hv_build_logical_dev_id_prefix(hdev);
|
||||
|
||||
ret = hv_pci_register_dev_id(dom, prefix);
|
||||
if (ret)
|
||||
goto free_windows;
|
||||
|
||||
ret = create_root_hv_pci_bus(hbus);
|
||||
if (ret)
|
||||
goto unregister_pci_bus;
|
||||
|
||||
mutex_unlock(&hbus->state_lock);
|
||||
return 0;
|
||||
|
||||
unregister_pci_bus:
|
||||
hv_pci_unregister_dev_id(dom);
|
||||
free_windows:
|
||||
hv_pci_free_bridge_windows(hbus);
|
||||
exit_d0:
|
||||
@@ -4004,6 +4011,8 @@ static void hv_pci_remove(struct hv_device *hdev)
|
||||
|
||||
hbus = hv_get_drvdata(hdev);
|
||||
if (hbus->state == hv_pcibus_installed) {
|
||||
int dom = hbus->bridge->domain_nr;
|
||||
|
||||
tasklet_disable(&hdev->channel->callback_event);
|
||||
hbus->state = hv_pcibus_removing;
|
||||
tasklet_enable(&hdev->channel->callback_event);
|
||||
@@ -4021,6 +4030,8 @@ static void hv_pci_remove(struct hv_device *hdev)
|
||||
hv_pci_remove_slots(hbus);
|
||||
pci_remove_root_bus(hbus->bridge->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
hv_pci_unregister_dev_id(dom);
|
||||
}
|
||||
|
||||
hv_pci_bus_exit(hdev, false);
|
||||
|
||||
@@ -73,6 +73,10 @@ extern enum hv_partition_type hv_curr_partition_type;
|
||||
extern void * __percpu *hyperv_pcpu_input_arg;
|
||||
extern void * __percpu *hyperv_pcpu_output_arg;
|
||||
|
||||
int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix);
|
||||
void hv_pci_unregister_dev_id(int pci_domain_nr);
|
||||
int hv_pci_lookup_dev_id(int pci_domain_nr, u32 *prefix);
|
||||
|
||||
u64 hv_do_hypercall(u64 control, void *inputaddr, void *outputaddr);
|
||||
u64 hv_do_fast_hypercall8(u16 control, u64 input8);
|
||||
u64 hv_do_fast_hypercall16(u16 control, u64 input1, u64 input2);
|
||||
|
||||
@@ -37,6 +37,7 @@ struct hv_u128 {
|
||||
#define HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED 0x20
|
||||
#define HV_STATUS_INVALID_LP_INDEX 0x41
|
||||
#define HV_STATUS_INVALID_REGISTER_VALUE 0x50
|
||||
#define HV_STATUS_DEVICE_ALREADY_IN_DOMAIN 0x66
|
||||
#define HV_STATUS_OPERATION_FAILED 0x71
|
||||
#define HV_STATUS_INSUFFICIENT_ROOT_MEMORY 0x73
|
||||
#define HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY 0x75
|
||||
@@ -486,10 +487,17 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
|
||||
#define HVCALL_GET_VP_INDEX_FROM_APIC_ID 0x009a
|
||||
#define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_SPACE 0x00af
|
||||
#define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_LIST 0x00b0
|
||||
#define HVCALL_CREATE_DEVICE_DOMAIN 0x00b1
|
||||
#define HVCALL_ATTACH_DEVICE_DOMAIN 0x00b2
|
||||
#define HVCALL_SIGNAL_EVENT_DIRECT 0x00c0
|
||||
#define HVCALL_POST_MESSAGE_DIRECT 0x00c1
|
||||
#define HVCALL_DISPATCH_VP 0x00c2
|
||||
#define HVCALL_DETACH_DEVICE_DOMAIN 0x00c4
|
||||
#define HVCALL_DELETE_DEVICE_DOMAIN 0x00c5
|
||||
#define HVCALL_GET_GPA_PAGES_ACCESS_STATES 0x00c9
|
||||
#define HVCALL_CONFIGURE_DEVICE_DOMAIN 0x00ce
|
||||
#define HVCALL_FLUSH_DEVICE_DOMAIN 0x00d0
|
||||
#define HVCALL_FLUSH_DEVICE_DOMAIN_LIST 0x00d1
|
||||
#define HVCALL_ACQUIRE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d7
|
||||
#define HVCALL_RELEASE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d8
|
||||
#define HVCALL_MODIFY_SPARSE_GPA_PAGE_HOST_VISIBILITY 0x00db
|
||||
@@ -502,6 +510,8 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
|
||||
#define HVCALL_MMIO_READ 0x0106
|
||||
#define HVCALL_MMIO_WRITE 0x0107
|
||||
#define HVCALL_DISABLE_HYP_EX 0x010f
|
||||
#define HVCALL_GET_IOMMU_CAPABILITIES 0x0125
|
||||
#define HVCALL_GET_LOGICAL_DEVICE_PROPERTY 0x0127
|
||||
#define HVCALL_MAP_STATS_PAGE2 0x0131
|
||||
|
||||
/* HV_HYPERCALL_INPUT */
|
||||
|
||||
@@ -548,4 +548,159 @@ union hv_device_id { /* HV_DEVICE_ID */
|
||||
} acpi;
|
||||
} __packed;
|
||||
|
||||
/* Device domain types */
|
||||
#define HV_DEVICE_DOMAIN_TYPE_S1 1 /* HV_DEVICE_DOMAIN_ID_TYPE_S1 */
|
||||
|
||||
/* ID for default domain and NULL domain */
|
||||
#define HV_DEVICE_DOMAIN_ID_DEFAULT 0
|
||||
#define HV_DEVICE_DOMAIN_ID_NULL 0xFFFFFFFFULL
|
||||
|
||||
union hv_device_domain_id { /* HV_DEVICE_DOMAIN_ID */
|
||||
u64 as_uint64;
|
||||
struct {
|
||||
u32 type: 4;
|
||||
u32 reserved: 28;
|
||||
u32 id;
|
||||
} __packed;
|
||||
};
|
||||
|
||||
struct hv_input_device_domain { /* HV_INPUT_DEVICE_DOMAIN */
|
||||
u64 partition_id;
|
||||
union hv_input_vtl owner_vtl;
|
||||
u8 padding[7];
|
||||
union hv_device_domain_id domain_id;
|
||||
} __packed;
|
||||
|
||||
/* HV_CREATE_DEVICE_DOMAIN_FLAGS */
|
||||
union hv_create_device_domain_flags {
|
||||
u32 as_uint32;
|
||||
struct {
|
||||
u32 forward_progress_required: 1;
|
||||
u32 inherit_owning_vtl: 1;
|
||||
u32 reserved: 30;
|
||||
} __packed;
|
||||
};
|
||||
|
||||
/* HV_INPUT_CREATE_DEVICE_DOMAIN */
|
||||
struct hv_input_create_device_domain {
|
||||
struct hv_input_device_domain device_domain;
|
||||
union hv_create_device_domain_flags create_device_domain_flags;
|
||||
u32 padding;
|
||||
} __packed;
|
||||
|
||||
/* HV_INPUT_DELETE_DEVICE_DOMAIN */
|
||||
struct hv_input_delete_device_domain {
|
||||
struct hv_input_device_domain device_domain;
|
||||
} __packed;
|
||||
|
||||
/* HV_INPUT_ATTACH_DEVICE_DOMAIN */
|
||||
struct hv_input_attach_device_domain {
|
||||
struct hv_input_device_domain device_domain;
|
||||
union hv_device_id device_id;
|
||||
} __packed;
|
||||
|
||||
/* HV_INPUT_DETACH_DEVICE_DOMAIN */
|
||||
struct hv_input_detach_device_domain {
|
||||
u64 partition_id;
|
||||
union hv_device_id device_id;
|
||||
} __packed;
|
||||
|
||||
struct hv_device_domain_settings { /* HV_DEVICE_DOMAIN_SETTINGS */
|
||||
struct {
|
||||
/*
|
||||
* Enable translations. If not enabled, all transaction bypass
|
||||
* S1 translations.
|
||||
*/
|
||||
u64 translation_enabled: 1;
|
||||
u64 blocked: 1;
|
||||
/*
|
||||
* First stage address translation paging mode:
|
||||
* 0: 4-level paging (default)
|
||||
* 1: 5-level paging
|
||||
*/
|
||||
u64 first_stage_paging_mode: 1;
|
||||
u64 reserved: 61;
|
||||
} flags;
|
||||
|
||||
/* Address of translation table */
|
||||
u64 page_table_root;
|
||||
} __packed;
|
||||
|
||||
/* HV_INPUT_CONFIGURE_DEVICE_DOMAIN */
|
||||
struct hv_input_configure_device_domain {
|
||||
struct hv_input_device_domain device_domain;
|
||||
struct hv_device_domain_settings settings;
|
||||
} __packed;
|
||||
|
||||
/* HV_INPUT_GET_IOMMU_CAPABILITIES */
|
||||
struct hv_input_get_iommu_capabilities {
|
||||
u64 partition_id;
|
||||
u64 reserved;
|
||||
} __packed;
|
||||
|
||||
/* HV_OUTPUT_GET_IOMMU_CAPABILITIES */
|
||||
struct hv_output_get_iommu_capabilities {
|
||||
u32 size;
|
||||
u16 reserved;
|
||||
u8 max_iova_width;
|
||||
u8 max_pasid_width;
|
||||
|
||||
#define HV_IOMMU_CAP_PRESENT BIT_ULL(0)
|
||||
#define HV_IOMMU_CAP_S2 BIT_ULL(1)
|
||||
#define HV_IOMMU_CAP_S1 BIT_ULL(2)
|
||||
#define HV_IOMMU_CAP_S1_5LVL BIT_ULL(3)
|
||||
#define HV_IOMMU_CAP_PASID BIT_ULL(4)
|
||||
#define HV_IOMMU_CAP_ATS BIT_ULL(5)
|
||||
#define HV_IOMMU_CAP_PRI BIT_ULL(6)
|
||||
|
||||
u64 iommu_cap;
|
||||
u64 pgsize_bitmap;
|
||||
} __packed;
|
||||
|
||||
/* HV_LOGICAL_DEVICE_PROPERTY_CODE */
|
||||
enum hv_logical_device_property_code {
|
||||
HV_LOGICAL_DEVICE_PROPERTY_PVIOMMU = 10,
|
||||
};
|
||||
|
||||
/* HV_INPUT_GET_LOGICAL_DEVICE_PROPERTY */
|
||||
struct hv_input_get_logical_device_property {
|
||||
u64 partition_id;
|
||||
u64 logical_device_id;
|
||||
/* Takes values from enum hv_logical_device_property_code. */
|
||||
u32 code;
|
||||
u32 reserved;
|
||||
} __packed;
|
||||
|
||||
/* HV_OUTPUT_GET_LOGICAL_DEVICE_PROPERTY */
|
||||
struct hv_output_get_logical_device_property {
|
||||
#define HV_DEVICE_IOMMU_ENABLED BIT_ULL(0)
|
||||
u64 device_iommu;
|
||||
u64 reserved;
|
||||
} __packed;
|
||||
|
||||
/* HV_INPUT_FLUSH_DEVICE_DOMAIN */
|
||||
struct hv_input_flush_device_domain {
|
||||
struct hv_input_device_domain device_domain;
|
||||
u32 flags;
|
||||
u32 reserved;
|
||||
} __packed;
|
||||
|
||||
union hv_iommu_flush_va { /* HV_IOMMU_GVA_RANGE */
|
||||
u64 as_uint64;
|
||||
struct {
|
||||
u64 page_mask_shift : 6;
|
||||
u64 reserved : 6;
|
||||
u64 page_number : 52;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
/* HV_INPUT_FLUSH_DEVICE_DOMAIN_LIST */
|
||||
struct hv_input_flush_device_domain_list {
|
||||
struct hv_input_device_domain device_domain;
|
||||
#define HV_FLUSH_DEVICE_DOMAIN_LIST_IOMMU_FORMAT BIT(0)
|
||||
u32 flags;
|
||||
u32 reserved;
|
||||
union hv_iommu_flush_va iova_list[];
|
||||
} __packed;
|
||||
|
||||
#endif /* _HV_HVHDK_MINI_H */
|
||||
@@ -11,11 +11,11 @@
|
||||
#include <linux/types.h>
|
||||
|
||||
struct amd_iommu;
|
||||
struct pci_dev;
|
||||
|
||||
#ifdef CONFIG_AMD_IOMMU
|
||||
|
||||
struct task_struct;
|
||||
struct pci_dev;
|
||||
|
||||
extern void amd_iommu_detect(void);
|
||||
|
||||
@@ -76,4 +76,15 @@ static inline int amd_iommu_snp_disable(void) { return 0; }
|
||||
static inline bool amd_iommu_sev_tio_supported(void) { return false; }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_AMD_IOMMU
|
||||
int amd_iommu_enable_perfopt(struct pci_dev *pdev);
|
||||
void amd_iommu_disable_perfopt(struct pci_dev *pdev);
|
||||
#else
|
||||
static inline int amd_iommu_enable_perfopt(struct pci_dev *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void amd_iommu_disable_perfopt(struct pci_dev *pdev) { }
|
||||
#endif
|
||||
|
||||
#endif /* _ASM_X86_AMD_IOMMU_H */
|
||||
@@ -213,4 +213,10 @@ enum {
|
||||
PT_FEAT_X86_64_AMD_ENCRYPT_TABLES = PT_FEAT_FMT_START,
|
||||
};
|
||||
|
||||
struct pt_bcm2712 {
|
||||
struct pt_common common;
|
||||
u8 bigpage_lg2;
|
||||
u8 superpage_lg2;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -346,6 +346,18 @@ struct pt_iommu_x86_64_hw_info {
|
||||
|
||||
IOMMU_FORMAT(x86_64, x86_64_pt);
|
||||
|
||||
struct pt_iommu_bcm2712_cfg {
|
||||
struct pt_iommu_cfg common;
|
||||
u8 bigpage_lg2;
|
||||
u8 superpage_lg2;
|
||||
};
|
||||
|
||||
struct pt_iommu_bcm2712_hw_info {
|
||||
phys_addr_t pt_base;
|
||||
};
|
||||
|
||||
IOMMU_FORMAT(bcm2712, bcm2712pt);
|
||||
|
||||
#undef IOMMU_PROTOTYPES
|
||||
#undef IOMMU_FORMAT
|
||||
#endif
|
||||
@@ -1299,6 +1299,14 @@ struct hv_device {
|
||||
#define device_to_hv_device(d) container_of_const(d, struct hv_device, device)
|
||||
#define drv_to_hv_drv(d) container_of_const(d, struct hv_driver, driver)
|
||||
|
||||
static inline u32 hv_build_logical_dev_id_prefix(struct hv_device *hdev)
|
||||
{
|
||||
return ((u32)hdev->dev_instance.b[5] << 24) |
|
||||
((u32)hdev->dev_instance.b[4] << 16) |
|
||||
((u32)hdev->dev_instance.b[7] << 8) |
|
||||
(hdev->dev_instance.b[6] & 0xf8u);
|
||||
}
|
||||
|
||||
static inline void hv_set_drvdata(struct hv_device *dev, void *data)
|
||||
{
|
||||
dev_set_drvdata(&dev->device, data);
|
||||
|
||||
@@ -900,7 +900,6 @@ int iommu_device_sysfs_add(struct iommu_device *iommu,
|
||||
void iommu_device_sysfs_remove(struct iommu_device *iommu);
|
||||
int iommu_device_link(struct iommu_device *iommu, struct device *link);
|
||||
void iommu_device_unlink(struct iommu_device *iommu, struct device *link);
|
||||
int iommu_deferred_attach(struct device *dev, struct iommu_domain *domain);
|
||||
|
||||
static inline struct iommu_device *dev_to_iommu_device(struct device *dev)
|
||||
{
|
||||
@@ -945,7 +944,6 @@ extern void iommu_detach_device(struct iommu_domain *domain,
|
||||
struct device *dev);
|
||||
extern struct iommu_domain *iommu_get_domain_for_dev(struct device *dev);
|
||||
struct iommu_domain *iommu_driver_get_domain_for_dev(struct device *dev);
|
||||
extern struct iommu_domain *iommu_get_dma_domain(struct device *dev);
|
||||
extern int iommu_map(struct iommu_domain *domain, unsigned long iova,
|
||||
phys_addr_t paddr, size_t size, int prot, gfp_t gfp);
|
||||
int iommu_map_nosync(struct iommu_domain *domain, unsigned long iova,
|
||||
|
||||
Reference in new issue
Block a user