Check for Iommu Support on Linux

check for IOMMU support on linux

Since 2014 enabled iommu are registered in /sys (sysfs) special file system as class iommu (documented at ABI/testing/sysfs-class-iommu):
https://patchwork.kernel.org/patch/4345491/ "[2/3] iommu/intel: Make use of IOMMU sysfs support" - June 12, 2014

Register our DRHD IOMMUs, cross link devices, and provide a base set
of attributes for the IOMMU. ...
On a typical desktop system, this provides the following (pruned):

$ find /sys | grep dmar
/sys/devices/virtual/iommu/dmar0
...
/sys/class/iommu/dmar0
/sys/class/iommu/dmar1

The code is iommu_device_create (http://elixir.free-electrons.com/linux/v4.5/ident/iommu_device_create, around 4.5) or iommu_device_sysfs_add (http://elixir.free-electrons.com/linux/v4.11/ident/iommu_device_sysfs_add) in more recent kernels.

/*
* Create an IOMMU device and return a pointer to it. IOMMU specific
* attributes can be provided as an attribute group, allowing a unique
* namespace per IOMMU type.
*/
struct device *iommu_device_create(struct device *parent, void *drvdata,
const struct attribute_group **groups,
const char *fmt, ...)

Registration is done only for enabled IOMMU. DMAR:

if (intel_iommu_enabled) {
iommu->iommu_dev = iommu_device_create(NULL, iommu,
intel_iommu_groups,
"%s", iommu->name);

AMD IOMMU:

static int iommu_init_pci(struct amd_iommu *iommu)
{ ...
if (!iommu->dev)
return -ENODEV;
...
iommu->iommu_dev = iommu_device_create(&iommu->dev->dev, iommu,
amd_iommu_groups, "ivhd%d",
iommu->index);

Intel:

int __init intel_iommu_init(void)
{ ...
pr_info("Intel(R) Virtualization Technology for Directed I/O\n");
...
for_each_active_iommu(iommu, drhd)
iommu->iommu_dev = iommu_device_create(NULL, iommu,
intel_iommu_groups,
"%s", iommu->name);

With 4.11 linux kernel version iommu_device_sysfs_add is referenced in many IOMMU drivers, so checking /sys/class/iommu is better (more universal) way to programmatically detect enabled IOMMU than parsing dmesg output or searching in /var/log/kern.log or /var/log/messages for driver-specific enable messages:

Referenced in 10 files:

  • drivers/iommu/amd_iommu_init.c, line 1640
  • drivers/iommu/arm-smmu-v3.c, line 2709
  • drivers/iommu/arm-smmu.c, line 2163
  • drivers/iommu/dmar.c, line 1083
  • drivers/iommu/exynos-iommu.c, line 623
  • drivers/iommu/intel-iommu.c, line 4878
  • drivers/iommu/iommu-sysfs.c, line 57
  • drivers/iommu/msm_iommu.c, line 797
  • drivers/iommu/mtk_iommu.c, line 581

Create an IOMMU entry in Linux

I ended up with a pretty hacky solution, not the optimal one, but it worked for my usecase. Adjusted the function iommu_dma_map_page in dma-iommu.c to look like the following and export it.

(vanilla 5.18 except for this modification)

dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
bool coherent = dev_is_dma_coherent(dev);
int prot = dma_info_to_prot(dir, coherent, attrs);
struct iommu_domain *domain = iommu_get_dma_domain(dev);
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iova_domain *iovad = &cookie->iovad;
dma_addr_t iova, dma_mask = dma_get_mask(dev);
phys_addr_t phys;
if (page->flags == 0xF0F0F0F0F0F0F) {
phys = page->dma_addr;
} else {
phys = page_to_phys(page) + offset;
}

/*
* If both the physical buffer start address and size are
* page aligned, we don't need to use a bounce page.
*/
if (dev_use_swiotlb(dev) && iova_offset(iovad, phys | size)) {
void *padding_start;
size_t padding_size, aligned_size;

aligned_size = iova_align(iovad, size);
phys = swiotlb_tbl_map_single(dev, phys, size, aligned_size,
iova_mask(iovad), dir, attrs);

if (phys == DMA_MAPPING_ERROR)
return DMA_MAPPING_ERROR;

/* Cleanup the padding area. */
padding_start = phys_to_virt(phys);
padding_size = aligned_size;

if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC) &&
(dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL)) {
padding_start += size;
padding_size -= size;
}

memset(padding_start, 0, padding_size);
}

if (!coherent && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
arch_sync_dma_for_device(phys, size, dir);

iova = __iommu_dma_map(dev, phys, size, prot, dma_mask);
if (iova == DMA_MAPPING_ERROR && is_swiotlb_buffer(dev, phys))
swiotlb_tbl_unmap_single(dev, phys, size, dir, attrs);
return iova;
}
EXPORT_SYMBOL(iommu_dma_map_page);

Then use the following kernel module to program the entry. This could be also extended and programmed in a more usable manner, but for prototyping, it should be enough.

#include <linux/init.h>
#include <asm/io.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>

extern dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size, enum dma_data_direction dir,
unsigned long attrs);

int magic_value = 0xF0F0F0F0F0F0F;

struct page page_ = {
.flags = 0xF0F0F0F0F0F0F,
.dma_addr = 0x0000002f000f0000,
};

static int my_init(void)
{
dma_addr_t dma_addr;
struct pci_dev *dummy = pci_get_device(0x10EE, 0x0666, NULL);
if (dummy != NULL)
{
printk(KERN_INFO "module loaded.\n");
dma_addr = iommu_dma_map_page(&(dummy->dev), &page_, 0, 4096, DMA_BIDIRECTIONAL, DMA_ATTR_SKIP_CPU_SYNC);
printk(KERN_INFO "DMA_addr: %llx", dma_addr);
}
else
{
printk("Error getting device");
}

return 0;
}

static void my_exit(void)
{
printk(KERN_INFO "iommu_alloc unloaded.\n");

return;
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("benedict.schlueter@inf.ethz.ch");
MODULE_DESCRIPTION("Alloc IOMMU entry");


Related Topics



Leave a reply



Submit