是否可以将物理地址映射为数据片段sk_buff
?我正在开发 Zynq Ultrascale+ 平台(FPGA + ARM SOC)。我将内存缓冲区映射到物理地址。目标是通过 UDP 有效地发送该数据。高效我的意思是零拷贝。我要做的是开发 linux 驱动程序,该驱动程序将该物理地址映射到内核内存并将其sk_buff
作为片段附加到。我开始:
#define PACKET_LEN 1024
struct page *pag;
struct net_device *dev;
struct sk_buff *skb = NULL;
skb = alloc_skb(LL_RESERVED_SPACE(dev) + PACKET_LEN + ip_header_l +
udp_header_l, GFP_ATOMIC);
udp = skb_push(skb, udp_header_l);
//Fill up udp header
...
ip = skb_push(skb, ip_header_l);
//fill up ip header
...
dev_hard_header(skb, dev, ETH_P_IP, addr, myaddr, dev->addr_len);
skb->dev = dev;
//map page with data as fragment
skb_fill_page_desc(skb, 0, pag, 0, PACKET_LEN);
//send data
dev_queue_xmit(skb);
只要页面是由以下人员创建的:
pagebuff = vmalloc(PACKET_LEN);
pag = vmalloc_to_page(pagebuff);
一切正常。数据包被发送。数据包由两个 DMA 事务(Scatter Gather)发送。为了实现我的目标,我将vmalloc
ed page 替换为:
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
membase = devm_ioremap_resource(&pdev->dev, res);
pag = virt_to_page(membase);
物理地址是0xb0000000
并且被映射到虚拟地址0xffffff800ad30000
页面是在0xffffffbf0025e280
。在dev_queue_xmit
数据包进入网络队列并最终被映射为 DMA 之后。swiotlb_map_page
使用0x00ad30000
as时会出现问题phys_addr
,这与 original 不同0xb0000000
。
virt_to_phys
用于swiotlb_map_page
计算物理地址,它基本上采用低 32 位作为物理地址。是否有不同的方法来映射内存区域以便将其用作sk_buff
片段?
作为临时修复,我创建了这样的假页面:
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pag = alloc_page(0); //create fake page
memset(pag, 0, sizeof(struct page));
pag->private = res->start;
并修补了以太网驱动程序以使用页面私有数据作为映射地址:
mapping = skb_frag_page(frag)->private;
if (mapping) {
// printk("macb mapping override to %p\n",mapping);
}
else {
mapping = skb_frag_dma_map(&bp->pdev->dev, frag, offset, size, DMA_TO_DEVICE);
if (dma_mapping_error(&bp->pdev->dev, mapping))
goto dma_error;
}
有了这样的黑客,一切都奏效了。数据由 的内容填充0xb0000000
。虽然它工作正常,但我真的怀疑这是正确的方法。尽管如此,它表明没有硬件限制来做到这一点。有谁知道如何正确映射该内存?
swiotlb_map_page
PS我还尝试以可以计算正确地址的方式将物理地址映射到固定虚拟地址(并且virt_to_phys
确实如此),但它以“无法在虚拟地址处理内核分页请求”错误结束。
membase = phys_to_virt(res->start);
i = ioremap_page_range(membase, membase + resource_size(res),
res->start, PAGE_KERNEL);
//I tried both
pag = phys_to_page(res->start);
pag = virt_to_page(membase);
也许我正在寻找错误地址的页面,或者它不存在。谁能指出我正确的方向。有没有办法在没有这种讨厌的黑客攻击的情况下实现目标?