问题描述
问题描述:
设备寄存器中指定地址范围为32bit,实际dma申请到的内存地址,超过32bit
系统:
sw_64的kylin系统
linux-4.19.90-2003.4.0.0036.ky3.kb27.sw_64
配置改动,打开了EHCI和UVC的功能
参看 <巨人的肩膀> linux系统,开启驱动模块
日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14//file drivers/usb/host/ehci-q.c
//function submit_async
struct usb_hcd hcd = ehci_to_hcd(ehci);
printk("qh:%p, async:%p, hw:%p, dma:%llx",qh, ehci->async, ehci->async->hw, ehci->async->qh_dma);
printk("self.sysdev:%p, dma_ops:%p,coherent_dma_mask%llx,dma_pfn_offset:%lx,r_ops:%p",
hcd->self.sysdev,
hcd->self.sysdev->dma_ops,
hcd->self.sysdev->coherent_dma_mask,
hcd->self.sysdev->dma_pfn_offset,
&dma_direct_ops);
if (hcd->self.controller->dma_mask)
printk("mask:%llx",*hcd->self.controller->dma_mask);
else
printk("no mask null");详细日志如下
1
2
3
4
5
6
7
8
9
10
11
12[ 101.130000] usb usb5: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 4.19
[ 101.130000] usb usb5: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[ 101.130000] usb usb5: Product: EHCI Host Controller
[ 101.130000] usb usb5: Manufacturer: Linux 4.19.90-2003.4.0.0036.ky3.kb28.sw_64-qemu ehci_hcd
[ 101.130000] usb usb5: SerialNumber: 0000:08:00.0
[ 101.130000] hub 5-0:1.0: USB hub found
[ 101.130000] hub 5-0:1.0: 15 ports detected
[ 106.850000] usb 5-6: new high-speed USB device number 2 using ehci-pci
[ 106.850000] qh:00000000ee1ffd7a, async:00000000e48cc161, hw:00000000098c4365, dma:1e3814000
[ 106.850000] self.sysdev:00000000b2db70ff, dma_ops: (null),coherent_dma_maskffffffff,dma_pfn_offset:0,r_ops:00000000bc999dce
[ 106.850000] mask:ffffffff
[ 112.090000] usb 5-6: device descriptor read/64, error -60
排查
尝试设置32bit,确认正确设置了dma的地址掩码
- ehci_caps::hcc_params ,这是一个32bit标志,最后1bit 表示是否支持64-bit地址
- 查看hcd-ehci的代码,发现只在发现支持64的时候,设置为64。尝试修改,在else中设置32bit(此时怀疑默认值是64)
1
2
3
4}else{
printk("ehci enabled 32bit DMA\n");
dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32));
} - 修改后,尝试无效。
- 去除这段代码,dma_mask依然是0xffffffff,所以默认就是32bit不需要修改
排查过程用到的数据结构简单整理
classDiagram class device{ +numa_node +dma_mask +dma_pool +id } class pci_dev{ +dma_mask +sysdata } class usb_bus { +sysdev +__usb_create_hcd() } class dma_map_ops{ alloc() free() map_page() unmap_page() map_sg() unmap_sg() dma_supported() mapping_error() } device o-- dma_map_ops dma_map_ops --|> sw64_dma_direct_ops dma_map_ops --|> dma_direct_ops device --|> pci_dev pci_dev "1" .. "1" dma_map_ops pci_dev o-- pci_controller device --|> usb_bus usb_bus o-- pci_dev device --|> usb_device usb_device o-- usb_bus usb_bus --|> usb_hcd usb_hcd --|> ehci_hcd
查看 ehci_qh_alloc
- 首先是ehci中,内存分配的逻辑如下 获取的vaddr 给了qh->hw, dma_addr 给了 qh->qh_dma
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//file drivers/usb/host/ehci-mem.c
static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, gfp_t flags)
{
struct ehci_qh *qh;
dma_addr_t dma;
qh = kzalloc(sizeof *qh, GFP_ATOMIC);
if (!qh)
goto done;
qh->hw = (struct ehci_qh_hw *)
dma_pool_alloc(ehci->qh_pool, flags, &dma);
if (!qh->hw)
goto fail;
memset(qh->hw, 0, sizeof *qh->hw);
qh->qh_dma = dma;
//...
}
查看 dma_pool_alloc
- 通过 pool_alloc_page 获取page,从page中获取虚拟内存地址和dma地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77// file mm/dmapool.c
/**
* dma_pool_alloc - get a block of consistent memory
* @pool: dma pool that will produce the block
* @mem_flags: GFP_* bitmask
* @handle: pointer to dma address of block
*
* Return: the kernel virtual address of a currently unused block,
* and reports its dma address through the handle.
* If such a memory block can't be allocated, %NULL is returned.
*/
void *dma_pool_alloc(struct dma_pool *pool, gfp_t mem_flags,
dma_addr_t *handle)
{
unsigned long flags;
struct dma_page *page;
size_t offset;
void *retval;
might_alloc(mem_flags);
spin_lock_irqsave(&pool->lock, flags);
list_for_each_entry(page, &pool->page_list, page_list) {
if (page->offset < pool->allocation)
goto ready;
}
/* pool_alloc_page() might sleep, so temporarily drop &pool->lock */
spin_unlock_irqrestore(&pool->lock, flags);
page = pool_alloc_page(pool, mem_flags & (~__GFP_ZERO));
if (!page)
return NULL;
spin_lock_irqsave(&pool->lock, flags);
list_add(&page->page_list, &pool->page_list);
ready:
page->in_use++;
offset = page->offset;
page->offset = *(int *)(page->vaddr + offset);
retval = offset + page->vaddr;
*handle = offset + page->dma;
{
int i;
u8 *data = retval;
/* page->offset is stored in first 4 bytes */
for (i = sizeof(page->offset); i < pool->size; i++) {
if (data[i] == POOL_POISON_FREED)
continue;
if (pool->dev)
dev_err(pool->dev, "%s %s, %p (corrupted)\n",
__func__, pool->name, retval);
else
pr_err("%s %s, %p (corrupted)\n",
__func__, pool->name, retval);
/*
* Dump the first 4 bytes even if they are not
* POOL_POISON_FREED
*/
print_hex_dump(KERN_ERR, "", DUMP_PREFIX_OFFSET, 16, 1,
data, pool->size, 1);
break;
}
}
if (!(mem_flags & __GFP_ZERO))
memset(retval, POOL_POISON_ALLOCATED, pool->size);
spin_unlock_irqrestore(&pool->lock, flags);
if (want_init_on_alloc(mem_flags))
memset(retval, 0, pool->size);
return retval;
}
查看 pool_alloc_page
- 申请一个page
- page的 vaddr和 dma 通过 dma_alloc_coherent 函数获取
1 | // file mm/dmapool.c |
查看 dma_alloc_coherent
dma_alloc_coherent ==> dma_alloc_attrs
尝试通过dma_alloc_from_dev_coherent获取dma_handle 和cpu_addr
失败的话 就通过 ops->alloc 获取 dma_handle 和 cpu_addr
先看dma_alloc_from_dev_coherent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//file include/linux/dma-mapping.h
static inline void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag)
{
return dma_alloc_attrs(dev, size, dma_handle, flag, 0);
}
static inline void *dma_alloc_attrs(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag,
unsigned long attrs)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
void *cpu_addr;
BUG_ON(!ops);
WARN_ON_ONCE(dev && !dev->coherent_dma_mask);
if (dma_alloc_from_dev_coherent(dev, size, dma_handle, &cpu_addr))
return cpu_addr;
/* let the implementation decide on the zone to allocate from: */
flag &= ~(__GFP_DMA | __GFP_DMA32 | __GFP_HIGHMEM);
if (!arch_dma_alloc_attrs(&dev))
return NULL;
if (!ops->alloc)
return NULL;
cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs);
debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr);
return cpu_addr;
}dma_alloc_from_dev_coherent 需要CONFIG_HAVE_GENERIC_DMA_COHERENT配置支持
检查menuconfig,发现没有配置
1 |
|
- 查看ops的实例是哪个
- 检查menuconfig CONFIG_HAS_DMA 配置开启
- 没找到调用set_dma_ops的地方
- 所以dev->dma_ops可能是NULL
- 通过日志打印,确认dma_ops 是NULL,所以这里用的是get_arch_dma_ops
1 | //file include/linux/dma-mapping.h |
排查 get_arch_dma_ops
- 确认 CONFIG_DMA_NONCOHERENT_OPS 未开启
- 查看 dma_direct_ops
1
2
3
4
5
6
7
8
9
10
11
12
13
14//file include/asm-generic/dma-mapping.h
static inline const struct dma_map_ops *get_arch_dma_ops(struct bus_type *bus)
{
/*
* Use the non-coherent ops if available. If an architecture wants a
* more fine-grained selection of operations it will have to implement
* get_arch_dma_ops itself or use the per-device dma_ops.
*/
return &dma_noncoherent_ops;
return &dma_direct_ops;
} - 这里不太确定是否使用dma_direct_ops,还是sw64_dma_direct_ops
先排查 dma_direct_ops
- 按照这个逻辑一路排查,发现最后算出来dma_addr 和 vaddr 竟然相同,不符合我们的日志
1 | //file kernel/dma/direct.c |
- page相关
1
2
3
4//file include/asm-generic/page.h
1 | //file include/asm-generic/memory_model.h |
- 经过检查,开启的是CONFIG_DISCONTIGMEM配置
1
2
3
4
5
6
7
8
9
10
11
12
13//file include/linux/mem_encrypt.h
static inline bool sme_active(void) { return false; }
static inline bool sev_active(void) { return false; } - CONFIG_ARCH_HAS_MEM_ENCRYPT 没有配置
1 |
|
再排查 sw64_dma_direct_ops
1 | const struct dma_map_ops sw64_dma_direct_ops = { |
1 | //file arch/sw_64/include/asm/pci.h |
1 | # file arch/sw_64/chip/Makefile |
- 检查配置,发现使用的是CONFIG_SW64_CHIP_QEMU
1 | // file arch/sw_64/chip/chip_qemu/chip_qemu.c |
1 | arch/sw_64/include/asm/chip_qemu_io.h:74: EPDMABAR = 0x80UL, |
1 | // file arch/sw_64/include/asm/page.h |
所以 addr = 0xfff0880300000000 + 0x80UL
按着个计算下来,很有问题,数字太大,并且关系也和实际日志不太符合
规避
受限于时间进度问题,我没有在对这个问题进行跟进
后来整理资料的时候,发现一个规避方法,如果物理内存总共只有4G,那么dma地址无论如何,不会超过32bit。
所以,只要虚拟机内存分配只给4G,那么就没有问题
- 本文作者: crazyboy
- 本文链接: http://crazyboy.www.crazyboy.info/blog/blog/2022/07/05/it/linux/qemu/usb/ehci/debug/dma/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!