是否可以将物理地址映射为sk_buff
中的数据片段?
我正在Zynq Ultrascale +平台(FPGA + ARM SOC)上工作。我有映射到物理地址的内存缓冲区。目的是通过UDP有效地发送该数据。有效地是指零复制。我要做的是开发将那个物理地址映射到内核内存并将其作为片段附加到sk_buff
的linux驱动程序。
我开始:
#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页面替换为:
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
作为phys_addr
时出现问题,这与原始的 0xb0000000
不同。
virt_to_phys
中的swiotlb_map_page
用于计算物理地址,它基本上将低32位用作phys地址。映射内存区域是否有其他方法,以便可以用作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
的内容。尽管效果很好,但我真的怀疑这是正确的方法。但是,它表明没有硬件限制可以做到这一点。有人知道如何正确映射该内存吗?
P.S。我还尝试将物理地址映射到固定的虚拟地址,使得swiotlb_map_page
将计算正确的地址(而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);
也许我正在寻找错误地址的页面,或者它不存在。 谁能指出我正确的方向。没有这种讨厌的骇客,有没有办法实现目标?