From 610e4001b8e4d97156b8330fd956efaf4fa6a445 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 12 Aug 2026 02:41:12 -0700 Subject: [PATCH 1/7] iommufd-bindings: Regenerate bindings from kernel v7.1 The bindings were generated from a kernel that predates the iommufd vIOMMU, vDevice, VEVENTQ and HW_QUEUE uAPI, so none of it can be reached from Rust. Regenerate against v7.1, which has all of it. Assisted-by: Claude:Opus-5 Signed-off-by: Sebastien Boeuf --- iommufd-bindings/README.md | 8 +- iommufd-bindings/src/iommufd.rs | 772 +++++++++++++++++++++++++++++++- iommufd-bindings/src/lib.rs | 6 +- 3 files changed, 765 insertions(+), 21 deletions(-) diff --git a/iommufd-bindings/README.md b/iommufd-bindings/README.md index 97f04d7..5ff6a78 100644 --- a/iommufd-bindings/README.md +++ b/iommufd-bindings/README.md @@ -7,15 +7,15 @@ kernel version running on a particular host. The user must ensure that specific structures, members, or constants are supported and valid for the kernel version they are using. -Currently, the bindings are generated using bindgen version 0.72.0 and -kernel version [v6.6](https://github.com/torvalds/linux/tree/v6.6). +Currently, the bindings are generated using bindgen version 0.72.1 and +kernel version [v7.1](https://github.com/torvalds/linux/tree/v7.1). ## Regenerating Bindings ### Bindgen -Install bindgen version 0.72.0 +Install bindgen version 0.72.1 ```bash -cargo install bindgen-cli --vers 0.72.0 +cargo install bindgen-cli --vers 0.72.1 ``` ### Linux Kernel diff --git a/iommufd-bindings/src/iommufd.rs b/iommufd-bindings/src/iommufd.rs index 9c99fb3..14dcdad 100644 --- a/iommufd-bindings/src/iommufd.rs +++ b/iommufd-bindings/src/iommufd.rs @@ -1,7 +1,5 @@ -/* automatically generated by rust-bindgen 0.72.0 */ +/* automatically generated by rust-bindgen 0.72.1 */ -pub const __BITS_PER_LONG: u32 = 64; -pub const __FD_SETSIZE: u32 = 1024; pub const _IOC_NRBITS: u32 = 8; pub const _IOC_TYPEBITS: u32 = 8; pub const _IOC_SIZEBITS: u32 = 14; @@ -22,6 +20,9 @@ pub const IOC_OUT: u32 = 2147483648; pub const IOC_INOUT: u32 = 3221225472; pub const IOCSIZE_MASK: u32 = 1073676288; pub const IOCSIZE_SHIFT: u32 = 16; +pub const __BITS_PER_LONG: u32 = 64; +pub const __BITS_PER_LONG_LONG: u32 = 64; +pub const __FD_SETSIZE: u32 = 1024; pub const IOMMUFD_TYPE: u8 = 59u8; pub type __s8 = ::std::os::raw::c_schar; pub type __u8 = ::std::os::raw::c_uchar; @@ -79,6 +80,7 @@ const _: () = { }; pub type __kernel_off_t = __kernel_long_t; pub type __kernel_loff_t = ::std::os::raw::c_longlong; +pub type __kernel_uoff_t = ::std::os::raw::c_ulonglong; pub type __kernel_old_time_t = __kernel_long_t; pub type __kernel_time_t = __kernel_long_t; pub type __kernel_time64_t = ::std::os::raw::c_longlong; @@ -111,6 +113,16 @@ pub const IOMMUFD_CMD_OPTION: _bindgen_ty_1 = 135; pub const IOMMUFD_CMD_VFIO_IOAS: _bindgen_ty_1 = 136; pub const IOMMUFD_CMD_HWPT_ALLOC: _bindgen_ty_1 = 137; pub const IOMMUFD_CMD_GET_HW_INFO: _bindgen_ty_1 = 138; +pub const IOMMUFD_CMD_HWPT_SET_DIRTY_TRACKING: _bindgen_ty_1 = 139; +pub const IOMMUFD_CMD_HWPT_GET_DIRTY_BITMAP: _bindgen_ty_1 = 140; +pub const IOMMUFD_CMD_HWPT_INVALIDATE: _bindgen_ty_1 = 141; +pub const IOMMUFD_CMD_FAULT_QUEUE_ALLOC: _bindgen_ty_1 = 142; +pub const IOMMUFD_CMD_IOAS_MAP_FILE: _bindgen_ty_1 = 143; +pub const IOMMUFD_CMD_VIOMMU_ALLOC: _bindgen_ty_1 = 144; +pub const IOMMUFD_CMD_VDEVICE_ALLOC: _bindgen_ty_1 = 145; +pub const IOMMUFD_CMD_IOAS_CHANGE_PROCESS: _bindgen_ty_1 = 146; +pub const IOMMUFD_CMD_VEVENTQ_ALLOC: _bindgen_ty_1 = 147; +pub const IOMMUFD_CMD_HW_QUEUE_ALLOC: _bindgen_ty_1 = 148; #[doc = " DOC: General ioctl format\n\n The ioctl interface follows a general format to allow for extensibility. Each\n ioctl is passed in a structure pointer as the argument providing the size of\n the structure in the first u32. The kernel checks that any structure space\n beyond what it understands is 0. This allows userspace to use the backward\n compatible portion while consistently using the newer, larger, structures.\n\n ioctls use a standard meaning for common errnos:\n\n - ENOTTY: The IOCTL number itself is not supported at all\n - E2BIG: The IOCTL number is supported, but the provided structure has\n non-zero in a part the kernel does not understand.\n - EOPNOTSUPP: The IOCTL number is supported, and the structure is\n understood, however a known field has a value the kernel does not\n understand or support.\n - EINVAL: Everything about the IOCTL was understood, but a field is not\n correct.\n - ENOENT: An ID or IOVA provided does not exist.\n - ENOMEM: Out of memory.\n - EOVERFLOW: Mathematics overflowed.\n\n As well as additional errnos, within specific ioctls."] pub type _bindgen_ty_1 = ::std::os::raw::c_uint; #[doc = " struct iommu_destroy - ioctl(IOMMU_DESTROY)\n @size: sizeof(struct iommu_destroy)\n @id: iommufd object ID to destroy. Can be any destroyable object type.\n\n Destroy any object held within iommufd."] @@ -253,6 +265,37 @@ const _: () = { ["Offset of field: iommu_ioas_map::iova"] [::std::mem::offset_of!(iommu_ioas_map, iova) - 32usize]; }; +#[doc = " struct iommu_ioas_map_file - ioctl(IOMMU_IOAS_MAP_FILE)\n @size: sizeof(struct iommu_ioas_map_file)\n @flags: same as for iommu_ioas_map\n @ioas_id: same as for iommu_ioas_map\n @fd: the memfd to map\n @start: byte offset from start of file to map from\n @length: same as for iommu_ioas_map\n @iova: same as for iommu_ioas_map\n\n Set an IOVA mapping from a memfd file. All other arguments and semantics\n match those of IOMMU_IOAS_MAP."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_ioas_map_file { + pub size: __u32, + pub flags: __u32, + pub ioas_id: __u32, + pub fd: __s32, + pub start: __u64, + pub length: __u64, + pub iova: __u64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_ioas_map_file"][::std::mem::size_of::() - 40usize]; + ["Alignment of iommu_ioas_map_file"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_ioas_map_file::size"] + [::std::mem::offset_of!(iommu_ioas_map_file, size) - 0usize]; + ["Offset of field: iommu_ioas_map_file::flags"] + [::std::mem::offset_of!(iommu_ioas_map_file, flags) - 4usize]; + ["Offset of field: iommu_ioas_map_file::ioas_id"] + [::std::mem::offset_of!(iommu_ioas_map_file, ioas_id) - 8usize]; + ["Offset of field: iommu_ioas_map_file::fd"] + [::std::mem::offset_of!(iommu_ioas_map_file, fd) - 12usize]; + ["Offset of field: iommu_ioas_map_file::start"] + [::std::mem::offset_of!(iommu_ioas_map_file, start) - 16usize]; + ["Offset of field: iommu_ioas_map_file::length"] + [::std::mem::offset_of!(iommu_ioas_map_file, length) - 24usize]; + ["Offset of field: iommu_ioas_map_file::iova"] + [::std::mem::offset_of!(iommu_ioas_map_file, iova) - 32usize]; +}; #[doc = " struct iommu_ioas_copy - ioctl(IOMMU_IOAS_COPY)\n @size: sizeof(struct iommu_ioas_copy)\n @flags: Combination of enum iommufd_ioas_map_flags\n @dst_ioas_id: IOAS ID to change the mapping of\n @src_ioas_id: IOAS ID to copy from\n @length: Number of bytes to copy and map\n @dst_iova: IOVA the mapping was placed at. If IOMMU_IOAS_MAP_FIXED_IOVA is\n set then this must be provided as input.\n @src_iova: IOVA to start the copy\n\n Copy an already existing mapping from src_ioas_id and establish it in\n dst_ioas_id. The src iova/length must exactly match a range used with\n IOMMU_IOAS_MAP.\n\n This may be used to efficiently clone a subset of an IOAS to another, or as a\n kind of 'cache' to speed up mapping. Copy has an efficiency advantage over\n establishing equivalent new mappings, as internal resources are shared, and\n the kernel will pin the user memory only once."] #[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] @@ -308,7 +351,7 @@ const _: () = { }; pub const iommufd_option_IOMMU_OPTION_RLIMIT_MODE: iommufd_option = 0; pub const iommufd_option_IOMMU_OPTION_HUGE_PAGES: iommufd_option = 1; -#[doc = " enum iommufd_option - ioctl(IOMMU_OPTION_RLIMIT_MODE) and\n ioctl(IOMMU_OPTION_HUGE_PAGES)\n @IOMMU_OPTION_RLIMIT_MODE:\n Change how RLIMIT_MEMLOCK accounting works. The caller must have privilege\n to invoke this. Value 0 (default) is user based accouting, 1 uses process\n based accounting. Global option, object_id must be 0\n @IOMMU_OPTION_HUGE_PAGES:\n Value 1 (default) allows contiguous pages to be combined when generating\n iommu mappings. Value 0 disables combining, everything is mapped to\n PAGE_SIZE. This can be useful for benchmarking. This is a per-IOAS\n option, the object_id must be the IOAS ID."] +#[doc = " enum iommufd_option - ioctl(IOMMU_OPTION_RLIMIT_MODE) and\n ioctl(IOMMU_OPTION_HUGE_PAGES)\n @IOMMU_OPTION_RLIMIT_MODE:\n Change how RLIMIT_MEMLOCK accounting works. The caller must have privilege\n to invoke this. Value 0 (default) is user based accounting, 1 uses process\n based accounting. Global option, object_id must be 0\n @IOMMU_OPTION_HUGE_PAGES:\n Value 1 (default) allows contiguous pages to be combined when generating\n iommu mappings. Value 0 disables combining, everything is mapped to\n PAGE_SIZE. This can be useful for benchmarking. This is a per-IOAS\n option, the object_id must be the IOAS ID."] pub type iommufd_option = ::std::os::raw::c_uint; pub const iommufd_option_ops_IOMMU_OPTION_OP_SET: iommufd_option_ops = 0; pub const iommufd_option_ops_IOMMU_OPTION_OP_GET: iommufd_option_ops = 1; @@ -365,7 +408,73 @@ const _: () = { ["Offset of field: iommu_vfio_ioas::__reserved"] [::std::mem::offset_of!(iommu_vfio_ioas, __reserved) - 10usize]; }; -#[doc = " struct iommu_hwpt_alloc - ioctl(IOMMU_HWPT_ALLOC)\n @size: sizeof(struct iommu_hwpt_alloc)\n @flags: Must be 0\n @dev_id: The device to allocate this HWPT for\n @pt_id: The IOAS to connect this HWPT to\n @out_hwpt_id: The ID of the new HWPT\n @__reserved: Must be 0\n\n Explicitly allocate a hardware page table object. This is the same object\n type that is returned by iommufd_device_attach() and represents the\n underlying iommu driver's iommu_domain kernel object.\n\n A HWPT will be created with the IOVA mappings from the given IOAS."] +pub const iommufd_hwpt_alloc_flags_IOMMU_HWPT_ALLOC_NEST_PARENT: iommufd_hwpt_alloc_flags = 1; +pub const iommufd_hwpt_alloc_flags_IOMMU_HWPT_ALLOC_DIRTY_TRACKING: iommufd_hwpt_alloc_flags = 2; +pub const iommufd_hwpt_alloc_flags_IOMMU_HWPT_FAULT_ID_VALID: iommufd_hwpt_alloc_flags = 4; +pub const iommufd_hwpt_alloc_flags_IOMMU_HWPT_ALLOC_PASID: iommufd_hwpt_alloc_flags = 8; +#[doc = " enum iommufd_hwpt_alloc_flags - Flags for HWPT allocation\n @IOMMU_HWPT_ALLOC_NEST_PARENT: If set, allocate a HWPT that can serve as\n the parent HWPT in a nesting configuration.\n @IOMMU_HWPT_ALLOC_DIRTY_TRACKING: Dirty tracking support for device IOMMU is\n enforced on device attachment\n @IOMMU_HWPT_FAULT_ID_VALID: The fault_id field of hwpt allocation data is\n valid.\n @IOMMU_HWPT_ALLOC_PASID: Requests a domain that can be used with PASID. The\n domain can be attached to any PASID on the device.\n Any domain attached to the non-PASID part of the\n device must also be flagged, otherwise attaching a\n PASID will blocked.\n For the user that wants to attach PASID, ioas is\n not recommended for both the non-PASID part\n and PASID part of the device.\n If IOMMU does not support PASID it will return\n error (-EOPNOTSUPP)."] +pub type iommufd_hwpt_alloc_flags = ::std::os::raw::c_uint; +pub const iommu_hwpt_vtd_s1_flags_IOMMU_VTD_S1_SRE: iommu_hwpt_vtd_s1_flags = 1; +pub const iommu_hwpt_vtd_s1_flags_IOMMU_VTD_S1_EAFE: iommu_hwpt_vtd_s1_flags = 2; +pub const iommu_hwpt_vtd_s1_flags_IOMMU_VTD_S1_WPE: iommu_hwpt_vtd_s1_flags = 4; +#[doc = " enum iommu_hwpt_vtd_s1_flags - Intel VT-d stage-1 page table\n entry attributes\n @IOMMU_VTD_S1_SRE: Supervisor request\n @IOMMU_VTD_S1_EAFE: Extended access enable\n @IOMMU_VTD_S1_WPE: Write protect enable"] +pub type iommu_hwpt_vtd_s1_flags = ::std::os::raw::c_uint; +#[doc = " struct iommu_hwpt_vtd_s1 - Intel VT-d stage-1 page table\n info (IOMMU_HWPT_DATA_VTD_S1)\n @flags: Combination of enum iommu_hwpt_vtd_s1_flags\n @pgtbl_addr: The base address of the stage-1 page table.\n @addr_width: The address width of the stage-1 page table\n @__reserved: Must be 0"] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_vtd_s1 { + pub flags: __u64, + pub pgtbl_addr: __u64, + pub addr_width: __u32, + pub __reserved: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_vtd_s1"][::std::mem::size_of::() - 24usize]; + ["Alignment of iommu_hwpt_vtd_s1"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hwpt_vtd_s1::flags"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1, flags) - 0usize]; + ["Offset of field: iommu_hwpt_vtd_s1::pgtbl_addr"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1, pgtbl_addr) - 8usize]; + ["Offset of field: iommu_hwpt_vtd_s1::addr_width"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1, addr_width) - 16usize]; + ["Offset of field: iommu_hwpt_vtd_s1::__reserved"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1, __reserved) - 20usize]; +}; +#[doc = " struct iommu_hwpt_arm_smmuv3 - ARM SMMUv3 nested STE\n (IOMMU_HWPT_DATA_ARM_SMMUV3)\n\n @ste: The first two double words of the user space Stream Table Entry for\n the translation. Must be little-endian.\n Allowed fields: (Refer to \"5.2 Stream Table Entry\" in SMMUv3 HW Spec)\n - word-0: V, Cfg, S1Fmt, S1ContextPtr, S1CDMax\n - word-1: EATS, S1DSS, S1CIR, S1COR, S1CSH, S1STALLD\n\n -EIO will be returned if @ste is not legal or contains any non-allowed field.\n Cfg can be used to select a S1, Bypass or Abort configuration. A Bypass\n nested domain will translate the same as the nesting parent. The S1 will\n install a Context Descriptor Table pointing at userspace memory translated\n by the nesting parent.\n\n It's suggested to allocate a vDEVICE object carrying vSID and then re-attach\n the nested domain, as soon as the vSID is available in the VMM level:\n\n - when Cfg=translate, a vDEVICE must be allocated prior to attaching to the\n allocated nested domain, as CD/ATS invalidations and vevents need a vSID.\n - when Cfg=bypass/abort, a vDEVICE is not enforced during the nested domain\n attachment, to support a GBPA case where VM sets CR0.SMMUEN=0. However, if\n VM sets CR0.SMMUEN=1 while missing a vDEVICE object, kernel would fail to\n report events to the VM. E.g. F_TRANSLATION when guest STE.Cfg=abort."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_arm_smmuv3 { + pub ste: [__le64; 2usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_arm_smmuv3"][::std::mem::size_of::() - 16usize]; + ["Alignment of iommu_hwpt_arm_smmuv3"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hwpt_arm_smmuv3::ste"] + [::std::mem::offset_of!(iommu_hwpt_arm_smmuv3, ste) - 0usize]; +}; +#[doc = " struct iommu_hwpt_amd_guest - AMD IOMMU guest I/O page table data\n\t\t\t\t (IOMMU_HWPT_DATA_AMD_GUEST)\n @dte: Guest Device Table Entry (DTE)"] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_amd_guest { + pub dte: [__u64; 4usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_amd_guest"][::std::mem::size_of::() - 32usize]; + ["Alignment of iommu_hwpt_amd_guest"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hwpt_amd_guest::dte"] + [::std::mem::offset_of!(iommu_hwpt_amd_guest, dte) - 0usize]; +}; +pub const iommu_hwpt_data_type_IOMMU_HWPT_DATA_NONE: iommu_hwpt_data_type = 0; +pub const iommu_hwpt_data_type_IOMMU_HWPT_DATA_VTD_S1: iommu_hwpt_data_type = 1; +pub const iommu_hwpt_data_type_IOMMU_HWPT_DATA_ARM_SMMUV3: iommu_hwpt_data_type = 2; +pub const iommu_hwpt_data_type_IOMMU_HWPT_DATA_AMD_GUEST: iommu_hwpt_data_type = 3; +#[doc = " enum iommu_hwpt_data_type - IOMMU HWPT Data Type\n @IOMMU_HWPT_DATA_NONE: no data\n @IOMMU_HWPT_DATA_VTD_S1: Intel VT-d stage-1 page table\n @IOMMU_HWPT_DATA_ARM_SMMUV3: ARM SMMUv3 Context Descriptor Table\n @IOMMU_HWPT_DATA_AMD_GUEST: AMD IOMMU guest page table"] +pub type iommu_hwpt_data_type = ::std::os::raw::c_uint; +#[doc = " struct iommu_hwpt_alloc - ioctl(IOMMU_HWPT_ALLOC)\n @size: sizeof(struct iommu_hwpt_alloc)\n @flags: Combination of enum iommufd_hwpt_alloc_flags\n @dev_id: The device to allocate this HWPT for\n @pt_id: The IOAS or HWPT or vIOMMU to connect this HWPT to\n @out_hwpt_id: The ID of the new HWPT\n @__reserved: Must be 0\n @data_type: One of enum iommu_hwpt_data_type\n @data_len: Length of the type specific data\n @data_uptr: User pointer to the type specific data\n @fault_id: The ID of IOMMUFD_FAULT object. Valid only if flags field of\n IOMMU_HWPT_FAULT_ID_VALID is set.\n @__reserved2: Padding to 64-bit alignment. Must be 0.\n\n Explicitly allocate a hardware page table object. This is the same object\n type that is returned by iommufd_device_attach() and represents the\n underlying iommu driver's iommu_domain kernel object.\n\n A kernel-managed HWPT will be created with the mappings from the given\n IOAS via the @pt_id. The @data_type for this allocation must be set to\n IOMMU_HWPT_DATA_NONE. The HWPT can be allocated as a parent HWPT for a\n nesting configuration by passing IOMMU_HWPT_ALLOC_NEST_PARENT via @flags.\n\n A user-managed nested HWPT will be created from a given vIOMMU (wrapping a\n parent HWPT) or a parent HWPT via @pt_id, in which the parent HWPT must be\n allocated previously via the same ioctl from a given IOAS (@pt_id). In this\n case, the @data_type must be set to a pre-defined type corresponding to an\n I/O page table type supported by the underlying IOMMU hardware. The device\n via @dev_id and the vIOMMU via @pt_id must be associated to the same IOMMU\n instance.\n\n If the @data_type is set to IOMMU_HWPT_DATA_NONE, @data_len and\n @data_uptr should be zero. Otherwise, both @data_len and @data_uptr\n must be given."] #[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct iommu_hwpt_alloc { @@ -375,11 +484,16 @@ pub struct iommu_hwpt_alloc { pub pt_id: __u32, pub out_hwpt_id: __u32, pub __reserved: __u32, + pub data_type: __u32, + pub data_len: __u32, + pub data_uptr: __u64, + pub fault_id: __u32, + pub __reserved2: __u32, } #[allow(clippy::unnecessary_operation, clippy::identity_op)] const _: () = { - ["Size of iommu_hwpt_alloc"][::std::mem::size_of::() - 24usize]; - ["Alignment of iommu_hwpt_alloc"][::std::mem::align_of::() - 4usize]; + ["Size of iommu_hwpt_alloc"][::std::mem::size_of::() - 48usize]; + ["Alignment of iommu_hwpt_alloc"][::std::mem::align_of::() - 8usize]; ["Offset of field: iommu_hwpt_alloc::size"] [::std::mem::offset_of!(iommu_hwpt_alloc, size) - 0usize]; ["Offset of field: iommu_hwpt_alloc::flags"] @@ -392,8 +506,22 @@ const _: () = { [::std::mem::offset_of!(iommu_hwpt_alloc, out_hwpt_id) - 16usize]; ["Offset of field: iommu_hwpt_alloc::__reserved"] [::std::mem::offset_of!(iommu_hwpt_alloc, __reserved) - 20usize]; + ["Offset of field: iommu_hwpt_alloc::data_type"] + [::std::mem::offset_of!(iommu_hwpt_alloc, data_type) - 24usize]; + ["Offset of field: iommu_hwpt_alloc::data_len"] + [::std::mem::offset_of!(iommu_hwpt_alloc, data_len) - 28usize]; + ["Offset of field: iommu_hwpt_alloc::data_uptr"] + [::std::mem::offset_of!(iommu_hwpt_alloc, data_uptr) - 32usize]; + ["Offset of field: iommu_hwpt_alloc::fault_id"] + [::std::mem::offset_of!(iommu_hwpt_alloc, fault_id) - 40usize]; + ["Offset of field: iommu_hwpt_alloc::__reserved2"] + [::std::mem::offset_of!(iommu_hwpt_alloc, __reserved2) - 44usize]; }; -#[doc = " struct iommu_hw_info_vtd - Intel VT-d hardware information\n\n @flags: Must be 0\n @__reserved: Must be 0\n\n @cap_reg: Value of Intel VT-d capability register defined in VT-d spec\n section 11.4.2 Capability Register.\n @ecap_reg: Value of Intel VT-d capability register defined in VT-d spec\n section 11.4.3 Extended Capability Register.\n\n User needs to understand the Intel VT-d specification to decode the\n register value."] +pub const iommu_hw_info_vtd_flags_IOMMU_HW_INFO_VTD_ERRATA_772415_SPR17: iommu_hw_info_vtd_flags = + 1; +#[doc = " enum iommu_hw_info_vtd_flags - Flags for VT-d hw_info\n @IOMMU_HW_INFO_VTD_ERRATA_772415_SPR17: If set, disallow read-only mappings\n on a nested_parent domain.\n https://www.intel.com/content/www/us/en/content-details/772415/content-details.html"] +pub type iommu_hw_info_vtd_flags = ::std::os::raw::c_uint; +#[doc = " struct iommu_hw_info_vtd - Intel VT-d hardware information\n\n @flags: Combination of enum iommu_hw_info_vtd_flags\n @__reserved: Must be 0\n\n @cap_reg: Value of Intel VT-d capability register defined in VT-d spec\n section 11.4.2 Capability Register.\n @ecap_reg: Value of Intel VT-d capability register defined in VT-d spec\n section 11.4.3 Extended Capability Register.\n\n User needs to understand the Intel VT-d specification to decode the\n register value."] #[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct iommu_hw_info_vtd { @@ -415,25 +543,141 @@ const _: () = { ["Offset of field: iommu_hw_info_vtd::ecap_reg"] [::std::mem::offset_of!(iommu_hw_info_vtd, ecap_reg) - 16usize]; }; +#[doc = " struct iommu_hw_info_arm_smmuv3 - ARM SMMUv3 hardware information\n (IOMMU_HW_INFO_TYPE_ARM_SMMUV3)\n\n @flags: Must be set to 0\n @__reserved: Must be 0\n @idr: Implemented features for ARM SMMU Non-secure programming interface\n @iidr: Information about the implementation and implementer of ARM SMMU,\n and architecture version supported\n @aidr: ARM SMMU architecture version\n\n For the details of @idr, @iidr and @aidr, please refer to the chapters\n from 6.3.1 to 6.3.6 in the SMMUv3 Spec.\n\n This reports the raw HW capability, and not all bits are meaningful to be\n read by userspace. Only the following fields should be used:\n\n idr[0]: ST_LEVEL, TERM_MODEL, STALL_MODEL, TTENDIAN , CD2L, ASID16, TTF\n idr[1]: SIDSIZE, SSIDSIZE\n idr[3]: BBML, RIL\n idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K\n\n - S1P should be assumed to be true if a NESTED HWPT can be created\n - VFIO/iommufd only support platforms with COHACC, it should be assumed to be\n true.\n - ATS is a per-device property. If the VMM describes any devices as ATS\n capable in ACPI/DT it should set the corresponding idr.\n\n This list may expand in future (eg E0PD, AIE, PBHA, D128, DS etc). It is\n important that VMMs do not read bits outside the list to allow for\n compatibility with future kernels. Several features in the SMMUv3\n architecture are not currently supported by the kernel for nesting: HTTU,\n BTM, MPAM and others."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hw_info_arm_smmuv3 { + pub flags: __u32, + pub __reserved: __u32, + pub idr: [__u32; 6usize], + pub iidr: __u32, + pub aidr: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hw_info_arm_smmuv3"] + [::std::mem::size_of::() - 40usize]; + ["Alignment of iommu_hw_info_arm_smmuv3"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_hw_info_arm_smmuv3::flags"] + [::std::mem::offset_of!(iommu_hw_info_arm_smmuv3, flags) - 0usize]; + ["Offset of field: iommu_hw_info_arm_smmuv3::__reserved"] + [::std::mem::offset_of!(iommu_hw_info_arm_smmuv3, __reserved) - 4usize]; + ["Offset of field: iommu_hw_info_arm_smmuv3::idr"] + [::std::mem::offset_of!(iommu_hw_info_arm_smmuv3, idr) - 8usize]; + ["Offset of field: iommu_hw_info_arm_smmuv3::iidr"] + [::std::mem::offset_of!(iommu_hw_info_arm_smmuv3, iidr) - 32usize]; + ["Offset of field: iommu_hw_info_arm_smmuv3::aidr"] + [::std::mem::offset_of!(iommu_hw_info_arm_smmuv3, aidr) - 36usize]; +}; +#[doc = " struct iommu_hw_info_tegra241_cmdqv - NVIDIA Tegra241 CMDQV Hardware\n Information (IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV)\n\n @flags: Must be 0\n @version: Version number for the CMDQ-V HW for PARAM bits[03:00]\n @log2vcmdqs: Log2 of the total number of VCMDQs for PARAM bits[07:04]\n @log2vsids: Log2 of the total number of SID replacements for PARAM bits[15:12]\n @__reserved: Must be 0\n\n VMM can use these fields directly in its emulated global PARAM register. Note\n that only one Virtual Interface (VINTF) should be exposed to a VM, i.e. PARAM\n bits[11:08] should be set to 0 for log2 of the total number of VINTFs."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hw_info_tegra241_cmdqv { + pub flags: __u32, + pub version: __u8, + pub log2vcmdqs: __u8, + pub log2vsids: __u8, + pub __reserved: __u8, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hw_info_tegra241_cmdqv"] + [::std::mem::size_of::() - 8usize]; + ["Alignment of iommu_hw_info_tegra241_cmdqv"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_hw_info_tegra241_cmdqv::flags"] + [::std::mem::offset_of!(iommu_hw_info_tegra241_cmdqv, flags) - 0usize]; + ["Offset of field: iommu_hw_info_tegra241_cmdqv::version"] + [::std::mem::offset_of!(iommu_hw_info_tegra241_cmdqv, version) - 4usize]; + ["Offset of field: iommu_hw_info_tegra241_cmdqv::log2vcmdqs"] + [::std::mem::offset_of!(iommu_hw_info_tegra241_cmdqv, log2vcmdqs) - 5usize]; + ["Offset of field: iommu_hw_info_tegra241_cmdqv::log2vsids"] + [::std::mem::offset_of!(iommu_hw_info_tegra241_cmdqv, log2vsids) - 6usize]; + ["Offset of field: iommu_hw_info_tegra241_cmdqv::__reserved"] + [::std::mem::offset_of!(iommu_hw_info_tegra241_cmdqv, __reserved) - 7usize]; +}; +#[doc = " struct iommu_hw_info_amd - AMD IOMMU device info\n\n @efr : Value of AMD IOMMU Extended Feature Register (EFR)\n @efr2: Value of AMD IOMMU Extended Feature 2 Register (EFR2)\n\n Please See description of these registers in the following sections of\n the AMD I/O Virtualization Technology (IOMMU) Specification.\n (https://docs.amd.com/v/u/en-US/48882_3.10_PUB)\n\n - MMIO Offset 0030h IOMMU Extended Feature Register\n - MMIO Offset 01A0h IOMMU Extended Feature 2 Register\n\n Note: The EFR and EFR2 are raw values reported by hardware.\n VMM is responsible to determine the appropriate flags to be exposed to\n the VM since cetertain features are not currently supported by the kernel\n for HW-vIOMMU.\n\n Current VMM-allowed list of feature flags are:\n - EFR[GTSup, GASup, GioSup, PPRSup, EPHSup, GATS, GLX, PASmax]"] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hw_info_amd { + pub efr: __u64, + pub efr2: __u64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hw_info_amd"][::std::mem::size_of::() - 16usize]; + ["Alignment of iommu_hw_info_amd"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hw_info_amd::efr"] + [::std::mem::offset_of!(iommu_hw_info_amd, efr) - 0usize]; + ["Offset of field: iommu_hw_info_amd::efr2"] + [::std::mem::offset_of!(iommu_hw_info_amd, efr2) - 8usize]; +}; pub const iommu_hw_info_type_IOMMU_HW_INFO_TYPE_NONE: iommu_hw_info_type = 0; +pub const iommu_hw_info_type_IOMMU_HW_INFO_TYPE_DEFAULT: iommu_hw_info_type = 0; pub const iommu_hw_info_type_IOMMU_HW_INFO_TYPE_INTEL_VTD: iommu_hw_info_type = 1; -#[doc = " enum iommu_hw_info_type - IOMMU Hardware Info Types\n @IOMMU_HW_INFO_TYPE_NONE: Used by the drivers that do not report hardware\n info\n @IOMMU_HW_INFO_TYPE_INTEL_VTD: Intel VT-d iommu info type"] +pub const iommu_hw_info_type_IOMMU_HW_INFO_TYPE_ARM_SMMUV3: iommu_hw_info_type = 2; +pub const iommu_hw_info_type_IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV: iommu_hw_info_type = 3; +pub const iommu_hw_info_type_IOMMU_HW_INFO_TYPE_AMD: iommu_hw_info_type = 4; +#[doc = " enum iommu_hw_info_type - IOMMU Hardware Info Types\n @IOMMU_HW_INFO_TYPE_NONE: Output by the drivers that do not report hardware\n info\n @IOMMU_HW_INFO_TYPE_DEFAULT: Input to request for a default type\n @IOMMU_HW_INFO_TYPE_INTEL_VTD: Intel VT-d iommu info type\n @IOMMU_HW_INFO_TYPE_ARM_SMMUV3: ARM SMMUv3 iommu info type\n @IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM\n SMMUv3) info type\n @IOMMU_HW_INFO_TYPE_AMD: AMD IOMMU info type"] pub type iommu_hw_info_type = ::std::os::raw::c_uint; -#[doc = " struct iommu_hw_info - ioctl(IOMMU_GET_HW_INFO)\n @size: sizeof(struct iommu_hw_info)\n @flags: Must be 0\n @dev_id: The device bound to the iommufd\n @data_len: Input the length of a user buffer in bytes. Output the length of\n data that kernel supports\n @data_uptr: User pointer to a user-space buffer used by the kernel to fill\n the iommu type specific hardware information data\n @out_data_type: Output the iommu hardware info type as defined in the enum\n iommu_hw_info_type.\n @__reserved: Must be 0\n\n Query an iommu type specific hardware information data from an iommu behind\n a given device that has been bound to iommufd. This hardware info data will\n be used to sync capabilities between the virtual iommu and the physical\n iommu, e.g. a nested translation setup needs to check the hardware info, so\n a guest stage-1 page table can be compatible with the physical iommu.\n\n To capture an iommu type specific hardware information data, @data_uptr and\n its length @data_len must be provided. Trailing bytes will be zeroed if the\n user buffer is larger than the data that kernel has. Otherwise, kernel only\n fills the buffer using the given length in @data_len. If the ioctl succeeds,\n @data_len will be updated to the length that kernel actually supports,\n @out_data_type will be filled to decode the data filled in the buffer\n pointed by @data_uptr. Input @data_len == zero is allowed."] +pub const iommufd_hw_capabilities_IOMMU_HW_CAP_DIRTY_TRACKING: iommufd_hw_capabilities = 1; +pub const iommufd_hw_capabilities_IOMMU_HW_CAP_PCI_PASID_EXEC: iommufd_hw_capabilities = 2; +pub const iommufd_hw_capabilities_IOMMU_HW_CAP_PCI_PASID_PRIV: iommufd_hw_capabilities = 4; +pub const iommufd_hw_capabilities_IOMMU_HW_CAP_PCI_ATS_NOT_SUPPORTED: iommufd_hw_capabilities = 8; +#[doc = " enum iommufd_hw_capabilities\n @IOMMU_HW_CAP_DIRTY_TRACKING: IOMMU hardware support for dirty tracking\n If available, it means the following APIs\n are supported:\n\n IOMMU_HWPT_GET_DIRTY_BITMAP\n IOMMU_HWPT_SET_DIRTY_TRACKING\n\n @IOMMU_HW_CAP_PCI_PASID_EXEC: Execute Permission Supported, user ignores it\n when the struct\n iommu_hw_info::out_max_pasid_log2 is zero.\n @IOMMU_HW_CAP_PCI_PASID_PRIV: Privileged Mode Supported, user ignores it\n when the struct\n iommu_hw_info::out_max_pasid_log2 is zero.\n @IOMMU_HW_CAP_PCI_ATS_NOT_SUPPORTED: ATS is not supported or cannot be used\n on this device (absence implies ATS\n may be enabled)"] +pub type iommufd_hw_capabilities = ::std::os::raw::c_uint; +pub const iommufd_hw_info_flags_IOMMU_HW_INFO_FLAG_INPUT_TYPE: iommufd_hw_info_flags = 1; +#[doc = " enum iommufd_hw_info_flags - Flags for iommu_hw_info\n @IOMMU_HW_INFO_FLAG_INPUT_TYPE: If set, @in_data_type carries an input type\n for user space to request for a specific info"] +pub type iommufd_hw_info_flags = ::std::os::raw::c_uint; +#[doc = " struct iommu_hw_info - ioctl(IOMMU_GET_HW_INFO)\n @size: sizeof(struct iommu_hw_info)\n @flags: Must be 0\n @dev_id: The device bound to the iommufd\n @data_len: Input the length of a user buffer in bytes. Output the length of\n data that kernel supports\n @data_uptr: User pointer to a user-space buffer used by the kernel to fill\n the iommu type specific hardware information data\n @in_data_type: This shares the same field with @out_data_type, making it be\n a bidirectional field. When IOMMU_HW_INFO_FLAG_INPUT_TYPE is\n set, an input type carried via this @in_data_type field will\n be valid, requesting for the info data to the given type. If\n IOMMU_HW_INFO_FLAG_INPUT_TYPE is unset, any input value will\n be seen as IOMMU_HW_INFO_TYPE_DEFAULT\n @out_data_type: Output the iommu hardware info type as defined in the enum\n iommu_hw_info_type.\n @out_capabilities: Output the generic iommu capability info type as defined\n in the enum iommu_hw_capabilities.\n @out_max_pasid_log2: Output the width of PASIDs. 0 means no PASID support.\n PCI devices turn to out_capabilities to check if the\n specific capabilities is supported or not.\n @__reserved: Must be 0\n\n Query an iommu type specific hardware information data from an iommu behind\n a given device that has been bound to iommufd. This hardware info data will\n be used to sync capabilities between the virtual iommu and the physical\n iommu, e.g. a nested translation setup needs to check the hardware info, so\n a guest stage-1 page table can be compatible with the physical iommu.\n\n To capture an iommu type specific hardware information data, @data_uptr and\n its length @data_len must be provided. Trailing bytes will be zeroed if the\n user buffer is larger than the data that kernel has. Otherwise, kernel only\n fills the buffer using the given length in @data_len. If the ioctl succeeds,\n @data_len will be updated to the length that kernel actually supports,\n @out_data_type will be filled to decode the data filled in the buffer\n pointed by @data_uptr. Input @data_len == zero is allowed."] #[repr(C)] -#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[derive(Copy, Clone)] pub struct iommu_hw_info { pub size: __u32, pub flags: __u32, pub dev_id: __u32, pub data_len: __u32, pub data_uptr: __u64, + pub __bindgen_anon_1: iommu_hw_info__bindgen_ty_1, + pub out_max_pasid_log2: __u8, + pub __reserved: [__u8; 3usize], + pub out_capabilities: __u64, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union iommu_hw_info__bindgen_ty_1 { + pub in_data_type: __u32, pub out_data_type: __u32, - pub __reserved: __u32, } #[allow(clippy::unnecessary_operation, clippy::identity_op)] const _: () = { - ["Size of iommu_hw_info"][::std::mem::size_of::() - 32usize]; + ["Size of iommu_hw_info__bindgen_ty_1"] + [::std::mem::size_of::() - 4usize]; + ["Alignment of iommu_hw_info__bindgen_ty_1"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_hw_info__bindgen_ty_1::in_data_type"] + [::std::mem::offset_of!(iommu_hw_info__bindgen_ty_1, in_data_type) - 0usize]; + ["Offset of field: iommu_hw_info__bindgen_ty_1::out_data_type"] + [::std::mem::offset_of!(iommu_hw_info__bindgen_ty_1, out_data_type) - 0usize]; +}; +impl Default for iommu_hw_info__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +impl ::std::fmt::Debug for iommu_hw_info__bindgen_ty_1 { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!(f, "iommu_hw_info__bindgen_ty_1 {{ union }}") + } +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hw_info"][::std::mem::size_of::() - 40usize]; ["Alignment of iommu_hw_info"][::std::mem::align_of::() - 8usize]; ["Offset of field: iommu_hw_info::size"][::std::mem::offset_of!(iommu_hw_info, size) - 0usize]; ["Offset of field: iommu_hw_info::flags"] @@ -444,8 +688,504 @@ const _: () = { [::std::mem::offset_of!(iommu_hw_info, data_len) - 12usize]; ["Offset of field: iommu_hw_info::data_uptr"] [::std::mem::offset_of!(iommu_hw_info, data_uptr) - 16usize]; - ["Offset of field: iommu_hw_info::out_data_type"] - [::std::mem::offset_of!(iommu_hw_info, out_data_type) - 24usize]; + ["Offset of field: iommu_hw_info::out_max_pasid_log2"] + [::std::mem::offset_of!(iommu_hw_info, out_max_pasid_log2) - 28usize]; ["Offset of field: iommu_hw_info::__reserved"] - [::std::mem::offset_of!(iommu_hw_info, __reserved) - 28usize]; + [::std::mem::offset_of!(iommu_hw_info, __reserved) - 29usize]; + ["Offset of field: iommu_hw_info::out_capabilities"] + [::std::mem::offset_of!(iommu_hw_info, out_capabilities) - 32usize]; +}; +impl Default for iommu_hw_info { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +impl ::std::fmt::Debug for iommu_hw_info { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!( + f, + "iommu_hw_info {{ size: {:?}, flags: {:?}, dev_id: {:?}, data_len: {:?}, data_uptr: {:?}, __bindgen_anon_1: {:?}, out_max_pasid_log2: {:?}, __reserved: {:?}, out_capabilities: {:?} }}", + self.size, + self.flags, + self.dev_id, + self.data_len, + self.data_uptr, + self.__bindgen_anon_1, + self.out_max_pasid_log2, + self.__reserved, + self.out_capabilities + ) + } +} +pub const iommufd_hwpt_set_dirty_tracking_flags_IOMMU_HWPT_DIRTY_TRACKING_ENABLE: + iommufd_hwpt_set_dirty_tracking_flags = 1; +pub type iommufd_hwpt_set_dirty_tracking_flags = ::std::os::raw::c_uint; +#[doc = " struct iommu_hwpt_set_dirty_tracking - ioctl(IOMMU_HWPT_SET_DIRTY_TRACKING)\n @size: sizeof(struct iommu_hwpt_set_dirty_tracking)\n @flags: Combination of enum iommufd_hwpt_set_dirty_tracking_flags\n @hwpt_id: HW pagetable ID that represents the IOMMU domain\n @__reserved: Must be 0\n\n Toggle dirty tracking on an HW pagetable."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_set_dirty_tracking { + pub size: __u32, + pub flags: __u32, + pub hwpt_id: __u32, + pub __reserved: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_set_dirty_tracking"] + [::std::mem::size_of::() - 16usize]; + ["Alignment of iommu_hwpt_set_dirty_tracking"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_hwpt_set_dirty_tracking::size"] + [::std::mem::offset_of!(iommu_hwpt_set_dirty_tracking, size) - 0usize]; + ["Offset of field: iommu_hwpt_set_dirty_tracking::flags"] + [::std::mem::offset_of!(iommu_hwpt_set_dirty_tracking, flags) - 4usize]; + ["Offset of field: iommu_hwpt_set_dirty_tracking::hwpt_id"] + [::std::mem::offset_of!(iommu_hwpt_set_dirty_tracking, hwpt_id) - 8usize]; + ["Offset of field: iommu_hwpt_set_dirty_tracking::__reserved"] + [::std::mem::offset_of!(iommu_hwpt_set_dirty_tracking, __reserved) - 12usize]; +}; +pub const iommufd_hwpt_get_dirty_bitmap_flags_IOMMU_HWPT_GET_DIRTY_BITMAP_NO_CLEAR: + iommufd_hwpt_get_dirty_bitmap_flags = 1; +#[doc = " enum iommufd_hwpt_get_dirty_bitmap_flags - Flags for getting dirty bits\n @IOMMU_HWPT_GET_DIRTY_BITMAP_NO_CLEAR: Just read the PTEs without clearing\n any dirty bits metadata. This flag\n can be passed in the expectation\n where the next operation is an unmap\n of the same IOVA range.\n"] +pub type iommufd_hwpt_get_dirty_bitmap_flags = ::std::os::raw::c_uint; +#[doc = " struct iommu_hwpt_get_dirty_bitmap - ioctl(IOMMU_HWPT_GET_DIRTY_BITMAP)\n @size: sizeof(struct iommu_hwpt_get_dirty_bitmap)\n @hwpt_id: HW pagetable ID that represents the IOMMU domain\n @flags: Combination of enum iommufd_hwpt_get_dirty_bitmap_flags\n @__reserved: Must be 0\n @iova: base IOVA of the bitmap first bit\n @length: IOVA range size\n @page_size: page size granularity of each bit in the bitmap\n @data: bitmap where to set the dirty bits. The bitmap bits each\n represent a page_size which you deviate from an arbitrary iova.\n\n Checking a given IOVA is dirty:\n\n data[(iova / page_size) / 64] & (1ULL << ((iova / page_size) % 64))\n\n Walk the IOMMU pagetables for a given IOVA range to return a bitmap\n with the dirty IOVAs. In doing so it will also by default clear any\n dirty bit metadata set in the IOPTE."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_get_dirty_bitmap { + pub size: __u32, + pub hwpt_id: __u32, + pub flags: __u32, + pub __reserved: __u32, + pub iova: __u64, + pub length: __u64, + pub page_size: __u64, + pub data: __u64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_get_dirty_bitmap"] + [::std::mem::size_of::() - 48usize]; + ["Alignment of iommu_hwpt_get_dirty_bitmap"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::size"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, size) - 0usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::hwpt_id"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, hwpt_id) - 4usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::flags"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, flags) - 8usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::__reserved"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, __reserved) - 12usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::iova"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, iova) - 16usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::length"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, length) - 24usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::page_size"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, page_size) - 32usize]; + ["Offset of field: iommu_hwpt_get_dirty_bitmap::data"] + [::std::mem::offset_of!(iommu_hwpt_get_dirty_bitmap, data) - 40usize]; +}; +pub const iommu_hwpt_invalidate_data_type_IOMMU_HWPT_INVALIDATE_DATA_VTD_S1: + iommu_hwpt_invalidate_data_type = 0; +pub const iommu_hwpt_invalidate_data_type_IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3: + iommu_hwpt_invalidate_data_type = 1; +#[doc = " enum iommu_hwpt_invalidate_data_type - IOMMU HWPT Cache Invalidation\n Data Type\n @IOMMU_HWPT_INVALIDATE_DATA_VTD_S1: Invalidation data for VTD_S1\n @IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3: Invalidation data for ARM SMMUv3"] +pub type iommu_hwpt_invalidate_data_type = ::std::os::raw::c_uint; +pub const iommu_hwpt_vtd_s1_invalidate_flags_IOMMU_VTD_INV_FLAGS_LEAF: + iommu_hwpt_vtd_s1_invalidate_flags = 1; +#[doc = " enum iommu_hwpt_vtd_s1_invalidate_flags - Flags for Intel VT-d\n stage-1 cache invalidation\n @IOMMU_VTD_INV_FLAGS_LEAF: Indicates whether the invalidation applies\n to all-levels page structure cache or just\n the leaf PTE cache."] +pub type iommu_hwpt_vtd_s1_invalidate_flags = ::std::os::raw::c_uint; +#[doc = " struct iommu_hwpt_vtd_s1_invalidate - Intel VT-d cache invalidation\n (IOMMU_HWPT_INVALIDATE_DATA_VTD_S1)\n @addr: The start address of the range to be invalidated. It needs to\n be 4KB aligned.\n @npages: Number of contiguous 4K pages to be invalidated.\n @flags: Combination of enum iommu_hwpt_vtd_s1_invalidate_flags\n @__reserved: Must be 0\n\n The Intel VT-d specific invalidation data for user-managed stage-1 cache\n invalidation in nested translation. Userspace uses this structure to\n tell the impacted cache scope after modifying the stage-1 page table.\n\n Invalidating all the caches related to the page table by setting @addr\n to be 0 and @npages to be U64_MAX.\n\n The device TLB will be invalidated automatically if ATS is enabled."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_vtd_s1_invalidate { + pub addr: __u64, + pub npages: __u64, + pub flags: __u32, + pub __reserved: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_vtd_s1_invalidate"] + [::std::mem::size_of::() - 24usize]; + ["Alignment of iommu_hwpt_vtd_s1_invalidate"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hwpt_vtd_s1_invalidate::addr"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1_invalidate, addr) - 0usize]; + ["Offset of field: iommu_hwpt_vtd_s1_invalidate::npages"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1_invalidate, npages) - 8usize]; + ["Offset of field: iommu_hwpt_vtd_s1_invalidate::flags"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1_invalidate, flags) - 16usize]; + ["Offset of field: iommu_hwpt_vtd_s1_invalidate::__reserved"] + [::std::mem::offset_of!(iommu_hwpt_vtd_s1_invalidate, __reserved) - 20usize]; +}; +#[doc = " struct iommu_viommu_arm_smmuv3_invalidate - ARM SMMUv3 cache invalidation\n (IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3)\n @cmd: 128-bit cache invalidation command that runs in SMMU CMDQ.\n Must be little-endian.\n\n Supported command list only when passing in a vIOMMU via @hwpt_id:\n CMDQ_OP_TLBI_NSNH_ALL\n CMDQ_OP_TLBI_NH_VA\n CMDQ_OP_TLBI_NH_VAA\n CMDQ_OP_TLBI_NH_ALL\n CMDQ_OP_TLBI_NH_ASID\n CMDQ_OP_ATC_INV\n CMDQ_OP_CFGI_CD\n CMDQ_OP_CFGI_CD_ALL\n\n -EIO will be returned if the command is not supported."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_viommu_arm_smmuv3_invalidate { + pub cmd: [__le64; 2usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_viommu_arm_smmuv3_invalidate"] + [::std::mem::size_of::() - 16usize]; + ["Alignment of iommu_viommu_arm_smmuv3_invalidate"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_viommu_arm_smmuv3_invalidate::cmd"] + [::std::mem::offset_of!(iommu_viommu_arm_smmuv3_invalidate, cmd) - 0usize]; +}; +#[doc = " struct iommu_hwpt_invalidate - ioctl(IOMMU_HWPT_INVALIDATE)\n @size: sizeof(struct iommu_hwpt_invalidate)\n @hwpt_id: ID of a nested HWPT or a vIOMMU, for cache invalidation\n @data_uptr: User pointer to an array of driver-specific cache invalidation\n data.\n @data_type: One of enum iommu_hwpt_invalidate_data_type, defining the data\n type of all the entries in the invalidation request array. It\n should be a type supported by the hwpt pointed by @hwpt_id.\n @entry_len: Length (in bytes) of a request entry in the request array\n @entry_num: Input the number of cache invalidation requests in the array.\n Output the number of requests successfully handled by kernel.\n @__reserved: Must be 0.\n\n Invalidate iommu cache for user-managed page table or vIOMMU. Modifications\n on a user-managed page table should be followed by this operation, if a HWPT\n is passed in via @hwpt_id. Other caches, such as device cache or descriptor\n cache can be flushed if a vIOMMU is passed in via the @hwpt_id field.\n\n Each ioctl can support one or more cache invalidation requests in the array\n that has a total size of @entry_len * @entry_num.\n\n An empty invalidation request array by setting @entry_num==0 is allowed, and\n @entry_len and @data_uptr would be ignored in this case. This can be used to\n check if the given @data_type is supported or not by kernel."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_invalidate { + pub size: __u32, + pub hwpt_id: __u32, + pub data_uptr: __u64, + pub data_type: __u32, + pub entry_len: __u32, + pub entry_num: __u32, + pub __reserved: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_invalidate"][::std::mem::size_of::() - 32usize]; + ["Alignment of iommu_hwpt_invalidate"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hwpt_invalidate::size"] + [::std::mem::offset_of!(iommu_hwpt_invalidate, size) - 0usize]; + ["Offset of field: iommu_hwpt_invalidate::hwpt_id"] + [::std::mem::offset_of!(iommu_hwpt_invalidate, hwpt_id) - 4usize]; + ["Offset of field: iommu_hwpt_invalidate::data_uptr"] + [::std::mem::offset_of!(iommu_hwpt_invalidate, data_uptr) - 8usize]; + ["Offset of field: iommu_hwpt_invalidate::data_type"] + [::std::mem::offset_of!(iommu_hwpt_invalidate, data_type) - 16usize]; + ["Offset of field: iommu_hwpt_invalidate::entry_len"] + [::std::mem::offset_of!(iommu_hwpt_invalidate, entry_len) - 20usize]; + ["Offset of field: iommu_hwpt_invalidate::entry_num"] + [::std::mem::offset_of!(iommu_hwpt_invalidate, entry_num) - 24usize]; + ["Offset of field: iommu_hwpt_invalidate::__reserved"] + [::std::mem::offset_of!(iommu_hwpt_invalidate, __reserved) - 28usize]; +}; +pub const iommu_hwpt_pgfault_flags_IOMMU_PGFAULT_FLAGS_PASID_VALID: iommu_hwpt_pgfault_flags = 1; +pub const iommu_hwpt_pgfault_flags_IOMMU_PGFAULT_FLAGS_LAST_PAGE: iommu_hwpt_pgfault_flags = 2; +#[doc = " enum iommu_hwpt_pgfault_flags - flags for struct iommu_hwpt_pgfault\n @IOMMU_PGFAULT_FLAGS_PASID_VALID: The pasid field of the fault data is\n valid.\n @IOMMU_PGFAULT_FLAGS_LAST_PAGE: It's the last fault of a fault group."] +pub type iommu_hwpt_pgfault_flags = ::std::os::raw::c_uint; +pub const iommu_hwpt_pgfault_perm_IOMMU_PGFAULT_PERM_READ: iommu_hwpt_pgfault_perm = 1; +pub const iommu_hwpt_pgfault_perm_IOMMU_PGFAULT_PERM_WRITE: iommu_hwpt_pgfault_perm = 2; +pub const iommu_hwpt_pgfault_perm_IOMMU_PGFAULT_PERM_EXEC: iommu_hwpt_pgfault_perm = 4; +pub const iommu_hwpt_pgfault_perm_IOMMU_PGFAULT_PERM_PRIV: iommu_hwpt_pgfault_perm = 8; +#[doc = " enum iommu_hwpt_pgfault_perm - perm bits for struct iommu_hwpt_pgfault\n @IOMMU_PGFAULT_PERM_READ: request for read permission\n @IOMMU_PGFAULT_PERM_WRITE: request for write permission\n @IOMMU_PGFAULT_PERM_EXEC: (PCIE 10.4.1) request with a PASID that has the\n Execute Requested bit set in PASID TLP Prefix.\n @IOMMU_PGFAULT_PERM_PRIV: (PCIE 10.4.1) request with a PASID that has the\n Privileged Mode Requested bit set in PASID TLP\n Prefix."] +pub type iommu_hwpt_pgfault_perm = ::std::os::raw::c_uint; +#[doc = " struct iommu_hwpt_pgfault - iommu page fault data\n @flags: Combination of enum iommu_hwpt_pgfault_flags\n @dev_id: id of the originated device\n @pasid: Process Address Space ID\n @grpid: Page Request Group Index\n @perm: Combination of enum iommu_hwpt_pgfault_perm\n @__reserved: Must be 0.\n @addr: Fault address\n @length: a hint of how much data the requestor is expecting to fetch. For\n example, if the PRI initiator knows it is going to do a 10MB\n transfer, it could fill in 10MB and the OS could pre-fault in\n 10MB of IOVA. It's default to 0 if there's no such hint.\n @cookie: kernel-managed cookie identifying a group of fault messages. The\n cookie number encoded in the last page fault of the group should\n be echoed back in the response message."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_pgfault { + pub flags: __u32, + pub dev_id: __u32, + pub pasid: __u32, + pub grpid: __u32, + pub perm: __u32, + pub __reserved: __u32, + pub addr: __u64, + pub length: __u32, + pub cookie: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_pgfault"][::std::mem::size_of::() - 40usize]; + ["Alignment of iommu_hwpt_pgfault"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hwpt_pgfault::flags"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, flags) - 0usize]; + ["Offset of field: iommu_hwpt_pgfault::dev_id"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, dev_id) - 4usize]; + ["Offset of field: iommu_hwpt_pgfault::pasid"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, pasid) - 8usize]; + ["Offset of field: iommu_hwpt_pgfault::grpid"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, grpid) - 12usize]; + ["Offset of field: iommu_hwpt_pgfault::perm"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, perm) - 16usize]; + ["Offset of field: iommu_hwpt_pgfault::__reserved"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, __reserved) - 20usize]; + ["Offset of field: iommu_hwpt_pgfault::addr"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, addr) - 24usize]; + ["Offset of field: iommu_hwpt_pgfault::length"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, length) - 32usize]; + ["Offset of field: iommu_hwpt_pgfault::cookie"] + [::std::mem::offset_of!(iommu_hwpt_pgfault, cookie) - 36usize]; +}; +pub const iommufd_page_response_code_IOMMUFD_PAGE_RESP_SUCCESS: iommufd_page_response_code = 0; +pub const iommufd_page_response_code_IOMMUFD_PAGE_RESP_INVALID: iommufd_page_response_code = 1; +#[doc = " enum iommufd_page_response_code - Return status of fault handlers\n @IOMMUFD_PAGE_RESP_SUCCESS: Fault has been handled and the page tables\n populated, retry the access. This is the\n \"Success\" defined in PCI 10.4.2.1.\n @IOMMUFD_PAGE_RESP_INVALID: Could not handle this fault, don't retry the\n access. This is the \"Invalid Request\" in PCI\n 10.4.2.1."] +pub type iommufd_page_response_code = ::std::os::raw::c_uint; +#[doc = " struct iommu_hwpt_page_response - IOMMU page fault response\n @cookie: The kernel-managed cookie reported in the fault message.\n @code: One of response code in enum iommufd_page_response_code."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hwpt_page_response { + pub cookie: __u32, + pub code: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hwpt_page_response"] + [::std::mem::size_of::() - 8usize]; + ["Alignment of iommu_hwpt_page_response"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_hwpt_page_response::cookie"] + [::std::mem::offset_of!(iommu_hwpt_page_response, cookie) - 0usize]; + ["Offset of field: iommu_hwpt_page_response::code"] + [::std::mem::offset_of!(iommu_hwpt_page_response, code) - 4usize]; +}; +#[doc = " struct iommu_fault_alloc - ioctl(IOMMU_FAULT_QUEUE_ALLOC)\n @size: sizeof(struct iommu_fault_alloc)\n @flags: Must be 0\n @out_fault_id: The ID of the new FAULT\n @out_fault_fd: The fd of the new FAULT\n\n Explicitly allocate a fault handling object."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_fault_alloc { + pub size: __u32, + pub flags: __u32, + pub out_fault_id: __u32, + pub out_fault_fd: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_fault_alloc"][::std::mem::size_of::() - 16usize]; + ["Alignment of iommu_fault_alloc"][::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_fault_alloc::size"] + [::std::mem::offset_of!(iommu_fault_alloc, size) - 0usize]; + ["Offset of field: iommu_fault_alloc::flags"] + [::std::mem::offset_of!(iommu_fault_alloc, flags) - 4usize]; + ["Offset of field: iommu_fault_alloc::out_fault_id"] + [::std::mem::offset_of!(iommu_fault_alloc, out_fault_id) - 8usize]; + ["Offset of field: iommu_fault_alloc::out_fault_fd"] + [::std::mem::offset_of!(iommu_fault_alloc, out_fault_fd) - 12usize]; +}; +pub const iommu_viommu_type_IOMMU_VIOMMU_TYPE_DEFAULT: iommu_viommu_type = 0; +pub const iommu_viommu_type_IOMMU_VIOMMU_TYPE_ARM_SMMUV3: iommu_viommu_type = 1; +pub const iommu_viommu_type_IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV: iommu_viommu_type = 2; +#[doc = " enum iommu_viommu_type - Virtual IOMMU Type\n @IOMMU_VIOMMU_TYPE_DEFAULT: Reserved for future use\n @IOMMU_VIOMMU_TYPE_ARM_SMMUV3: ARM SMMUv3 driver specific type\n @IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM\n SMMUv3) enabled ARM SMMUv3 type"] +pub type iommu_viommu_type = ::std::os::raw::c_uint; +#[doc = " struct iommu_viommu_tegra241_cmdqv - NVIDIA Tegra241 CMDQV Virtual Interface\n (IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV)\n @out_vintf_mmap_offset: mmap offset argument for VINTF's page0\n @out_vintf_mmap_length: mmap length argument for VINTF's page0\n\n Both @out_vintf_mmap_offset and @out_vintf_mmap_length are reported by kernel\n for user space to mmap the VINTF page0 from the host physical address space\n to the guest physical address space so that a guest kernel can directly R/W\n access to the VINTF page0 in order to control its virtual command queues."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_viommu_tegra241_cmdqv { + pub out_vintf_mmap_offset: __u64, + pub out_vintf_mmap_length: __u64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_viommu_tegra241_cmdqv"] + [::std::mem::size_of::() - 16usize]; + ["Alignment of iommu_viommu_tegra241_cmdqv"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_viommu_tegra241_cmdqv::out_vintf_mmap_offset"] + [::std::mem::offset_of!(iommu_viommu_tegra241_cmdqv, out_vintf_mmap_offset) - 0usize]; + ["Offset of field: iommu_viommu_tegra241_cmdqv::out_vintf_mmap_length"] + [::std::mem::offset_of!(iommu_viommu_tegra241_cmdqv, out_vintf_mmap_length) - 8usize]; +}; +#[doc = " struct iommu_viommu_alloc - ioctl(IOMMU_VIOMMU_ALLOC)\n @size: sizeof(struct iommu_viommu_alloc)\n @flags: Must be 0\n @type: Type of the virtual IOMMU. Must be defined in enum iommu_viommu_type\n @dev_id: The device's physical IOMMU will be used to back the virtual IOMMU\n @hwpt_id: ID of a nesting parent HWPT to associate to\n @out_viommu_id: Output virtual IOMMU ID for the allocated object\n @data_len: Length of the type specific data\n @__reserved: Must be 0\n @data_uptr: User pointer to a driver-specific virtual IOMMU data\n\n Allocate a virtual IOMMU object, representing the underlying physical IOMMU's\n virtualization support that is a security-isolated slice of the real IOMMU HW\n that is unique to a specific VM. Operations global to the IOMMU are connected\n to the vIOMMU, such as:\n - Security namespace for guest owned ID, e.g. guest-controlled cache tags\n - Non-device-affiliated event reporting, e.g. invalidation queue errors\n - Access to a sharable nesting parent pagetable across physical IOMMUs\n - Virtualization of various platforms IDs, e.g. RIDs and others\n - Delivery of paravirtualized invalidation\n - Direct assigned invalidation queues\n - Direct assigned interrupts"] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_viommu_alloc { + pub size: __u32, + pub flags: __u32, + pub type_: __u32, + pub dev_id: __u32, + pub hwpt_id: __u32, + pub out_viommu_id: __u32, + pub data_len: __u32, + pub __reserved: __u32, + pub data_uptr: __u64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_viommu_alloc"][::std::mem::size_of::() - 40usize]; + ["Alignment of iommu_viommu_alloc"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_viommu_alloc::size"] + [::std::mem::offset_of!(iommu_viommu_alloc, size) - 0usize]; + ["Offset of field: iommu_viommu_alloc::flags"] + [::std::mem::offset_of!(iommu_viommu_alloc, flags) - 4usize]; + ["Offset of field: iommu_viommu_alloc::type_"] + [::std::mem::offset_of!(iommu_viommu_alloc, type_) - 8usize]; + ["Offset of field: iommu_viommu_alloc::dev_id"] + [::std::mem::offset_of!(iommu_viommu_alloc, dev_id) - 12usize]; + ["Offset of field: iommu_viommu_alloc::hwpt_id"] + [::std::mem::offset_of!(iommu_viommu_alloc, hwpt_id) - 16usize]; + ["Offset of field: iommu_viommu_alloc::out_viommu_id"] + [::std::mem::offset_of!(iommu_viommu_alloc, out_viommu_id) - 20usize]; + ["Offset of field: iommu_viommu_alloc::data_len"] + [::std::mem::offset_of!(iommu_viommu_alloc, data_len) - 24usize]; + ["Offset of field: iommu_viommu_alloc::__reserved"] + [::std::mem::offset_of!(iommu_viommu_alloc, __reserved) - 28usize]; + ["Offset of field: iommu_viommu_alloc::data_uptr"] + [::std::mem::offset_of!(iommu_viommu_alloc, data_uptr) - 32usize]; +}; +#[doc = " struct iommu_vdevice_alloc - ioctl(IOMMU_VDEVICE_ALLOC)\n @size: sizeof(struct iommu_vdevice_alloc)\n @viommu_id: vIOMMU ID to associate with the virtual device\n @dev_id: The physical device to allocate a virtual instance on the vIOMMU\n @out_vdevice_id: Object handle for the vDevice. Pass to IOMMU_DESTORY\n @virt_id: Virtual device ID per vIOMMU, e.g. vSID of ARM SMMUv3, vDeviceID\n of AMD IOMMU, and vRID of Intel VT-d\n\n Allocate a virtual device instance (for a physical device) against a vIOMMU.\n This instance holds the device's information (related to its vIOMMU) in a VM.\n User should use IOMMU_DESTROY to destroy the virtual device before\n destroying the physical device (by closing vfio_cdev fd). Otherwise the\n virtual device would be forcibly destroyed on physical device destruction,\n its vdevice_id would be permanently leaked (unremovable & unreusable) until\n iommu fd closed."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_vdevice_alloc { + pub size: __u32, + pub viommu_id: __u32, + pub dev_id: __u32, + pub out_vdevice_id: __u32, + pub virt_id: __u64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_vdevice_alloc"][::std::mem::size_of::() - 24usize]; + ["Alignment of iommu_vdevice_alloc"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_vdevice_alloc::size"] + [::std::mem::offset_of!(iommu_vdevice_alloc, size) - 0usize]; + ["Offset of field: iommu_vdevice_alloc::viommu_id"] + [::std::mem::offset_of!(iommu_vdevice_alloc, viommu_id) - 4usize]; + ["Offset of field: iommu_vdevice_alloc::dev_id"] + [::std::mem::offset_of!(iommu_vdevice_alloc, dev_id) - 8usize]; + ["Offset of field: iommu_vdevice_alloc::out_vdevice_id"] + [::std::mem::offset_of!(iommu_vdevice_alloc, out_vdevice_id) - 12usize]; + ["Offset of field: iommu_vdevice_alloc::virt_id"] + [::std::mem::offset_of!(iommu_vdevice_alloc, virt_id) - 16usize]; +}; +#[doc = " struct iommu_ioas_change_process - ioctl(VFIO_IOAS_CHANGE_PROCESS)\n @size: sizeof(struct iommu_ioas_change_process)\n @__reserved: Must be 0\n\n This transfers pinned memory counts for every memory map in every IOAS\n in the context to the current process. This only supports maps created\n with IOMMU_IOAS_MAP_FILE, and returns EINVAL if other maps are present.\n If the ioctl returns a failure status, then nothing is changed.\n\n This API is useful for transferring operation of a device from one process\n to another, such as during userland live update."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_ioas_change_process { + pub size: __u32, + pub __reserved: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_ioas_change_process"] + [::std::mem::size_of::() - 8usize]; + ["Alignment of iommu_ioas_change_process"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_ioas_change_process::size"] + [::std::mem::offset_of!(iommu_ioas_change_process, size) - 0usize]; + ["Offset of field: iommu_ioas_change_process::__reserved"] + [::std::mem::offset_of!(iommu_ioas_change_process, __reserved) - 4usize]; +}; +pub const iommu_veventq_flag_IOMMU_VEVENTQ_FLAG_LOST_EVENTS: iommu_veventq_flag = 1; +#[doc = " enum iommu_veventq_flag - flag for struct iommufd_vevent_header\n @IOMMU_VEVENTQ_FLAG_LOST_EVENTS: vEVENTQ has lost vEVENTs"] +pub type iommu_veventq_flag = ::std::os::raw::c_uint; +#[doc = " struct iommufd_vevent_header - Virtual Event Header for a vEVENTQ Status\n @flags: Combination of enum iommu_veventq_flag\n @sequence: The sequence index of a vEVENT in the vEVENTQ, with a range of\n [0, INT_MAX] where the following index of INT_MAX is 0\n\n Each iommufd_vevent_header reports a sequence index of the following vEVENT:\n\n +----------------------+-------+----------------------+-------+---+-------+\n | header0 {sequence=0} | data0 | header1 {sequence=1} | data1 |...| dataN |\n +----------------------+-------+----------------------+-------+---+-------+\n\n And this sequence index is expected to be monotonic to the sequence index of\n the previous vEVENT. If two adjacent sequence indexes has a delta larger than\n 1, it means that delta - 1 number of vEVENTs has lost, e.g. two lost vEVENTs:\n\n +-----+----------------------+-------+----------------------+-------+-----+\n | ... | header3 {sequence=3} | data3 | header6 {sequence=6} | data6 | ... |\n +-----+----------------------+-------+----------------------+-------+-----+\n\n If a vEVENT lost at the tail of the vEVENTQ and there is no following vEVENT\n providing the next sequence index, an IOMMU_VEVENTQ_FLAG_LOST_EVENTS header\n would be added to the tail, and no data would follow this header:\n\n +--+----------------------+-------+-----------------------------------------+\n |..| header3 {sequence=3} | data3 | header4 {flags=LOST_EVENTS, sequence=4} |\n +--+----------------------+-------+-----------------------------------------+"] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommufd_vevent_header { + pub flags: __u32, + pub sequence: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommufd_vevent_header"][::std::mem::size_of::() - 8usize]; + ["Alignment of iommufd_vevent_header"] + [::std::mem::align_of::() - 4usize]; + ["Offset of field: iommufd_vevent_header::flags"] + [::std::mem::offset_of!(iommufd_vevent_header, flags) - 0usize]; + ["Offset of field: iommufd_vevent_header::sequence"] + [::std::mem::offset_of!(iommufd_vevent_header, sequence) - 4usize]; +}; +pub const iommu_veventq_type_IOMMU_VEVENTQ_TYPE_DEFAULT: iommu_veventq_type = 0; +pub const iommu_veventq_type_IOMMU_VEVENTQ_TYPE_ARM_SMMUV3: iommu_veventq_type = 1; +pub const iommu_veventq_type_IOMMU_VEVENTQ_TYPE_TEGRA241_CMDQV: iommu_veventq_type = 2; +#[doc = " enum iommu_veventq_type - Virtual Event Queue Type\n @IOMMU_VEVENTQ_TYPE_DEFAULT: Reserved for future use\n @IOMMU_VEVENTQ_TYPE_ARM_SMMUV3: ARM SMMUv3 Virtual Event Queue\n @IOMMU_VEVENTQ_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV Extension IRQ"] +pub type iommu_veventq_type = ::std::os::raw::c_uint; +#[doc = " struct iommu_vevent_arm_smmuv3 - ARM SMMUv3 Virtual Event\n (IOMMU_VEVENTQ_TYPE_ARM_SMMUV3)\n @evt: 256-bit ARM SMMUv3 Event record, little-endian.\n Reported event records: (Refer to \"7.3 Event records\" in SMMUv3 HW Spec)\n - 0x04 C_BAD_STE\n - 0x06 F_STREAM_DISABLED\n - 0x08 C_BAD_SUBSTREAMID\n - 0x0a C_BAD_CD\n - 0x10 F_TRANSLATION\n - 0x11 F_ADDR_SIZE\n - 0x12 F_ACCESS\n - 0x13 F_PERMISSION\n\n StreamID field reports a virtual device ID. To receive a virtual event for a\n device, a vDEVICE must be allocated via IOMMU_VDEVICE_ALLOC."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_vevent_arm_smmuv3 { + pub evt: [__le64; 4usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_vevent_arm_smmuv3"][::std::mem::size_of::() - 32usize]; + ["Alignment of iommu_vevent_arm_smmuv3"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_vevent_arm_smmuv3::evt"] + [::std::mem::offset_of!(iommu_vevent_arm_smmuv3, evt) - 0usize]; +}; +#[doc = " struct iommu_vevent_tegra241_cmdqv - Tegra241 CMDQV IRQ\n (IOMMU_VEVENTQ_TYPE_TEGRA241_CMDQV)\n @lvcmdq_err_map: 128-bit logical vcmdq error map, little-endian.\n (Refer to register LVCMDQ_ERR_MAPs per VINTF )\n\n The 128-bit register value from HW exclusively reflect the error bits for a\n Virtual Interface represented by a vIOMMU object. Read and report directly."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_vevent_tegra241_cmdqv { + pub lvcmdq_err_map: [__le64; 2usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_vevent_tegra241_cmdqv"] + [::std::mem::size_of::() - 16usize]; + ["Alignment of iommu_vevent_tegra241_cmdqv"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_vevent_tegra241_cmdqv::lvcmdq_err_map"] + [::std::mem::offset_of!(iommu_vevent_tegra241_cmdqv, lvcmdq_err_map) - 0usize]; +}; +#[doc = " struct iommu_veventq_alloc - ioctl(IOMMU_VEVENTQ_ALLOC)\n @size: sizeof(struct iommu_veventq_alloc)\n @flags: Must be 0\n @viommu_id: virtual IOMMU ID to associate the vEVENTQ with\n @type: Type of the vEVENTQ. Must be defined in enum iommu_veventq_type\n @veventq_depth: Maximum number of events in the vEVENTQ\n @out_veventq_id: The ID of the new vEVENTQ\n @out_veventq_fd: The fd of the new vEVENTQ. User space must close the\n successfully returned fd after using it\n @__reserved: Must be 0\n\n Explicitly allocate a virtual event queue interface for a vIOMMU. A vIOMMU\n can have multiple FDs for different types, but is confined to one per @type.\n User space should open the @out_veventq_fd to read vEVENTs out of a vEVENTQ,\n if there are vEVENTs available. A vEVENTQ will lose events due to overflow,\n if the number of the vEVENTs hits @veventq_depth.\n\n Each vEVENT in a vEVENTQ encloses a struct iommufd_vevent_header followed by\n a type-specific data structure, in a normal case:\n\n +-+---------+-------+---------+-------+-----+---------+-------+-+\n | | header0 | data0 | header1 | data1 | ... | headerN | dataN | |\n +-+---------+-------+---------+-------+-----+---------+-------+-+\n\n unless a tailing IOMMU_VEVENTQ_FLAG_LOST_EVENTS header is logged (refer to\n struct iommufd_vevent_header)."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_veventq_alloc { + pub size: __u32, + pub flags: __u32, + pub viommu_id: __u32, + pub type_: __u32, + pub veventq_depth: __u32, + pub out_veventq_id: __u32, + pub out_veventq_fd: __u32, + pub __reserved: __u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_veventq_alloc"][::std::mem::size_of::() - 32usize]; + ["Alignment of iommu_veventq_alloc"][::std::mem::align_of::() - 4usize]; + ["Offset of field: iommu_veventq_alloc::size"] + [::std::mem::offset_of!(iommu_veventq_alloc, size) - 0usize]; + ["Offset of field: iommu_veventq_alloc::flags"] + [::std::mem::offset_of!(iommu_veventq_alloc, flags) - 4usize]; + ["Offset of field: iommu_veventq_alloc::viommu_id"] + [::std::mem::offset_of!(iommu_veventq_alloc, viommu_id) - 8usize]; + ["Offset of field: iommu_veventq_alloc::type_"] + [::std::mem::offset_of!(iommu_veventq_alloc, type_) - 12usize]; + ["Offset of field: iommu_veventq_alloc::veventq_depth"] + [::std::mem::offset_of!(iommu_veventq_alloc, veventq_depth) - 16usize]; + ["Offset of field: iommu_veventq_alloc::out_veventq_id"] + [::std::mem::offset_of!(iommu_veventq_alloc, out_veventq_id) - 20usize]; + ["Offset of field: iommu_veventq_alloc::out_veventq_fd"] + [::std::mem::offset_of!(iommu_veventq_alloc, out_veventq_fd) - 24usize]; + ["Offset of field: iommu_veventq_alloc::__reserved"] + [::std::mem::offset_of!(iommu_veventq_alloc, __reserved) - 28usize]; +}; +pub const iommu_hw_queue_type_IOMMU_HW_QUEUE_TYPE_DEFAULT: iommu_hw_queue_type = 0; +pub const iommu_hw_queue_type_IOMMU_HW_QUEUE_TYPE_TEGRA241_CMDQV: iommu_hw_queue_type = 1; +#[doc = " enum iommu_hw_queue_type - HW Queue Type\n @IOMMU_HW_QUEUE_TYPE_DEFAULT: Reserved for future use\n @IOMMU_HW_QUEUE_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM\n SMMUv3) Virtual Command Queue (VCMDQ)"] +pub type iommu_hw_queue_type = ::std::os::raw::c_uint; +#[doc = " struct iommu_hw_queue_alloc - ioctl(IOMMU_HW_QUEUE_ALLOC)\n @size: sizeof(struct iommu_hw_queue_alloc)\n @flags: Must be 0\n @viommu_id: Virtual IOMMU ID to associate the HW queue with\n @type: One of enum iommu_hw_queue_type\n @index: The logical index to the HW queue per virtual IOMMU for a multi-queue\n model\n @out_hw_queue_id: The ID of the new HW queue\n @nesting_parent_iova: Base address of the queue memory in the guest physical\n address space\n @length: Length of the queue memory\n\n Allocate a HW queue object for a vIOMMU-specific HW-accelerated queue, which\n allows HW to access a guest queue memory described using @nesting_parent_iova\n and @length.\n\n A vIOMMU can allocate multiple queues, but it must use a different @index per\n type to separate each allocation, e.g::\n\n Type1 HW queue0, Type1 HW queue1, Type2 HW queue0, ..."] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct iommu_hw_queue_alloc { + pub size: __u32, + pub flags: __u32, + pub viommu_id: __u32, + pub type_: __u32, + pub index: __u32, + pub out_hw_queue_id: __u32, + pub nesting_parent_iova: __u64, + pub length: __u64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of iommu_hw_queue_alloc"][::std::mem::size_of::() - 40usize]; + ["Alignment of iommu_hw_queue_alloc"][::std::mem::align_of::() - 8usize]; + ["Offset of field: iommu_hw_queue_alloc::size"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, size) - 0usize]; + ["Offset of field: iommu_hw_queue_alloc::flags"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, flags) - 4usize]; + ["Offset of field: iommu_hw_queue_alloc::viommu_id"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, viommu_id) - 8usize]; + ["Offset of field: iommu_hw_queue_alloc::type_"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, type_) - 12usize]; + ["Offset of field: iommu_hw_queue_alloc::index"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, index) - 16usize]; + ["Offset of field: iommu_hw_queue_alloc::out_hw_queue_id"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, out_hw_queue_id) - 20usize]; + ["Offset of field: iommu_hw_queue_alloc::nesting_parent_iova"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, nesting_parent_iova) - 24usize]; + ["Offset of field: iommu_hw_queue_alloc::length"] + [::std::mem::offset_of!(iommu_hw_queue_alloc, length) - 32usize]; }; diff --git a/iommufd-bindings/src/lib.rs b/iommufd-bindings/src/lib.rs index d2b913f..c07da6a 100644 --- a/iommufd-bindings/src/lib.rs +++ b/iommufd-bindings/src/lib.rs @@ -3,7 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 // -#[allow(non_camel_case_types, non_upper_case_globals)] +#[allow( + non_camel_case_types, + non_upper_case_globals, + clippy::undocumented_unsafe_blocks +)] pub mod iommufd; pub use iommufd::*; From 6324848a4005d3aa66320c1add35266af07a29f1 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Fri, 10 Jul 2026 21:52:47 +0000 Subject: [PATCH 2/7] iommufd-ioctls: Add ioctl wrappers for vIOMMU and vDevice operations The crate only exposes the ioctls needed for plain IOAS based mapping, so a consumer cannot allocate a vIOMMU, bind a vDevice, allocate a nested HWPT, query hardware information or issue invalidations. All of that is required to drive nested translation on behalf of a guest. Assisted-by: Claude:Opus-5 Signed-off-by: Bo Chen Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/src/iommufd_ioctls.rs | 135 +++++++++++++++++++++++++++ iommufd-ioctls/src/lib.rs | 10 ++ 2 files changed, 145 insertions(+) diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index b91a4a7..9729f1e 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -47,9 +47,30 @@ impl IommuFd { pub fn map_iommu_ioas(&self, map: &iommu_ioas_map) -> Result<()> { iommufd_syscall::map_iommu_ioas(self, map) } + pub fn unmap_iommu_ioas(&self, unmap: &mut iommu_ioas_unmap) -> Result<()> { iommufd_syscall::unmap_iommu_ioas(self, unmap) } + + pub fn alloc_iommu_hwpt(&self, hwpt_alloc: &mut iommu_hwpt_alloc) -> Result<()> { + iommufd_syscall::alloc_iommu_hwpt(self, hwpt_alloc) + } + + pub fn get_hw_info(&self, hw_info: &mut iommu_hw_info) -> Result<()> { + iommufd_syscall::get_hw_info(self, hw_info) + } + + pub fn invalidate_hwpt(&self, hwpt_invalidate: &mut iommu_hwpt_invalidate) -> Result<()> { + iommufd_syscall::invalidate_hwpt(self, hwpt_invalidate) + } + + pub fn alloc_iommu_viommu(&self, viommu_alloc: &mut iommu_viommu_alloc) -> Result<()> { + iommufd_syscall::alloc_iommu_viommu(self, viommu_alloc) + } + + pub fn alloc_iommu_vdevice(&self, vdevice_alloc: &mut iommu_vdevice_alloc) -> Result<()> { + iommufd_syscall::alloc_iommu_vdevice(self, vdevice_alloc) + } } impl AsRawFd for IommuFd { @@ -70,6 +91,31 @@ ioctl_io_nr!( IOMMUFD_TYPE as u32, IOMMUFD_CMD_IOAS_UNMAP ); +ioctl_io_nr!( + IOMMU_HWPT_ALLOC, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_HWPT_ALLOC +); +ioctl_io_nr!( + IOMMUFD_GET_HW_INFO, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_GET_HW_INFO +); +ioctl_io_nr!( + IOMMUFD_HWPT_INVALIDATE, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_HWPT_INVALIDATE +); +ioctl_io_nr!( + IOMMU_VIOMMU_ALLOC, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_VIOMMU_ALLOC +); +ioctl_io_nr!( + IOMMU_VDEVICE_ALLOC, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_VDEVICE_ALLOC +); // Safety: // - absolutely trust the underlying kernel @@ -126,6 +172,7 @@ pub(crate) mod iommufd_syscall { Ok(()) } } + pub(crate) fn unmap_iommu_ioas(iommufd: &IommuFd, unmap: &mut iommu_ioas_unmap) -> Result<()> { // SAFETY: // 1. The file descriptor provided by 'iommufd' is valid and open. @@ -139,6 +186,89 @@ pub(crate) mod iommufd_syscall { Ok(()) } } + + pub(crate) fn alloc_iommu_hwpt( + iommufd: &IommuFd, + hwpt_alloc: &mut iommu_hwpt_alloc, + ) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'hwpt_alloc' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = unsafe { ioctl_with_mut_ref(iommufd, IOMMU_HWPT_ALLOC(), hwpt_alloc) }; + if ret < 0 { + Err(IommufdError::IommuHwptAlloc(SysError::last())) + } else { + Ok(()) + } + } + + pub(crate) fn get_hw_info(iommufd: &IommuFd, hw_info: &mut iommu_hw_info) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'hw_info' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = unsafe { ioctl_with_mut_ref(iommufd, IOMMUFD_GET_HW_INFO(), hw_info) }; + if ret < 0 { + Err(IommufdError::IommuGetHwInfo(SysError::last())) + } else { + Ok(()) + } + } + + pub(crate) fn invalidate_hwpt( + iommufd: &IommuFd, + hwpt_invalidate: &mut iommu_hwpt_invalidate, + ) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'hwpt_invalidate' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = + unsafe { ioctl_with_mut_ref(iommufd, IOMMUFD_HWPT_INVALIDATE(), hwpt_invalidate) }; + if ret < 0 { + Err(IommufdError::IommuHwptInvalidate(SysError::last())) + } else { + Ok(()) + } + } + + pub(crate) fn alloc_iommu_viommu( + iommufd: &IommuFd, + viommu_alloc: &mut iommu_viommu_alloc, + ) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'viommu_alloc' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = unsafe { ioctl_with_mut_ref(iommufd, IOMMU_VIOMMU_ALLOC(), viommu_alloc) }; + if ret < 0 { + Err(IommufdError::IommuViommuAlloc(SysError::last())) + } else { + Ok(()) + } + } + + pub(crate) fn alloc_iommu_vdevice( + iommufd: &IommuFd, + vdevice_alloc: &mut iommu_vdevice_alloc, + ) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'vdevice_alloc' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = unsafe { ioctl_with_mut_ref(iommufd, IOMMU_VDEVICE_ALLOC(), vdevice_alloc) }; + if ret < 0 { + Err(IommufdError::IommuVdeviceAlloc(SysError::last())) + } else { + Ok(()) + } + } } #[cfg(test)] @@ -151,5 +281,10 @@ mod tests { assert_eq!(IOMMU_IOAS_ALLOC(), 15233); assert_eq!(IOMMU_IOAS_MAP(), 15237); assert_eq!(IOMMU_IOAS_UNMAP(), 15238); + assert_eq!(IOMMU_HWPT_ALLOC(), 15241); + assert_eq!(IOMMUFD_GET_HW_INFO(), 15242); + assert_eq!(IOMMUFD_HWPT_INVALIDATE(), 15245); + assert_eq!(IOMMU_VIOMMU_ALLOC(), 15248); + assert_eq!(IOMMU_VDEVICE_ALLOC(), 15249); } } diff --git a/iommufd-ioctls/src/lib.rs b/iommufd-ioctls/src/lib.rs index cad0691..658e31a 100644 --- a/iommufd-ioctls/src/lib.rs +++ b/iommufd-ioctls/src/lib.rs @@ -26,6 +26,16 @@ pub enum IommufdError { IommuIoasMap(#[source] SysError), #[error("failed to unmap an IOVA range from the IOAS: {0}")] IommuIoasUnmap(#[source] SysError), + #[error("failed to allocate HWPT: {0}")] + IommuHwptAlloc(#[source] SysError), + #[error("failed to allocate vIOMMU: {0}")] + IommuViommuAlloc(#[source] SysError), + #[error("failed to allocate vDevice: {0}")] + IommuVdeviceAlloc(#[source] SysError), + #[error("failed to get HW info: {0}")] + IommuGetHwInfo(#[source] SysError), + #[error("failed to invalidate HWPT: {0}")] + IommuHwptInvalidate(#[source] SysError), } pub type Result = std::result::Result; From a0c5b06b7fc0aad062d8ac9570cab45d86a6cf07 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 12 Aug 2026 02:42:28 -0700 Subject: [PATCH 3/7] iommufd-ioctls: Add vIOMMU and vDevice abstraction layer The raw ioctl wrappers leave the caller to track object ids, their parent relationships and their teardown order by hand, which is easy to get wrong and has to be repeated by every consumer. Wrap a vIOMMU and its vDevices in owning types so a consumer can drive nested translation for a passthrough device without managing that lifecycle itself. Assisted-by: Claude:Opus-5 Signed-off-by: Bo Chen Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/Cargo.toml | 1 + iommufd-ioctls/src/iommufd_ioctls.rs | 312 +++++++++++++++++++++++++++ iommufd-ioctls/src/lib.rs | 8 + 3 files changed, 321 insertions(+) diff --git a/iommufd-ioctls/Cargo.toml b/iommufd-ioctls/Cargo.toml index 7a5f72f..1913122 100644 --- a/iommufd-ioctls/Cargo.toml +++ b/iommufd-ioctls/Cargo.toml @@ -11,5 +11,6 @@ keywords = ["iommufd"] [dependencies] iommufd-bindings = { version = "0.1.0", path = "../iommufd-bindings" } +log = "0.4" thiserror = "2.0.17" vmm-sys-util = { version = "0.15.0" } diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index 9729f1e..eb45eca 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -5,8 +5,10 @@ use std::fs::{File, OpenOptions}; use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::Arc; use iommufd_bindings::iommufd::*; +use log::error; use vmm_sys_util::errno::Error as SysError; use crate::{IommufdError, Result}; @@ -71,6 +73,316 @@ impl IommuFd { pub fn alloc_iommu_vdevice(&self, vdevice_alloc: &mut iommu_vdevice_alloc) -> Result<()> { iommufd_syscall::alloc_iommu_vdevice(self, vdevice_alloc) } + + pub fn device_hw_info( + &self, + dev_id: u32, + hw_info_data: &mut IommufdHwInfoData, + ) -> Result { + let mut hw_info = match hw_info_data { + IommufdHwInfoData::Smmuv3(data) => iommu_hw_info { + size: std::mem::size_of::() as u32, + dev_id, + data_len: std::mem::size_of::() as u32, + data_uptr: data as *mut _ as u64, + ..Default::default() + }, + }; + + self.get_hw_info(&mut hw_info)?; + + Ok(hw_info) + } +} + +#[derive(Debug, Copy, Clone)] +pub enum IommufdInvalidateData { + Smmuv3(iommu_viommu_arm_smmuv3_invalidate), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IommuKind { + Smmuv3, + Smmuv3Cmdqv, + Unsupported(iommu_hw_info_type), +} + +impl IommuKind { + fn supports_hw_info_type( + iommufd: &IommuFd, + dev_id: u32, + hw_info_type: iommu_hw_info_type, + ) -> bool { + let mut hw_info = iommu_hw_info { + size: std::mem::size_of::() as u32, + flags: iommufd_hw_info_flags_IOMMU_HW_INFO_FLAG_INPUT_TYPE, + dev_id, + __bindgen_anon_1: iommu_hw_info__bindgen_ty_1 { + in_data_type: hw_info_type, + }, + ..Default::default() + }; + + iommufd.get_hw_info(&mut hw_info).is_ok() + } + + #[allow(non_upper_case_globals)] + fn query(iommufd: &IommuFd, dev_id: u32) -> Result { + let mut hw_info = iommu_hw_info { + size: std::mem::size_of::() as u32, + dev_id, + ..Default::default() + }; + iommufd.get_hw_info(&mut hw_info)?; + + // SAFETY: the union holds one `__u32` under either name. + let hw_info_type = unsafe { hw_info.__bindgen_anon_1.out_data_type }; + + match hw_info_type { + iommu_hw_info_type_IOMMU_HW_INFO_TYPE_ARM_SMMUV3 => { + if Self::supports_hw_info_type( + iommufd, + dev_id, + iommu_hw_info_type_IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV, + ) { + Ok(IommuKind::Smmuv3Cmdqv) + } else { + Ok(IommuKind::Smmuv3) + } + } + _ => Ok(IommuKind::Unsupported(hw_info_type)), + } + } +} + +pub struct IommufdVIommu { + iommufd: Arc, + viommu_id: u32, + s2_hwpt_id: u32, + bypass_hwpt_id: u32, + abort_hwpt_id: u32, + kind: IommuKind, +} + +impl IommufdVIommu { + pub fn new(iommufd: Arc, ioas_id: u32, dev_id: u32) -> Result { + let kind = IommuKind::query(&iommufd, dev_id)?; + + let (bypass_s1_hwpt_data, abort_s1_hwpt_data) = match kind { + IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv => { + const SMMU_STE_VALID: u64 = 1 << 0; + const SMMU_STE_CFG_BYPASS: u64 = 1 << 3; + + ( + IommufdHwptData::Smmuv3(iommu_hwpt_arm_smmuv3 { + ste: [SMMU_STE_CFG_BYPASS | SMMU_STE_VALID, 0x0], + }), + IommufdHwptData::Smmuv3(iommu_hwpt_arm_smmuv3 { + ste: [SMMU_STE_VALID, 0x0], + }), + ) + } + IommuKind::Unsupported(hw_info_type) => { + return Err(IommufdError::UnsupportedIommu(hw_info_type)); + } + }; + + let mut s2_iommufd_hwpt_alloc = iommu_hwpt_alloc { + size: std::mem::size_of::() as u32, + flags: iommufd_hwpt_alloc_flags_IOMMU_HWPT_ALLOC_NEST_PARENT, + dev_id, + pt_id: ioas_id, + data_type: iommu_hwpt_data_type_IOMMU_HWPT_DATA_NONE, + ..Default::default() + }; + iommufd.alloc_iommu_hwpt(&mut s2_iommufd_hwpt_alloc)?; + let s2_hwpt_id = s2_iommufd_hwpt_alloc.out_hwpt_id; + + let mut viommu_alloc = iommu_viommu_alloc { + size: std::mem::size_of::() as u32, + type_: iommu_viommu_type_IOMMU_VIOMMU_TYPE_ARM_SMMUV3, + hwpt_id: s2_hwpt_id, + dev_id, + ..Default::default() + }; + iommufd.alloc_iommu_viommu(&mut viommu_alloc)?; + let viommu_id = viommu_alloc.out_viommu_id; + + let bypass_hwpt_id = + Self::alloc_s1_hwpt_on(&iommufd, kind, viommu_id, dev_id, &bypass_s1_hwpt_data)?; + let abort_hwpt_id = + Self::alloc_s1_hwpt_on(&iommufd, kind, viommu_id, dev_id, &abort_s1_hwpt_data)?; + + Ok(IommufdVIommu { + iommufd, + viommu_id, + s2_hwpt_id, + bypass_hwpt_id, + abort_hwpt_id, + kind, + }) + } + + pub fn alloc_s1_hwpt(&self, dev_id: u32, hwpt_data: &IommufdHwptData) -> Result { + Self::alloc_s1_hwpt_on(&self.iommufd, self.kind, self.viommu_id, dev_id, hwpt_data) + } + + fn alloc_s1_hwpt_on( + iommufd: &IommuFd, + kind: IommuKind, + viommu_id: u32, + dev_id: u32, + hwpt_data: &IommufdHwptData, + ) -> Result { + let (data_type, data_len, data_uptr) = match (kind, hwpt_data) { + (IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv, IommufdHwptData::Smmuv3(data)) => ( + iommu_hwpt_data_type_IOMMU_HWPT_DATA_ARM_SMMUV3, + std::mem::size_of::() as u32, + data as *const iommu_hwpt_arm_smmuv3 as u64, + ), + (kind, _) => return Err(IommufdError::IommuTypeMismatch(kind)), + }; + + let mut hwpt_alloc = iommu_hwpt_alloc { + size: std::mem::size_of::() as u32, + dev_id, + pt_id: viommu_id, + data_type, + data_len, + data_uptr, + ..Default::default() + }; + iommufd.alloc_iommu_hwpt(&mut hwpt_alloc)?; + + Ok(hwpt_alloc.out_hwpt_id) + } + + pub fn invalidate(&self, cmd: &mut IommufdInvalidateData) -> Result { + match (self.kind, cmd) { + (IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv, IommufdInvalidateData::Smmuv3(data)) => { + let mut hw_invalidate = iommu_hwpt_invalidate { + size: std::mem::size_of::() as u32, + hwpt_id: self.viommu_id, + data_type: + iommu_hwpt_invalidate_data_type_IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3, + entry_len: std::mem::size_of::() as u32, + entry_num: 1, + data_uptr: data as *mut iommu_viommu_arm_smmuv3_invalidate as u64, + ..Default::default() + }; + self.iommufd.invalidate_hwpt(&mut hw_invalidate)?; + + if hw_invalidate.entry_num == 1 { + Ok(true) + } else { + Ok(false) + } + } + (kind, _) => Err(IommufdError::IommuTypeMismatch(kind)), + } + } +} + +impl Drop for IommufdVIommu { + fn drop(&mut self) { + for (id, what) in [ + (self.bypass_hwpt_id, "bypass_hwpt"), + (self.abort_hwpt_id, "abort_hwpt"), + (self.viommu_id, "vIOMMU"), + (self.s2_hwpt_id, "s2_hwpt"), + ] { + if let Err(e) = self.iommufd.destroy_iommu_object(id) { + error!("Failed to destroy {what} id {id}: {e}"); + } + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum IommufdHwInfoData { + Smmuv3(iommu_hw_info_arm_smmuv3), +} + +#[derive(Debug, Copy, Clone)] +pub enum IommufdHwptData { + Smmuv3(iommu_hwpt_arm_smmuv3), +} + +pub struct IommufdVDevice { + viommu: Arc, + dev_id: u32, + virt_id: u64, + vdevice_id: u32, + s1_hwpt_id: Option, +} + +impl IommufdVDevice { + pub fn new(viommu: Arc, dev_id: u32, virt_id: u64) -> Result { + let mut vdevice_alloc = iommu_vdevice_alloc { + size: std::mem::size_of::() as u32, + viommu_id: viommu.viommu_id, + dev_id, + virt_id, + ..Default::default() + }; + viommu.iommufd.alloc_iommu_vdevice(&mut vdevice_alloc)?; + + Ok(IommufdVDevice { + viommu, + dev_id, + virt_id, + vdevice_id: vdevice_alloc.out_vdevice_id, + s1_hwpt_id: None, + }) + } + + pub fn allocate_s1_hwpt(&mut self, hwpt_data: &IommufdHwptData) -> Result { + if self.s1_hwpt_id.is_some() { + return Err(IommufdError::S1HwptAlreadyAllocated(self.vdevice_id)); + } + + let s1_hwpt_id = self.viommu.alloc_s1_hwpt(self.dev_id, hwpt_data)?; + self.s1_hwpt_id = Some(s1_hwpt_id); + + Ok(s1_hwpt_id) + } + + pub fn destroy_s1_hwpt(&mut self) -> Result<()> { + if let Some(s1_hwpt_id) = self.s1_hwpt_id { + self.viommu.iommufd.destroy_iommu_object(s1_hwpt_id)?; + self.s1_hwpt_id = None; + } + Ok(()) + } + + pub fn hw_info(&self, hw_info_data: &mut IommufdHwInfoData) -> Result { + match (self.viommu.kind, &hw_info_data) { + (IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv, IommufdHwInfoData::Smmuv3(_)) => {} + (kind, _) => return Err(IommufdError::IommuTypeMismatch(kind)), + } + + self.viommu + .iommufd + .device_hw_info(self.dev_id, hw_info_data) + } + + pub fn virt_id(&self) -> u64 { + self.virt_id + } +} + +impl Drop for IommufdVDevice { + fn drop(&mut self) { + if let Some(s1_hwpt_id) = self.s1_hwpt_id + && let Err(e) = self.viommu.iommufd.destroy_iommu_object(s1_hwpt_id) + { + error!("Failed to destroy s1_hwpt id {s1_hwpt_id}: {e}"); + } + + if let Err(e) = self.viommu.iommufd.destroy_iommu_object(self.vdevice_id) { + error!("Failed to destroy vDevice id {}: {e}", self.vdevice_id); + } + } } impl AsRawFd for IommuFd { diff --git a/iommufd-ioctls/src/lib.rs b/iommufd-ioctls/src/lib.rs index 658e31a..5553634 100644 --- a/iommufd-ioctls/src/lib.rs +++ b/iommufd-ioctls/src/lib.rs @@ -14,6 +14,8 @@ pub mod iommufd_ioctls; pub use iommufd_ioctls::*; +use iommufd_bindings::iommufd::*; + #[derive(Debug, Error)] pub enum IommufdError { #[error("failed to open /dev/iommufd: {0}")] @@ -36,6 +38,12 @@ pub enum IommufdError { IommuGetHwInfo(#[source] SysError), #[error("failed to invalidate HWPT: {0}")] IommuHwptInvalidate(#[source] SysError), + #[error("unsupported IOMMU: {0}")] + UnsupportedIommu(iommu_hw_info_type), + #[error("the data does not match the backing IOMMU: {0:?}")] + IommuTypeMismatch(IommuKind), + #[error("S1 HWPT already allocated with for the given vDevice: {0}")] + S1HwptAlreadyAllocated(u32), } pub type Result = std::result::Result; From fea48a2ac48a4547d59eebaac03c1069310b4fc5 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 12 Aug 2026 02:42:56 -0700 Subject: [PATCH 4/7] iommufd-ioctls: Add VEVENTQ allocation and ARM SMMUv3 event decoding Adding support for allocating a VEVENTQ so that we can forward virtual events from the HW IOMMU all the way to the guest. Also adding support for decoding SMMUv3 events specifically. Assisted-by: Claude:Opus-5 Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/Cargo.toml | 1 + iommufd-ioctls/src/iommufd_ioctls.rs | 185 ++++++++++++++++++++++++++- iommufd-ioctls/src/lib.rs | 4 + 3 files changed, 189 insertions(+), 1 deletion(-) diff --git a/iommufd-ioctls/Cargo.toml b/iommufd-ioctls/Cargo.toml index 1913122..307270d 100644 --- a/iommufd-ioctls/Cargo.toml +++ b/iommufd-ioctls/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["iommufd"] [dependencies] iommufd-bindings = { version = "0.1.0", path = "../iommufd-bindings" } +libc = "0.2.39" log = "0.4" thiserror = "2.0.17" vmm-sys-util = { version = "0.15.0" } diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index eb45eca..2d6f0d1 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -4,7 +4,8 @@ // use std::fs::{File, OpenOptions}; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::mem::ManuallyDrop; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::sync::Arc; use iommufd_bindings::iommufd::*; @@ -93,6 +94,21 @@ impl IommuFd { Ok(hw_info) } + + pub fn alloc_veventq(&self, veventq_alloc: &mut iommu_veventq_alloc) -> Result<(u32, File)> { + iommufd_syscall::alloc_veventq(self, veventq_alloc)?; + + // SAFETY: valid fd opened and owned by us. + let file = unsafe { File::from_raw_fd(veventq_alloc.out_veventq_fd as RawFd) }; + + // SAFETY: FFI call on an fd we own and the return value is checked. + let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK) }; + if ret < 0 { + return Err(IommufdError::VeventqNonBlocking(SysError::last())); + } + + Ok((veventq_alloc.out_veventq_id, file)) + } } #[derive(Debug, Copy, Clone)] @@ -281,6 +297,33 @@ impl IommufdVIommu { (kind, _) => Err(IommufdError::IommuTypeMismatch(kind)), } } + + pub fn allocate_veventq(self: &Arc, depth: u32) -> Result { + let veventq_type = match self.kind { + IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv => { + iommu_veventq_type_IOMMU_VEVENTQ_TYPE_ARM_SMMUV3 + } + IommuKind::Unsupported(hw_info_type) => { + return Err(IommufdError::UnsupportedIommu(hw_info_type)); + } + }; + let mut veventq_alloc = iommu_veventq_alloc { + size: std::mem::size_of::() as u32, + viommu_id: self.viommu_id, + type_: veventq_type, + veventq_depth: depth, + ..Default::default() + }; + let (veventq_id, file) = self.iommufd.alloc_veventq(&mut veventq_alloc)?; + + Ok(IommufdVEventQ { + viommu: Arc::clone(self), + veventq_id, + veventq_type, + file: ManuallyDrop::new(file), + last_sequence: None, + }) + } } impl Drop for IommufdVIommu { @@ -298,6 +341,123 @@ impl Drop for IommufdVIommu { } } +#[derive(Debug, Copy, Clone)] +pub enum IommufdVEventData { + Smmuv3(iommu_vevent_arm_smmuv3), +} + +/// A decoded virtual event. +#[derive(Debug, Copy, Clone)] +pub struct IommufdVEvent { + pub header: iommufd_vevent_header, + pub data: Option, + pub lost: u32, +} + +fn vevent_record_len(veventq_type: iommu_veventq_type) -> Option { + if veventq_type == iommu_veventq_type_IOMMU_VEVENTQ_TYPE_ARM_SMMUV3 { + Some(std::mem::size_of::()) + } else { + None + } +} + +fn decode_vevent_record( + veventq_type: iommu_veventq_type, + bytes: &[u8], +) -> Option { + // SAFETY: the records are `#[repr(C)]` PODs with no invalid bit pattern, + // the caller guarantees `bytes` covers one, and `bytes` may be unaligned. + unsafe { + if veventq_type == iommu_veventq_type_IOMMU_VEVENTQ_TYPE_ARM_SMMUV3 { + Some(IommufdVEventData::Smmuv3(std::ptr::read_unaligned( + bytes.as_ptr() as *const iommu_vevent_arm_smmuv3, + ))) + } else { + None + } + } +} + +pub struct IommufdVEventQ { + viommu: Arc, + veventq_id: u32, + veventq_type: iommu_veventq_type, + file: ManuallyDrop, + last_sequence: Option, +} + +impl IommufdVEventQ { + pub fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() + } + + pub fn veventq_type(&self) -> iommu_veventq_type { + self.veventq_type + } + + pub fn read_events(&mut self) -> std::io::Result> { + use std::io::Read; + + const HDR: usize = std::mem::size_of::(); + let rec = vevent_record_len(self.veventq_type).unwrap_or(0); + + let mut buf = vec![0u8; (HDR + rec) * 64]; + let n = self.file.read(&mut buf)?; + + let mut events = Vec::new(); + let mut off = 0; + while off + HDR <= n { + // SAFETY: `iommufd_vevent_header` is a `#[repr(C)]` POD read from + // an inbound slice; `buf` has no alignment guarantee. + let header: iommufd_vevent_header = + unsafe { std::ptr::read_unaligned(buf[off..].as_ptr() as *const _) }; + off += HDR; + + const SEQ_SPAN: u32 = i32::MAX as u32 + 1; + let lost = match self.last_sequence { + Some(prev) => { + (header.sequence.wrapping_sub(prev) & (SEQ_SPAN - 1)).saturating_sub(1) + } + None => 0, + }; + self.last_sequence = Some(header.sequence); + + if header.flags & iommu_veventq_flag_IOMMU_VEVENTQ_FLAG_LOST_EVENTS != 0 { + events.push(IommufdVEvent { + header, + data: None, + lost, + }); + continue; + } + + if off + rec > n { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "vEVENTQ returned a truncated record", + )); + } + let data = decode_vevent_record(self.veventq_type, &buf[off..]); + off += rec; + events.push(IommufdVEvent { header, data, lost }); + } + + Ok(events) + } +} + +impl Drop for IommufdVEventQ { + fn drop(&mut self) { + // SAFETY: `file` is live and never dropped anywhere else. + unsafe { ManuallyDrop::drop(&mut self.file) }; + + if let Err(e) = self.viommu.iommufd.destroy_iommu_object(self.veventq_id) { + error!("Failed to destroy vEVENTQ id {}: {e}", self.veventq_id); + } + } +} + #[derive(Debug, Copy, Clone)] pub enum IommufdHwInfoData { Smmuv3(iommu_hw_info_arm_smmuv3), @@ -428,6 +588,11 @@ ioctl_io_nr!( IOMMUFD_TYPE as u32, IOMMUFD_CMD_VDEVICE_ALLOC ); +ioctl_io_nr!( + IOMMU_VEVENTQ_ALLOC, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_VEVENTQ_ALLOC +); // Safety: // - absolutely trust the underlying kernel @@ -581,6 +746,23 @@ pub(crate) mod iommufd_syscall { Ok(()) } } + + pub(crate) fn alloc_veventq( + iommufd: &IommuFd, + veventq_alloc: &mut iommu_veventq_alloc, + ) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'veventq_alloc' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = unsafe { ioctl_with_mut_ref(iommufd, IOMMU_VEVENTQ_ALLOC(), veventq_alloc) }; + if ret < 0 { + Err(IommufdError::IommuVeventqAlloc(SysError::last())) + } else { + Ok(()) + } + } } #[cfg(test)] @@ -598,5 +780,6 @@ mod tests { assert_eq!(IOMMUFD_HWPT_INVALIDATE(), 15245); assert_eq!(IOMMU_VIOMMU_ALLOC(), 15248); assert_eq!(IOMMU_VDEVICE_ALLOC(), 15249); + assert_eq!(IOMMU_VEVENTQ_ALLOC(), 15251); } } diff --git a/iommufd-ioctls/src/lib.rs b/iommufd-ioctls/src/lib.rs index 5553634..518767b 100644 --- a/iommufd-ioctls/src/lib.rs +++ b/iommufd-ioctls/src/lib.rs @@ -38,6 +38,10 @@ pub enum IommufdError { IommuGetHwInfo(#[source] SysError), #[error("failed to invalidate HWPT: {0}")] IommuHwptInvalidate(#[source] SysError), + #[error("failed to allocate vEVENTQ: {0}")] + IommuVeventqAlloc(#[source] SysError), + #[error("failed to make the vEVENTQ fd non-blocking: {0}")] + VeventqNonBlocking(#[source] SysError), #[error("unsupported IOMMU: {0}")] UnsupportedIommu(iommu_hw_info_type), #[error("the data does not match the backing IOMMU: {0:?}")] From 9e5f98b5133f796463bd4442ca160eae505a70f5 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 12 Aug 2026 02:43:10 -0700 Subject: [PATCH 5/7] iommufd-ioctls: Add stage 1 HWPT install and uninstall Programming nested translation means allocating a stage 1 HWPT, attaching the device to it, and tearing the previous one down. Given the attach operation needs to be performed at VFIO level, we introduced a trait that can be implemented from the vfio crate to attach a device to a HW page table. Assisted-by: Claude:Opus-5 Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/src/iommufd_ioctls.rs | 48 ++++++++++++++++++++++++++-- iommufd-ioctls/src/lib.rs | 2 ++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index 2d6f0d1..9f25f52 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -273,6 +273,20 @@ impl IommufdVIommu { Ok(hwpt_alloc.out_hwpt_id) } + pub fn attach_bypass(&self, device: &dyn AttachHwpt) -> Result<()> { + self.attach(device, self.bypass_hwpt_id) + } + + pub fn attach_abort(&self, device: &dyn AttachHwpt) -> Result<()> { + self.attach(device, self.abort_hwpt_id) + } + + fn attach(&self, device: &dyn AttachHwpt, hwpt_id: u32) -> Result<()> { + device + .attach_hwpt(hwpt_id) + .map_err(IommufdError::AttachHwpt) + } + pub fn invalidate(&self, cmd: &mut IommufdInvalidateData) -> Result { match (self.kind, cmd) { (IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv, IommufdInvalidateData::Smmuv3(data)) => { @@ -468,6 +482,12 @@ pub enum IommufdHwptData { Smmuv3(iommu_hwpt_arm_smmuv3), } +/// A device attachable to an iommufd page table. +pub trait AttachHwpt: Send + Sync { + /// Attach the device to the HW page table. + fn attach_hwpt(&self, pt_id: u32) -> std::io::Result<()>; +} + pub struct IommufdVDevice { viommu: Arc, dev_id: u32, @@ -496,7 +516,7 @@ impl IommufdVDevice { }) } - pub fn allocate_s1_hwpt(&mut self, hwpt_data: &IommufdHwptData) -> Result { + fn allocate_s1_hwpt(&mut self, hwpt_data: &IommufdHwptData) -> Result { if self.s1_hwpt_id.is_some() { return Err(IommufdError::S1HwptAlreadyAllocated(self.vdevice_id)); } @@ -507,7 +527,7 @@ impl IommufdVDevice { Ok(s1_hwpt_id) } - pub fn destroy_s1_hwpt(&mut self) -> Result<()> { + fn destroy_s1_hwpt(&mut self) -> Result<()> { if let Some(s1_hwpt_id) = self.s1_hwpt_id { self.viommu.iommufd.destroy_iommu_object(s1_hwpt_id)?; self.s1_hwpt_id = None; @@ -515,6 +535,30 @@ impl IommufdVDevice { Ok(()) } + pub fn install_s1_hwpt( + &mut self, + device: &dyn AttachHwpt, + hwpt_data: &IommufdHwptData, + ) -> Result<()> { + let s1_hwpt_id = self.allocate_s1_hwpt(hwpt_data)?; + + device + .attach_hwpt(s1_hwpt_id) + .map_err(IommufdError::AttachHwpt)?; + + Ok(()) + } + + pub fn uninstall_s1_hwpt(&mut self, device: &dyn AttachHwpt, abort: bool) -> Result<()> { + if abort { + self.viommu.attach_abort(device)?; + } else { + self.viommu.attach_bypass(device)?; + } + + self.destroy_s1_hwpt() + } + pub fn hw_info(&self, hw_info_data: &mut IommufdHwInfoData) -> Result { match (self.viommu.kind, &hw_info_data) { (IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv, IommufdHwInfoData::Smmuv3(_)) => {} diff --git a/iommufd-ioctls/src/lib.rs b/iommufd-ioctls/src/lib.rs index 518767b..ca81445 100644 --- a/iommufd-ioctls/src/lib.rs +++ b/iommufd-ioctls/src/lib.rs @@ -48,6 +48,8 @@ pub enum IommufdError { IommuTypeMismatch(IommuKind), #[error("S1 HWPT already allocated with for the given vDevice: {0}")] S1HwptAlreadyAllocated(u32), + #[error("failed to attach the device to a page table: {0}")] + AttachHwpt(#[source] io::Error), } pub type Result = std::result::Result; From c3b142beee58e08dc8c633e5c1f26be2f9d5fae4 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 12 Aug 2026 02:44:17 -0700 Subject: [PATCH 6/7] iommufd-ioctls: Add HW_QUEUE and CMDQV support On hardware with NVIDIA's CMDQV extension a guest can own a command queue with direct read/write access to the HW IOMMU. For instance, this feature lets a guest submit invalidations without trapping into the VMM. That's why we extend the ioctls support by allowing a HW queue to be allocated from the IOMMUFD subsystem. Assisted-by: Claude:Opus-5 Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/src/iommufd_ioctls.rs | 148 ++++++++++++++++++++++++++- iommufd-ioctls/src/lib.rs | 8 ++ 2 files changed, 151 insertions(+), 5 deletions(-) diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index 9f25f52..4d6a28c 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -109,6 +109,10 @@ impl IommuFd { Ok((veventq_alloc.out_veventq_id, file)) } + + pub fn alloc_hw_queue(&self, hw_queue_alloc: &mut iommu_hw_queue_alloc) -> Result<()> { + iommufd_syscall::alloc_hw_queue(self, hw_queue_alloc) + } } #[derive(Debug, Copy, Clone)] @@ -171,6 +175,12 @@ impl IommuKind { } } +#[derive(Debug, Copy, Clone)] +pub enum IommufdViommuData { + Smmuv3, + Tegra241Cmdqv { mmap_offset: u64, mmap_length: u64 }, +} + pub struct IommufdVIommu { iommufd: Arc, viommu_id: u32, @@ -178,18 +188,28 @@ pub struct IommufdVIommu { bypass_hwpt_id: u32, abort_hwpt_id: u32, kind: IommuKind, + data: IommufdViommuData, } impl IommufdVIommu { - pub fn new(iommufd: Arc, ioas_id: u32, dev_id: u32) -> Result { + #[allow(non_upper_case_globals)] + pub fn new(iommufd: Arc, ioas_id: u32, dev_id: u32, hw_queue: bool) -> Result { let kind = IommuKind::query(&iommufd, dev_id)?; + if hw_queue && kind != IommuKind::Smmuv3Cmdqv { + return Err(IommufdError::HwQueueUnsupported); + } - let (bypass_s1_hwpt_data, abort_s1_hwpt_data) = match kind { + let (viommu_type, bypass_s1_hwpt_data, abort_s1_hwpt_data) = match kind { IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv => { const SMMU_STE_VALID: u64 = 1 << 0; const SMMU_STE_CFG_BYPASS: u64 = 1 << 3; ( + if hw_queue { + iommu_viommu_type_IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV + } else { + iommu_viommu_type_IOMMU_VIOMMU_TYPE_ARM_SMMUV3 + }, IommufdHwptData::Smmuv3(iommu_hwpt_arm_smmuv3 { ste: [SMMU_STE_CFG_BYPASS | SMMU_STE_VALID, 0x0], }), @@ -216,12 +236,28 @@ impl IommufdVIommu { let mut viommu_alloc = iommu_viommu_alloc { size: std::mem::size_of::() as u32, - type_: iommu_viommu_type_IOMMU_VIOMMU_TYPE_ARM_SMMUV3, - hwpt_id: s2_hwpt_id, + type_: viommu_type, dev_id, + hwpt_id: s2_hwpt_id, ..Default::default() }; - iommufd.alloc_iommu_viommu(&mut viommu_alloc)?; + let data = match viommu_type { + iommu_viommu_type_IOMMU_VIOMMU_TYPE_ARM_SMMUV3 => { + iommufd.alloc_iommu_viommu(&mut viommu_alloc)?; + IommufdViommuData::Smmuv3 + } + iommu_viommu_type_IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV => { + let mut cmdqv_data = iommu_viommu_tegra241_cmdqv::default(); + viommu_alloc.data_len = std::mem::size_of::() as u32; + viommu_alloc.data_uptr = &mut cmdqv_data as *mut iommu_viommu_tegra241_cmdqv as u64; + iommufd.alloc_iommu_viommu(&mut viommu_alloc)?; + IommufdViommuData::Tegra241Cmdqv { + mmap_offset: cmdqv_data.out_vintf_mmap_offset, + mmap_length: cmdqv_data.out_vintf_mmap_length, + } + } + _ => return Err(IommufdError::UnsupportedViommuType(viommu_type)), + }; let viommu_id = viommu_alloc.out_viommu_id; let bypass_hwpt_id = @@ -236,6 +272,7 @@ impl IommufdVIommu { bypass_hwpt_id, abort_hwpt_id, kind, + data, }) } @@ -273,6 +310,14 @@ impl IommufdVIommu { Ok(hwpt_alloc.out_hwpt_id) } + pub fn data(&self) -> IommufdViommuData { + self.data + } + + pub fn iommufd(&self) -> &Arc { + &self.iommufd + } + pub fn attach_bypass(&self, device: &dyn AttachHwpt) -> Result<()> { self.attach(device, self.bypass_hwpt_id) } @@ -312,6 +357,10 @@ impl IommufdVIommu { } } + pub fn hw_queue_support(&self) -> bool { + matches!(self.data, IommufdViommuData::Tegra241Cmdqv { .. }) + } + pub fn allocate_veventq(self: &Arc, depth: u32) -> Result { let veventq_type = match self.kind { IommuKind::Smmuv3 | IommuKind::Smmuv3Cmdqv => { @@ -321,6 +370,22 @@ impl IommufdVIommu { return Err(IommufdError::UnsupportedIommu(hw_info_type)); } }; + self.alloc_veventq(veventq_type, depth) + } + + pub fn allocate_cmdqv_veventq(self: &Arc, depth: u32) -> Result { + let veventq_type = iommu_veventq_type_IOMMU_VEVENTQ_TYPE_TEGRA241_CMDQV; + if !matches!(self.data, IommufdViommuData::Tegra241Cmdqv { .. }) { + return Err(IommufdError::UnsupportedVeventqType(veventq_type)); + } + self.alloc_veventq(veventq_type, depth) + } + + fn alloc_veventq( + self: &Arc, + veventq_type: iommu_veventq_type, + depth: u32, + ) -> Result { let mut veventq_alloc = iommu_veventq_alloc { size: std::mem::size_of::() as u32, viommu_id: self.viommu_id, @@ -338,6 +403,49 @@ impl IommufdVIommu { last_sequence: None, }) } + + pub fn allocate_hw_queue( + self: &Arc, + index: u32, + base_addr: u64, + length: u64, + ) -> Result { + let hw_queue_type = match self.data { + IommufdViommuData::Tegra241Cmdqv { .. } => { + iommu_hw_queue_type_IOMMU_HW_QUEUE_TYPE_TEGRA241_CMDQV + } + IommufdViommuData::Smmuv3 => return Err(IommufdError::HwQueueUnsupported), + }; + + let mut hw_queue_alloc = iommu_hw_queue_alloc { + size: std::mem::size_of::() as u32, + viommu_id: self.viommu_id, + type_: hw_queue_type, + index, + nesting_parent_iova: base_addr, + length, + ..Default::default() + }; + self.iommufd.alloc_hw_queue(&mut hw_queue_alloc)?; + + Ok(IommufdHwQueue { + viommu: Arc::clone(self), + hw_queue_id: hw_queue_alloc.out_hw_queue_id, + }) + } +} + +pub struct IommufdHwQueue { + viommu: Arc, + hw_queue_id: u32, +} + +impl Drop for IommufdHwQueue { + fn drop(&mut self) { + if let Err(e) = self.viommu.iommufd.destroy_iommu_object(self.hw_queue_id) { + error!("Failed to destroy HW queue id {}: {e}", self.hw_queue_id); + } + } } impl Drop for IommufdVIommu { @@ -358,6 +466,7 @@ impl Drop for IommufdVIommu { #[derive(Debug, Copy, Clone)] pub enum IommufdVEventData { Smmuv3(iommu_vevent_arm_smmuv3), + Tegra241Cmdqv(iommu_vevent_tegra241_cmdqv), } /// A decoded virtual event. @@ -371,6 +480,8 @@ pub struct IommufdVEvent { fn vevent_record_len(veventq_type: iommu_veventq_type) -> Option { if veventq_type == iommu_veventq_type_IOMMU_VEVENTQ_TYPE_ARM_SMMUV3 { Some(std::mem::size_of::()) + } else if veventq_type == iommu_veventq_type_IOMMU_VEVENTQ_TYPE_TEGRA241_CMDQV { + Some(std::mem::size_of::()) } else { None } @@ -387,6 +498,10 @@ fn decode_vevent_record( Some(IommufdVEventData::Smmuv3(std::ptr::read_unaligned( bytes.as_ptr() as *const iommu_vevent_arm_smmuv3, ))) + } else if veventq_type == iommu_veventq_type_IOMMU_VEVENTQ_TYPE_TEGRA241_CMDQV { + Some(IommufdVEventData::Tegra241Cmdqv(std::ptr::read_unaligned( + bytes.as_ptr() as *const iommu_vevent_tegra241_cmdqv, + ))) } else { None } @@ -637,6 +752,11 @@ ioctl_io_nr!( IOMMUFD_TYPE as u32, IOMMUFD_CMD_VEVENTQ_ALLOC ); +ioctl_io_nr!( + IOMMU_HW_QUEUE_ALLOC, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_HW_QUEUE_ALLOC +); // Safety: // - absolutely trust the underlying kernel @@ -807,6 +927,23 @@ pub(crate) mod iommufd_syscall { Ok(()) } } + + pub(crate) fn alloc_hw_queue( + iommufd: &IommuFd, + hw_queue_alloc: &mut iommu_hw_queue_alloc, + ) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'hw_queue_alloc' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = unsafe { ioctl_with_mut_ref(iommufd, IOMMU_HW_QUEUE_ALLOC(), hw_queue_alloc) }; + if ret < 0 { + Err(IommufdError::IommuHwQueueAlloc(SysError::last())) + } else { + Ok(()) + } + } } #[cfg(test)] @@ -825,5 +962,6 @@ mod tests { assert_eq!(IOMMU_VIOMMU_ALLOC(), 15248); assert_eq!(IOMMU_VDEVICE_ALLOC(), 15249); assert_eq!(IOMMU_VEVENTQ_ALLOC(), 15251); + assert_eq!(IOMMU_HW_QUEUE_ALLOC(), 15252); } } diff --git a/iommufd-ioctls/src/lib.rs b/iommufd-ioctls/src/lib.rs index ca81445..2babe95 100644 --- a/iommufd-ioctls/src/lib.rs +++ b/iommufd-ioctls/src/lib.rs @@ -42,6 +42,14 @@ pub enum IommufdError { IommuVeventqAlloc(#[source] SysError), #[error("failed to make the vEVENTQ fd non-blocking: {0}")] VeventqNonBlocking(#[source] SysError), + #[error("failed to allocate HW queue: {0}")] + IommuHwQueueAlloc(#[source] SysError), + #[error("hardware queues are not available on this vIOMMU")] + HwQueueUnsupported, + #[error("unsupported vIOMMU type: {0}")] + UnsupportedViommuType(iommu_viommu_type), + #[error("unsupported vEVENTQ type: {0}")] + UnsupportedVeventqType(iommu_veventq_type), #[error("unsupported IOMMU: {0}")] UnsupportedIommu(iommu_hw_info_type), #[error("the data does not match the backing IOMMU: {0:?}")] From 7fd121052858c50f0f670292a6477904ef5098ad Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 12 Aug 2026 05:50:25 -0700 Subject: [PATCH 7/7] iommufd-bindings, iommufd-ioctls: Prepare 0.2.0 and 0.3.0 releases Releasing new versions of the crates so that we can benefit from the new bindings, and so that the ioctls crate now has nested translation capabilities. Assisted-by: Claude:Opus-5 Signed-off-by: Sebastien Boeuf --- iommufd-bindings/CHANGELOG.md | 13 +++++++++++++ iommufd-bindings/Cargo.toml | 2 +- iommufd-ioctls/CHANGELOG.md | 18 ++++++++++++++++++ iommufd-ioctls/Cargo.toml | 4 ++-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/iommufd-bindings/CHANGELOG.md b/iommufd-bindings/CHANGELOG.md index ea21eb3..ebebba9 100644 --- a/iommufd-bindings/CHANGELOG.md +++ b/iommufd-bindings/CHANGELOG.md @@ -7,6 +7,19 @@ ## Fixed +# [v0.2.0] + +## Changed +- Regenerate the bindings from kernel + [v7.1](https://github.com/torvalds/linux/tree/v7.1). + +## Added +- vIOMMU, vDevice, vEVENTQ and HW queue uAPIs, together with the ARM SMMUv3 + hardware info, invalidation and event types. + +## Fixed + + # [v0.1.0] This is the first `iommufd-bindings` crate release. diff --git a/iommufd-bindings/Cargo.toml b/iommufd-bindings/Cargo.toml index f2db44a..30630d4 100644 --- a/iommufd-bindings/Cargo.toml +++ b/iommufd-bindings/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iommufd-bindings" -version = "0.1.0" +version = "0.2.0" authors = ["The Cloud Hypervisor Authors"] license = "Apache-2.0" description = "Rust FFI bindings to iommufd generated using bindgen." diff --git a/iommufd-ioctls/CHANGELOG.md b/iommufd-ioctls/CHANGELOG.md index 726a398..d1f8efc 100644 --- a/iommufd-ioctls/CHANGELOG.md +++ b/iommufd-ioctls/CHANGELOG.md @@ -6,6 +6,24 @@ ## Fixed +# [v0.3.0] + +## Changed +- Require `iommufd-bindings` 0.2.0. + +## Added +- Add `Iommufd` wrappers for the vIOMMU, vDevice, vEVENTQ and HW queue ioctls. +- Add `IommufdVIommu` and `IommufdVDevice`, which own the lifetime of a vIOMMU, + its stage 2 HWPT and the per device stage 1 HWPTs, and expose invalidation and + hardware info queries. +- Add `IommufdVEventQ` and ARM SMMUv3 event decoding. +- Add the `AttachHwpt` trait so a caller can attach a device to a stage 1 + HWPT without depending on how the device was opened. +- Add `IommufdHwQueue` for hardware accelerated command queues, enabled with + the `hw_queue` flag through `IommufdVIommu::new`. + +## Fixed + # [v0.2.0] ## Changed diff --git a/iommufd-ioctls/Cargo.toml b/iommufd-ioctls/Cargo.toml index 307270d..9bc6141 100644 --- a/iommufd-ioctls/Cargo.toml +++ b/iommufd-ioctls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iommufd-ioctls" -version = "0.2.0" +version = "0.3.0" authors = ["The Cloud Hypervisor Authors"] license = "Apache-2.0" description = "Safe wrappers over IOMMUFD uAPIs" @@ -10,7 +10,7 @@ edition = "2024" keywords = ["iommufd"] [dependencies] -iommufd-bindings = { version = "0.1.0", path = "../iommufd-bindings" } +iommufd-bindings = { version = "0.2.0", path = "../iommufd-bindings" } libc = "0.2.39" log = "0.4" thiserror = "2.0.17"