Skip to content

IOMMU: Add IOMMU Support - #358

Merged
nspin merged 1 commit into
seL4:mainfrom
au-ts:callumb_and_cheng/iommu_capdl_support
Jul 14, 2026
Merged

IOMMU: Add IOMMU Support#358
nspin merged 1 commit into
seL4:mainfrom
au-ts:callumb_and_cheng/iommu_capdl_support

Conversation

@cazb2

@cazb2 cazb2 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

This commit adds support for the creation of IOMMU address spaces on x86.

The capdl spec for defining an IO Address Space on x86 is a root IODevice object, which contains in slot 0 the root IOPageTable object and in slots 1.. spare IOPageTable objects. These IOPageTable objects are similar to the normal x86 page table structures.

At runtime, the initialiser will calculate how many of the spare page tables must be mapped. It does this by taking the difference of what seL4 reports to be the number of page table levels and the configured number of levels we define in the x86_io_address_space module.

This means that capdl enforces a fixed upper limit on the maximum IO virtual address and will fail if seL4 reports that the IOMMU supports less than this maximum. If seL4 reports support for a larger maximum IO virtual address, this design works by mapping in the spare page tables into the highest levels of the hierarchy in slot 0, until we reach the page table responsible for translating the first valid bits of the maximum IO virtual address.

@cazb2
cazb2 requested a review from nspin as a code owner July 8, 2026 02:59
@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch 3 times, most recently from 5770590 to 91d501c Compare July 8, 2026 03:20
@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch from 91d501c to db76418 Compare July 8, 2026 03:44
Comment thread crates/sel4/src/arch/x86/object.rs
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment thread crates/sel4-capdl-initializer/src/initialize.rs Outdated
Comment on lines +786 to +787
for entry in obj.slots.iter() {
let entry_ioaddr = ioaddr + (usize::from(entry.slot) << shift);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please follow init_vspace if you can; I suspect this should be for (i, entry) in obj.entries().

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.

Equivalent, but will update.

);
}

let runtime_level = iopt_level + prefix_depth;

@midnightveil midnightveil Jul 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I need to think about this prefix a bit more, FWIW.

It's not immediately obvious from the code how this works, so it would be a good idea to include your motivation from the PR description as a comment here.

I do have a question though: how are the levels encoded in the spec, as who defines "Level = 0 => top of the page table"?

Obviously there's a mismatch here somewhere, but I'm not clear on precisely where it is.

// Level is zero indexed in the spec.

This comment to me seems to imply that the spec always starts the levels from 0.

And how is this not an issue for the VSpace? seL4 has similar behaviour; for AARch64-hyp with PASize=40 it's 3-level, but for AArch64-hyp with PASize>=44 it's 4-level, so I don't see why IOMMU would be special in this case (where special = extra behaviour not needed for vspace).

@midnightveil midnightveil Jul 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nspin How does your VSpace code handle PASize=40 (num_levels=3) on AArch64? (AARCH64_VSPACE_S2_START_L1 in seL4 and this relevant discussion here).

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.

Yep, the difference is the IODevice (which is the IOSpace) object. For a normal address space it would be the vspace that consumes 4KiB and acts as the root of the translation table. But for IOMMU the IOSpace is meagrely a capability i.e. it's a fake kernel object. The root of translation is the actually the first IOPT that gets mapped in.

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 don't know if this is the same as the hypervisor, I would be surprised...

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 think I simplified it:)

@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch 3 times, most recently from 072aa65 to 474be57 Compare July 9, 2026 01:40

@midnightveil midnightveil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm happy with this now, until the kernel changes (seL4/seL4#1703) go through.

Waiting on @nspin review.

}

#[sel4::sel4_cfg(all(ARCH_X86_64, IOMMU))]
fn init_iopt_tree(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the *_tree postfix is a bit out of place compared to similar functions like init_vspace_x86_ept() and init_vspace(). I feel like this should be called init_iospace().

pub struct IODevice {
pub slots: Vec<CapTableEntry>,
pub domain_id: Word,
pub pci_device: (Word, Word, Word),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I know that it is obvious but:

Suggested change
pub pci_device: (Word, Word, Word),
pub pci_device: (Word, Word, Word), // bus:device.function

for folks who aren't familliar

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Or make it into a struct

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this matches the capDL spec, I don't know to what extend it can differ. (might be wrong here)

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.

^

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.

seL4/capdl#90 is the part it would have to match, and that new module is (a) not yet upstreamed and (b) doesn't have IOMMU support yet anyways. So this PR can define what that part of the spec should look like.

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.

awesome :)

@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch 2 times, most recently from d237332 to d48b9ba Compare July 10, 2026 01:56
// since this is runtime information. The second is the spec assumes that all domain ids are
// valid, which is also not true since the kernel may reserve an unknown number of domain
// ids during boot.
let [cap_data] = sel4::sys::seL4_X86_IOSpace_CapData::new(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I suspect you might want to make this a bit nicer. Have a look at what Nick did for 'CNodeCapData' and where it is used with syscalls.

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 think the change I made with to_sel4 could easily generalise, so a macro would be cool... I didn't implement it here though.

@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch 3 times, most recently from 7a6d083 to ac6a168 Compare July 10, 2026 03:15

@nspin nspin 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 for this. Just a few comments, mostly nits.

pub struct IODevice {
pub slots: Vec<CapTableEntry>,
pub domain_id: Word,
pub pci_device: (Word, Word, Word), // bus:device.function

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.

Could you change this to be a struct?

That would also eliminate the need for trait ToSel4.

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.

yep, so we don't have to match the C/haskell spec identically, which represents this as a tuple?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@nspin, apologies it looks like you might have missed this one, we just wanted to confirm whether its necessary to match how the C/haskell implementation defined these types. (I doesn't look like there is a full implementation in the C/haskell yet...)

I agree having this as a structure would be way better if its allowed :)

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.

Answered here: #358 (comment)

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.

Ty for the clarification below :)

Comment thread crates/sel4-capdl-initializer/types/src/x86_io_address_space.rs
PageTable(object::PageTable),
AsidPool(object::AsidPool),
IODevice(object::IODevice),
IOPT(object::IOPT),

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.

Could you expand IOPT to IOPageTable? Just to match the convention of the surrounding code.

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.

Just to confirm, so rust sel4 naming doesn't need to correspond to the c/haskell capdl?

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.

Not the main part of the Haskell CapDL tool. Just this part:

seL4/capdl#90

which doesn't have IOMMU support yet. So no need to worry about matching, this PR will determine with the JSON spec looks like.

Comment thread crates/sel4/src/arch/x86/vspace.rs Outdated
))
}

pub fn new_from_pci_tuple(domain_id: Word, pci_device: (Word, Word, Word)) -> Self {

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.

Is a tuple much better than three arguments? This could be a structured type, and could be the target of a .to_sel4() method of a corresponding structured type in sel4-capdl-initializer-types.

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.

Yeah fair, I simplified it to the interface to just the three arg new :)

Comment thread crates/sel4/src/arch/x86/vspace.rs Outdated
}

#[sel4_cfg(IOMMU)]
pub const fn from_level_iopt(_level: usize) -> Option<Self> {

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.

Could you expand this to from_level_io_page_table?

Ok(())
}

fn init_iospaces(&mut self) -> Result<()> {

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 think it might be cleaner to #[cfg] this method and then sel4_cfg_if its invocation above, like init_sched_contexts does.

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.

yep, will change it for now, but in future if SMMU support, (the ARM version), is added some form of Arch ifdef will be needed...

Comment on lines +679 to +684
match dst.mint(&src, CapRights::all(), cap_data.into()) {
Ok(_) => Ok(self.orig_cap::<cap_type::IOSpace>(obj_id)),
Err(err) => {
panic!("Error: {err} when minting an x86 IOSpace capability.")
}
}

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 might read more cleanly with unwrap_or_else(|err| ...) rather than a match.

@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch from ac6a168 to e9d1990 Compare July 10, 2026 10:09
@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch from e9d1990 to f38b17a Compare July 12, 2026 07:44
PageTable(object::PageTable),
AsidPool(object::AsidPool),
IODevice(object::IODevice),
IOPT(object::IOPT),

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.

Not the main part of the Haskell CapDL tool. Just this part:

seL4/capdl#90

which doesn't have IOMMU support yet. So no need to worry about matching, this PR will determine with the JSON spec looks like.

pub struct IODevice {
pub slots: Vec<CapTableEntry>,
pub domain_id: Word,
pub pci_device: (Word, Word, Word), // bus:device.function

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.

Answered here: #358 (comment)

@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch from f38b17a to 9132d3e Compare July 14, 2026 11:48
This commit adds support for the creation of IOMMU address spaces on
x86.

The capdl spec for defining an IO Address Space on x86 is a root
IOSpace object, which contains in slot 0 the root IOPageTable object
and in slots 1..  spare IOPageTable objects. These IOPageTable objects
are similar to the normal x86 page table structures.

At runtime, the initialiser will calculate how many of the spare page
tables must be mapped.  It does this by taking the difference of what
seL4 reports to be the number of page table levels and the configured
number of levels we define in the x86_io_address_space module.

This means that capdl enforces a fixed upper limit on the maximum IO
virtual address and will fail if seL4 reports that the IOMMU supports
less than this maximum. If seL4 reports support for a larger maximum IO
virtual address, this design works by mapping in the spare page tables
into the highest levels of the hierarchy in slot 0, until we reach the
page table responsible for translating the first valid bits of the
maximum IO virtual address.

Signed-off-by: Callum <c.berry@student.unsw.edu.au>
@cazb2
cazb2 force-pushed the callumb_and_cheng/iommu_capdl_support branch from 9132d3e to 718c6fa Compare July 14, 2026 12:44
@nspin
nspin merged commit 21cca71 into seL4:main Jul 14, 2026
14 checks passed
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.

4 participants