Skip to content

feat(armv8/iommu): add SMMUv3 stage-2 driver - #382

Open
rabara wants to merge 1 commit into
bao-project:mainfrom
rabara:add-smmuv3-support
Open

feat(armv8/iommu): add SMMUv3 stage-2 driver#382
rabara wants to merge 1 commit into
bao-project:mainfrom
rabara:add-smmuv3-support

Conversation

@rabara

@rabara rabara commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Add a minimal ARM SMMUv3 (IHI 0070) driver for Armv8-A, providing stage-2-only DMA translation. The driver takes over the non-secure SMMU left enabled by firmware: it installs a linear stream table (all streams BYPASS by default so masters keep working with their incoming attributes), a command queue and an event queue, and promotes individual streams to stage-2 as VMs bind them. Faults are drained and logged via the event queue (wired SPI plus a cheap poll hook on the GIC IRQ path).

Driver selection is done at build time via SMMU_VERSION (default 2): arch/smmu.h resolves to smmuv2.h or smmuv3.h, and objects.mk builds the matching driver. Platforms opt into v3 with SMMU_VERSION:=3 in their platform.mk.

Verified working on Agilex5 SoCFPGA platform.

@joaopeixoto13 joaopeixoto13 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this. An SMMUv3 driver has been sitting in our "someday" pile for some time, so it's great to see a clean, focused driver actually land!

Did a close pass and two things I'd want sorted before moving on, and a couple of design questions that aren't blockers.

Functional issues

  1. Init-order race on the poll hook. smmu.evtq_prod gets published near the top of smmuv3_init(), right after the mem_alloc_map_dev(), but smmu.evtq isn't allocated and EVENTQ_BASE isn't programmed until much further down. smmuv3_poll_events() only checks evtq_prod != NULL and fires from gic_handle() on every core for every IRQ, so any IPI landing on a secondary CPU while the boot CPU is still mid-init will latch a stale PROD/CONS delta and walk smmu.evtq while it's still NULL. Cleanest fix is a ready flag flipped last, once SMMUEN is acked, with the poll hook gated on that instead of on the pointer. Left specifics inline.

  2. No SMMU TLBI on stage-2 remap. You issue TLBI_S12_VMALL at bind time, but nothing hangs off the stage-2 invalidation path (core/inc/tlb.h, the // TODO: inval iommu tlbs sites), so TLBI VMALLS12E1IS on the CPU side never reaches the SMMU's TLBs. Fine as long as every device mapping is static, which is almost certainly why it comes up clean, but the moment a bound VM's stage-2 gets touched the SMMU keeps hitting stale walk-cache/TLB entries and translating against the old tables. This PR is what finally makes those TODOs actionable, so it's the natural spot to hook SMMU invalidation into that path, or at least to spell out the static-mapping assumption in the docs.

Design questions (not blockers)

  • BYPASS-by-default vs abort-by-default. Any streamID without a group stays parked on a BYPASS STE, i.e. free-for-all DMA into any VM's PA space. Reasonable for a transparent firmware handoff, but it means the SMMU is acting as an opt-in translator rather than an isolation boundary: a master nobody enumerated sails through instead of getting terminated. Once a platform's streamID map is fully known you'd usually want the unbound default to be abort (invalid STE, faults land in the event queue). Any appetite for a build/per-platform knob to flip the default?

  • Single instance. One static struct smmuv3_priv smmu plus a single platform.arch.smmu bakes in one SMMU per SoC. Multi-SMMU parts are increasingly common, and a streamID is only unique within a given instance's stream table, so the config eventually needs a per-group "which SMMU" selector. Nothing to do here, just worth not boxing the interface in now.

Rest is smaller stuff and a few latent-but-not-yet-reachable bits, all inline. Thanks again for putting this up.

vaddr_t regs = mem_alloc_map_dev(&cpu()->as, SEC_HYP_GLOBAL, INVALID_VA,
platform.arch.smmu.base, NUM_PAGES(SMMUV3_PAGE1_EVENTQ_CONS + 4));
smmu.hw = (volatile struct smmuv3_hw*)regs;
smmu.evtq_prod = (volatile uint32_t*)(regs + SMMUV3_PAGE1_EVENTQ_PROD);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evtq_prod/evtq_cons are published early in smmuv3_init(), but smmu.evtq is allocated later and EVENTQ_BASE programmed later still. smmuv3_poll_events() (called from gic_handle() on every CPU, every IRQ) gates only on evtq_prod == NULL, so an IPI taken by another core during init can read a stale PROD/CONS pair, and dereference smmu.evtq while it's still NULL.

Suggest a dedicated ready flag published last. Sketch (spans three spots, so not a one-click suggestion):

/* in struct smmuv3_priv, alongside `coherent`: */
bool coherent;
bool ready;          /* set once init completes; gates the poll hook */
spinlock_t lock;

/* in smmuv3_poll_events(), replace the NULL gate: */
if (!smmu.ready) {
    return;
}

/* at the very end of smmuv3_init(), after SMMUEN is acked: */
smmu.ready = true;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agee - I will add ready flag along with release barrier before publishing ready

/* smmuv3.c — struct */
struct smmuv3_priv {
    ...
    bool coherent;
    volatile bool ready;   /* published last; gates the poll hook */
};

/* smmuv3_poll_events() */
void smmuv3_poll_events(void)
{
    if (!smmu.ready) {
        return;
    }
    /* Acquire: order the flag load before the queue-state loads below.
     * A control dependency alone doesn't order load→load on ARMv8. */
    fence_ord_read();           /* DMB ISHLD */
    uint32_t prod = *smmu.evtq_prod;
}
/* end of smmuv3_init(), after the CR0ACK poll loop */

 /* Release: make every prior store (evtq pointer, zeroed queue memory,
 * PROD/CONS shadow init) visible to other observers before the flag.
 * Without this, another core can observe ready==true while smmu.evtq
 * is still NULL from its point of view.
 */
	fence_ord_write();          /* DMB ISHST — store→store ordering */
	smmu.ready = true;
	

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/arch/armv8/gic.c Outdated
#if (SMMU_VERSION == 3)
/* Surface any SMMU translation faults (a refused DMA usually coincides with
* a device error IRQ). Cheap no-op when the event queue is empty. */
smmuv3_poll_events();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs on every interrupt on every CPU and does two page-1 MMIO reads even in the common empty case: a measurable per-IRQ cost on interrupt-heavy workloads. It's a genuinely useful backstop where the wired eventq IRQ isn't reliably delivered, so I wouldn't drop it, but just consider scoping it. Cheapest option, restrict the poll to the master CPU (the wired IRQ handler already covers the general case):

#if (SMMU_VERSION == 3)
    if (cpu_is_master()) {
        smmuv3_poll_events();
    }
#endif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I will update the PR with this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/arch/armv8/armv8-a/smmuv3.c Outdated
Comment on lines +27 to +29
/* Linear stream table cap: 2^8 = 256 entries (16 KiB). This comfortably covers
* typical single-level stream-ID spaces without needing a 2-level table. */
#define SMMUV3_ST_LOG2_CAP (8)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 256-entry cap assumes a small, dense stream-ID space. Stream IDs are integrator-defined and can be sparse or start high (PCIe Requester IDs especially). On a platform whose IDs exceed 255, every smmuv3_write_ste_s2() fails its bounds check and VM init aborts, with the only fix being to edit this constant. The table is linear, so letting a platform override it costs only memory. Suggestion makes it overridable from platform.mk (e.g. -DSMMUV3_ST_LOG2_CAP=11):

Suggested change
/* Linear stream table cap: 2^8 = 256 entries (16 KiB). This comfortably covers
* typical single-level stream-ID spaces without needing a 2-level table. */
#define SMMUV3_ST_LOG2_CAP (8)
/* Linear stream table cap: 2^SMMUV3_ST_LOG2_CAP entries (default 2^8 = 256,
* 16 KiB). A platform whose stream-ID space is sparse or starts high can
* override this from its platform.mk (e.g. -DSMMUV3_ST_LOG2_CAP=11). */
#ifndef SMMUV3_ST_LOG2_CAP
#define SMMUV3_ST_LOG2_CAP (8)
#endif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - I will update the PR code , so it can be overridable from platform.mk.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/arch/armv8/armv8-a/smmuv3.c Outdated
* wired event IRQ isn't delivered. No-op until the driver is initialised. */
void smmuv3_poll_events(void)
{
if (smmu.evtq_prod == NULL) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of the init-race fix above. This is the gate that goes live too early. Once a ready flag exists, this line becomes if (!smmu.ready) return;. Flagging it here so the two spots are reviewed together.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/arch/armv8/gic.c Outdated
Comment on lines +168 to +169
bool gicd_get_pend(irqid_t int_id)
{
return (gicd->ISPENDR[GIC_INT_REG(int_id)] & GIC_INT_MASK(int_id)) != 0;
}
{ return (gicd->ISPENDR[GIC_INT_REG(int_id)] & GIC_INT_MASK(int_id)) != 0; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like unrelated reformatting (the one-lined bodies don't match the surrounding style and aren't part of the SMMUv3 change). Worth reverting to keep the diff focused:

Suggested change
bool gicd_get_pend(irqid_t int_id)
{
return (gicd->ISPENDR[GIC_INT_REG(int_id)] & GIC_INT_MASK(int_id)) != 0;
}
{ return (gicd->ISPENDR[GIC_INT_REG(int_id)] & GIC_INT_MASK(int_id)) != 0; }
bool gicd_get_pend(irqid_t int_id)
{
return (gicd->ISPENDR[GIC_INT_REG(int_id)] & GIC_INT_MASK(int_id)) != 0;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be due to my local clang-format check, will fix in PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/arch/armv8/gic.c
}
}

bool gicd_get_act(irqid_t int_id)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as gicd_get_pend

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +125 to +130
for (size_t i = 0; i < SMMUV3_POLL_LIMIT; i++) {
if ((smmu.hw->CMDQ_CONS & SMMUV3_Q_IDX_MASK(SMMUV3_CMDQ_LOG2SIZE)) ==
(smmu.cmdq_prod & SMMUV3_Q_IDX_MASK(SMMUV3_CMDQ_LOG2SIZE))) {
return;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sync-completion check compares only the index bits and drops the wrap bit, so a completely full queue (PROD = CONS + size) is indistinguishable from empty. Not reachable today since every push sequence syncs within a few entries of a 128-deep queue, but latent if commands are ever batched before syncing. cmdq_prod already carries the full {wrap, index} (it's kept modulo 2×size), and hardware maintains the wrap bit in CMDQ_CONS, so comparing the full field is robust:

Suggested change
for (size_t i = 0; i < SMMUV3_POLL_LIMIT; i++) {
if ((smmu.hw->CMDQ_CONS & SMMUV3_Q_IDX_MASK(SMMUV3_CMDQ_LOG2SIZE)) ==
(smmu.cmdq_prod & SMMUV3_Q_IDX_MASK(SMMUV3_CMDQ_LOG2SIZE))) {
return;
}
}
uint32_t fullmask = (SMMUV3_Q_WRAP(SMMUV3_CMDQ_LOG2SIZE) << 1) - 1;
for (size_t i = 0; i < SMMUV3_POLL_LIMIT; i++) {
if ((smmu.hw->CMDQ_CONS & fullmask) == (smmu.cmdq_prod & fullmask)) {
return;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree - I will update the PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +90 to +95
#define SMMUV3_IDR1_TABLES_PRESET (1U << 30)
#define SMMUV3_IDR1_QUEUES_PRESET (1U << 29)
#define SMMUV3_IDR1_CMDQS_OFF (21)
#define SMMUV3_IDR1_CMDQS_LEN (5)
#define SMMUV3_IDR1_EVTQS_OFF (16)
#define SMMUV3_IDR1_EVTQS_LEN (5)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These capability macros are defined but never used

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove these unused macros.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@rabara

rabara commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @joaopeixoto13

Thanks a lot for this. An SMMUv3 driver has been sitting in our "someday" pile for some time, so it's great to see a clean, focused driver actually land!

Did a close pass and two things I'd want sorted before moving on, and a couple of design questions that aren't blockers.

Functional issues

  1. Init-order race on the poll hook.

I agree with your suggestion and will update the code in this PR.
Additionally I am also thinking of adding a release barrier (fence_sync_write() / DSB) before publishing ready so the store can't be reordered ahead of the queue setup as seen by other observers — the flag alone isn't enough on a weakly-ordered core.

  1. No SMMU TLBI on stage-2 remap.

Agreed this is a real gap. It's not reachable in the current static-partitioning model.
My preference is to document the static-mapping assumption explicitly in this PR and track the full invalidation hook in future PR. What is your opinion?

I foresee the testing would be a major challenge as well.

Design questions (not blockers)

  • BYPASS-by-default vs abort-by-default.

The knob is cheap, low-risk, and strictly increases capability while keeping the safe default — worth adding.
How about per-platform knob (e.g. -DSMMUV3_DEFAULT_ABORT from platform.mk) that flips the unbound-stream default from BYPASS to an abort STE, while keeping BYPASS as the default so the transparent firmware handoff (and bring-up on platforms without a fully-enumerated streamID map) is unaffected. The cost is just which STE template the unbound entries get. I'll keep GBPA in BYPASS during the reconfiguration window regardless, so in-flight transactions during handoff don't fault.

  • Single instance.

Fair point. I'll leave single-instance as-is for now, but I agree the interface shouldn't be boxed in — a per-group "which SMMU" selector is the right eventual shape given stream IDs are only unique within an instance's stream table.
Shall we add a note in code about this as a known limitation so multi-SMMU support can be layered on without an interface break? Or you have some other thought?

@joaopeixoto13

Copy link
Copy Markdown
Member

Thanks for the review @joaopeixoto13

Thanks a lot for this. An SMMUv3 driver has been sitting in our "someday" pile for some time, so it's great to see a clean, focused driver actually land!
Did a close pass and two things I'd want sorted before moving on, and a couple of design questions that aren't blockers.

Functional issues

  1. Init-order race on the poll hook.

I agree with your suggestion and will update the code in this PR. Additionally I am also thinking of adding a release barrier (fence_sync_write() / DSB) before publishing ready so the store can't be reordered ahead of the queue setup as seen by other observers — the flag alone isn't enough on a weakly-ordered core.

Yeah, agreed. Just note it needs a barrier on both sides, not just the writer. The if (!ready) return; check only orders later stores, not later loads on ARM, so another core can still read smmu.evtq / *evtq_prod before it sees ready == true. Easiest fix is to make ready a load-acquire (LDAR) on the reader, or put a DMB LD right after the gate. Then it's fully closed.

  1. No SMMU TLBI on stage-2 remap.

Agreed this is a real gap. It's not reachable in the current static-partitioning model. My preference is to document the static-mapping assumption explicitly in this PR and track the full invalidation hook in future PR. What is your opinion?

I foresee the testing would be a major challenge as well.

Fine to split it. Nothing hits that path in the static model today, so documenting the assumption now and doing the real hook later works for me. Two small asks: drop a note at the two // TODO: inval iommu tlbs lines in core/inc/tlb.h (not just the driver) so anyone adding dynamic remap later runs into it, and open an issue to track the actual hook.

And yeah, testing is the hard bit. You can't really trigger the stale-TLB case without remapping an already-bound VM's stage-2, which the static model never does, so the test only makes sense once the invalidation code exists. Fine to defer.

Design questions (not blockers)

  • BYPASS-by-default vs abort-by-default.

The knob is cheap, low-risk, and strictly increases capability while keeping the safe default — worth adding. How about per-platform knob (e.g. -DSMMUV3_DEFAULT_ABORT from platform.mk) that flips the unbound-stream default from BYPASS to an abort STE, while keeping BYPASS as the default so the transparent firmware handoff (and bring-up on platforms without a fully-enumerated streamID map) is unaffected. The cost is just which STE template the unbound entries get. I'll keep GBPA in BYPASS during the reconfiguration window regardless, so in-flight transactions during handoff don't fault.

Sounds good, that's exactly what I'd want: -DSMMUV3_DEFAULT_ABORT per platform, BYPASS as the default, GBPA staying in BYPASS during the reconfig window. All good.

  • Single instance.

Fair point. I'll leave single-instance as-is for now, but I agree the interface shouldn't be boxed in — a per-group "which SMMU" selector is the right eventual shape given stream IDs are only unique within an instance's stream table. Shall we add a note in code about this as a known limitation so multi-SMMU support can be layered on without an interface break? Or you have some other thought?

Yeah, a code note is enough. I'd put it in two spots: at static struct smmuv3_priv smmu (deliberately single-instance) and at the smmu_group struct (where a per-group instance selector would go later). That way someone can add multi-SMMU support down the line without breaking the interface. No need to do any of it now.

Add a minimal ARM SMMUv3 (IHI 0070) driver for Armv8-A, providing
stage-2-only DMA translation. The driver takes over the non-secure
SMMU left enabled by firmware: it installs a linear stream table
(all streams BYPASS by default so masters keep working with their
incoming attributes), a command queue and an event queue, and
promotes individual streams to stage-2 as VMs bind them. Faults are
drained and logged via the event queue (wired SPI plus a cheap poll
hook on the GIC IRQ path).

Driver selection is done at build time via SMMU_VERSION (default 2):
arch/smmu.h resolves to smmuv2.h or smmuv3.h, and objects.mk builds
the matching driver. Platforms opt into v3 with SMMU_VERSION:=3 in
their platform.mk; all existing platforms keep SMMUv2 unchanged.

Signed-off-by: Niravkumar L Rabara <niravkumarlaxmidas.rabara@altera.com>
@rabara
rabara force-pushed the add-smmuv3-support branch 2 times, most recently from a4946c2 to 650e9d7 Compare August 4, 2026 01:56
* 16 KiB). A platform whose stream-ID space is sparse or starts high can
* override this from its platform.mk (e.g. -DSMMUV3_ST_LOG2_CAP=11). */
#ifndef SMMUV3_ST_LOG2_CAP
#define SMMUV3_ST_LOG2_CAP (8)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the platform.mk override came out of the earlier discussion, but I'd rather not have a compile time cap at all. There is no reason 2^8 covers whatever platforms we end up running on, and any PCIe derived stream IDs blow way past what a linear table can reasonably hold. Since the full config is available when the driver initializes, can we size the table from it instead? Scan every VM's smmu groups and device ids, take the largest (for groups, id | mask), round up to a power of two and clamp to SIDSIZE. We could eventually do it completely statically using the config scritps to generate macros. Anything above the used ID space then fail-closes with C_BAD_STREAMID without spending STEs on it. Also please add a TODO noting the proper fix for large or sparse StreamID spaces is a 2-level stream table when SMMU_IDR0.ST_LEVEL allows it.

@joaopeixoto13 wdyt, does the config derived sizing work for you?


static void smmuv3_check_features(void)
{
uint32_t aidr = smmu.hw->AIDR & 0xF;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could:

Suggested change
uint32_t aidr = smmu.hw->AIDR & 0xF;
uint32_t aidr = smmu.hw->AIDR & SMMUV3_AIDR_ArchMinorRev_MASK;

* its platform.mk. Note this only changes the unbound default; GBPA is kept in
* BYPASS during the reconfiguration window regardless (see smmuv3_init), so
* in-flight transactions during handoff never fault. */
static void smmuv3_ste_fill_default(struct smmuv3_ste* e)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picking up the bypass vs abort question from the earlier discussion: I'd go all the way and make abort the only behavior, dropping SMMUV3_DEFAULT_ABORT and the GBPA bypass during the init window with it. The smmuv2 driver already faults unmatched streams (USFCFG), and with bypass the SMMU stops being an isolation boundary for anything the configs didn't bind. It's also inconsistent: stream IDs above the table size already abort with C_BAD_STREAMID, so whether an unbound master is contained depends on where its ID happens to fall. @rabara Is there a concrete use case you have or can think of that requires this bypass? If something ever requires it, I'd rather have an explicit per-stream bypass in the config than a global default.

@joaopeixoto13 you raised the original question, agree with going abort only?

smmu.evtq_prod = (volatile uint32_t*)(regs + SMMUV3_PAGE1_EVENTQ_PROD);
smmu.evtq_cons = (volatile uint32_t*)(regs + SMMUV3_PAGE1_EVENTQ_CONS);

smmu.coherent = (smmu.hw->IDR0 & SMMUV3_IDR0_COHACC_BIT) != 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see coherent is only used to decide whether to flush the driver's own structures (stream table and queues). But COHACC covers every SMMU-originated access, including the stage 2 walks, and those read the VM page tables, which Bao writes cacheable and never cleans since CPU walkers are coherent. So on a COHACC=0 SMMU the queues would work but the walks would read stale PTEs. I'd make this a hard requirement: ERROR in smmuv3_check_features when COHACC is 0, and drop the non-coherent path altogether, keeping only the barriers. At some point we can add the infrastructure to propagate the core's TLB maintenance to the IOMMU and lift this, but until that exists coherency is a must. Btw, does your Agilex5 integration tie COHACC high? On MMU-600 this depends on the sup_cohacc tie-off.

STE_W2_S2AFFD | STE_W2_S2R;

spin_lock(&smmu.lock);
struct smmuv3_ste* e = &smmu.st[sid];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing prevents two VMs from binding the same stream id: the second write just replaces the STE and the device silently moves to the last VM that bound it. The v2 driver ERRORs on this (smmu_compatible_sme_exists). Since the old STE is right here, checking for a valid entry with a different S2VMID before writing and ERRORing would be enough. We could also return success if the ste we are writing for that sid matches what is already there. This would be useful in platforms where sids are programable and we decide to set all sid of devices assigned to the same VM with the same sid

* track is whether the STE(s) have been installed.
*/
struct iommu_vm_arch {
bool initialized;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is written but never read anywhere. I'd drop it.

smmu.coherent ? "coherent" : "non-coherent");
}

bool smmuv3_write_ste_bypass(streamid_t sid)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing calls this. If we go abort only for unbound streams it becomes the building block for an explicit per-stream bypass, but until that exists I'd drop it.

interrupts_reserve(platform.arch.smmu.interrupt_id, smmuv3_eventq_irq_handler);
if (eid != INVALID_IRQID) {
interrupts_cpu_enable(eid, true);
smmu.hw->IRQ_CTRL = SMMUV3_IRQ_CTRL_EVENTQ | SMMUV3_IRQ_CTRL_GERROR;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GERROR is enabled here but there's no handler or ack path for it, so a command queue error only surfaces as the CMD_SYNC timeout WARNING. Either handle it minimally, log plus ack, or don't enable it.

{
uint32_t wrapmask = (SMMUV3_Q_WRAP(SMMUV3_EVTQ_LOG2SIZE) << 1) - 1;
uint32_t idxmask = SMMUV3_Q_IDX_MASK(SMMUV3_EVTQ_LOG2SIZE);
uint32_t prod = *smmu.evtq_prod & wrapmask;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overflow flag in EVENTQ_PROD (bit 31) is masked out and ignored, so an overflowing queue silently loses fault records. Worth at least a WARNING when it's set.

* + logs faults, so a refused DMA is visible instead of silent. */
if (platform.arch.smmu.interrupt_id != 0) {
irqid_t eid =
interrupts_reserve(platform.arch.smmu.interrupt_id, smmuv3_eventq_irq_handler);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the fault path as a whole: since we run the terminate model, an aborted DMA already surfaces inside the owning VM through the device itself, bus error status, the device's own error interrupt, a timeout, and the guest driver has to handle those on bare metal anyway. The eventq SPI on the other hand has a single target, so whoever runs on the master cpu absorbs fault handling for every VM's devices, a cross VM interference channel. Hypervisor side fault observation is still valuable to detect a misbehaving VM, but smmuv2 already reports nothing and I'd rather we approach that in a structured way for all iommus in a separate PR, where we can also design around the interference. So for this one I'd drop the eventq interrupt and the gic.c poll entirely and keep the driver to what the isolation needs. @joaopeixoto13 this reverses the earlier conclusion on scoping the poll to the master cpu, wdyt?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the comment on EVENTQ_PROD comment loses the effect if we remove fault handling...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants