Search code examples
linux-kernelkernel-moduledmaiommu

Create an IOMMU entry in Linux


I've been browsing through the Linux IOMMU code for quite a while now and couldn't find an easy approach to directly create an IOMMU entry.

I want to specify the physical address (maybe also the virtual but it is not necessary) and the device. The range should be inserted into the IOMMU and the virt address printed through printk.

I am searching for a function that lets me easily do it.

Thanks


Solution

  • 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("[email protected]");
    MODULE_DESCRIPTION("Alloc IOMMU entry");