Linux: 如何确定ioremap返回的对应的DMA地址
Linux: how to determine the DMA address corresponding to what's returned from ioremap
假设我有一些物理地址(这是我的 DMA 源外设的总线位置):
phys_addr = 0xffff0000;
我也是:
virt_addr = ioremap(phys_addr, PAGE_SIZE);
这样做之后,我不再可以直接访问 phys_addr
(在我的情况下,我没有访问权限,因为我正在编写的驱动程序仅通过 virt_addr
)。
我第一次失败的尝试:
wrong_phys_addr = virt_to_dma(dev,virt_addr);
顺便说一句,我不知道为什么设备 dev
与此查找相关...
这当然是行不通的,因为 virt_addr
不是直接映射的内核内存。 DMA-API 似乎暗示所有类似 virt_to_bus
的翻译都已弃用,每个人都应该使用 dma_map_single
等来获取 DMA 总线地址。所以我接下来尝试了这个:
wrong_phys_addr = dma_map_single(dev, virt_addr, size, DMA_FROM_DEVICE);
还是不行。当然,如果我直接使用 0xffff0000
作为 device_prep_dma_memcpy
的源 DMA 地址,一切都很好——但我还是不能这样做,因为我无法直接访问它。我能想到的获得它的唯一可能方法是遍历内核的页表……我真的不想手动执行此操作。有没有更好的方法?
更新
struct page *kmap_to_page(void * vaddr)
看起来很有希望。我不确定从 ioremap 返回的虚拟地址是否适用于此。另一种可能是
struct page* vmalloc_to_page(void* vmalloc_addr)
我需要进一步调查。
看起来 vmalloc_to_page
正是我要找的。如果我这样做:
phys_addr = page_to_phys(vmalloc_to_page(virt_addr)))
这会产生,phys_addr == 0xffff0000
。
按照 CL. 的建议,我还尝试执行以下所有操作:
wrong_phy_addr1 = dma_map_single(dev, 0xffff0000, size, DMA_FROM_DEVICE);
wrong_phy_addr2 = dma_map_single(dev, virt_addr, size, DMA_FROM_DEVICE);
correct_phy_addr = phys_to_dma(dev, 0xffff0000);
在 运行 之后我得到:
wrong_phy_addr1 == 7fff0000
wrong_phy_addr2 == 4091e000
correct_phy_addr == ffff0000
因此,看起来 phys_to_dma()
在这里获胜。使用这种方法比使用原始物理地址更可取,因为(如 CL. 建议的那样)物理地址不一定是 DMA 地址——在我的板上它们恰好是相同的。
总之,这是我原来问题的答案:
dma_addr = phys_to_dma(dev, page_to_phys(vmalloc_to_page(virt_addr));
话虽这么说,但如果我的驱动程序可以使用 phys_addr (0xffff0000),则可以避免这种额外的查找 - 我会考虑更改接口。
在接受我自己的回答之前,我会给其他人一些时间来提供他们自己的(更好的?)答案。
假设我有一些物理地址(这是我的 DMA 源外设的总线位置):
phys_addr = 0xffff0000;
我也是:
virt_addr = ioremap(phys_addr, PAGE_SIZE);
这样做之后,我不再可以直接访问 phys_addr
(在我的情况下,我没有访问权限,因为我正在编写的驱动程序仅通过 virt_addr
)。
我第一次失败的尝试:
wrong_phys_addr = virt_to_dma(dev,virt_addr);
顺便说一句,我不知道为什么设备 dev
与此查找相关...
这当然是行不通的,因为 virt_addr
不是直接映射的内核内存。 DMA-API 似乎暗示所有类似 virt_to_bus
的翻译都已弃用,每个人都应该使用 dma_map_single
等来获取 DMA 总线地址。所以我接下来尝试了这个:
wrong_phys_addr = dma_map_single(dev, virt_addr, size, DMA_FROM_DEVICE);
还是不行。当然,如果我直接使用 0xffff0000
作为 device_prep_dma_memcpy
的源 DMA 地址,一切都很好——但我还是不能这样做,因为我无法直接访问它。我能想到的获得它的唯一可能方法是遍历内核的页表……我真的不想手动执行此操作。有没有更好的方法?
更新
struct page *kmap_to_page(void * vaddr)
看起来很有希望。我不确定从 ioremap 返回的虚拟地址是否适用于此。另一种可能是
struct page* vmalloc_to_page(void* vmalloc_addr)
我需要进一步调查。
看起来 vmalloc_to_page
正是我要找的。如果我这样做:
phys_addr = page_to_phys(vmalloc_to_page(virt_addr)))
这会产生,phys_addr == 0xffff0000
。
按照 CL. 的建议,我还尝试执行以下所有操作:
wrong_phy_addr1 = dma_map_single(dev, 0xffff0000, size, DMA_FROM_DEVICE);
wrong_phy_addr2 = dma_map_single(dev, virt_addr, size, DMA_FROM_DEVICE);
correct_phy_addr = phys_to_dma(dev, 0xffff0000);
在 运行 之后我得到:
wrong_phy_addr1 == 7fff0000
wrong_phy_addr2 == 4091e000
correct_phy_addr == ffff0000
因此,看起来 phys_to_dma()
在这里获胜。使用这种方法比使用原始物理地址更可取,因为(如 CL. 建议的那样)物理地址不一定是 DMA 地址——在我的板上它们恰好是相同的。
总之,这是我原来问题的答案:
dma_addr = phys_to_dma(dev, page_to_phys(vmalloc_to_page(virt_addr));
话虽这么说,但如果我的驱动程序可以使用 phys_addr (0xffff0000),则可以避免这种额外的查找 - 我会考虑更改接口。
在接受我自己的回答之前,我会给其他人一些时间来提供他们自己的(更好的?)答案。