Linux на arm64: sendto вызывает "Необработанный сбой: сбой выравнивания (0x96000021)" при отправке данных из согласованного буфера DMA mmapped
Я строю систему сбора данных на базе UltraScale+ FPGA с процессором arm64. Данные передаются в RAM через DMA. Буферы DMA в драйвере зарезервированы, как показано ниже:
virt_buf[i] = dma_zalloc_coherent(&pdev->dev, BUF_SIZE, &phys_buf[i],GFP_KERNEL);
В функции драйвера mmap отображение в пространство пользователя выполняется следующим образом:
#ifdef ARCH_HAS_DMA_MMAP_COHERENT
printk(KERN_INFO "Mapping with dma_map_coherent DMA buffer at phys: %p virt %p\n",phys_buf[off],virt_buf[off]);
res = dma_mmap_coherent(&my_pdev->dev, vma, virt_buf[off], phys_buf[off], vsize);
#else
physical = phys_buf[off];
res=remap_pfn_range(vma,vma->vm_start, physical >> PAGE_SHIFT , vsize, pgprot_noncached(vma->vm_page_prot));
printk(KERN_INFO "Mapping with remap_pfn_range DMA buffer at phys: %p virt %p\n",physical,virt_buf[off]);
#endif
На моем UltraScale+ CPU используется remap_pfn_range. В приложении пользовательского пространства данные считываются из буфера и в настоящее время немедленно отправляются в пакетах UDP, длина которых ограничена MAX_DGRAM (первоначально равной 572).
int i = 0;
int bleft = nbytes;
while(i<nbytes) {
int bts = bleft < MAX_DGRAM ? bleft : MAX_DGRAM;
if (sendto(fd,&buf[nbuf][i],bts,0, res2->ai_addr,res2->ai_addrlen)==-1) {
printf("%s",strerror(errno));
exit(1);
}
bleft -= bts;
i+= bts;
}
Все отлично работало на 32-битной Zynq FPGA. Однако после того, как я переместил его на 64-битную UltraScale+ FPGA, я начал получать случайные ошибки после нескольких сотен передач.
[ 852.703491] Unhandled fault: alignment fault (0x96000021) at 0x0000007f82635584
[ 852.710739] Internal error: : 96000021 [#4] SMP
[ 852.715235] Modules linked in: axi4s2dmov(O) ksgpio(O)
[ 852.720358] CPU: 0 PID: 1870 Comm: a4s2dmov_send Tainted: G D O 4.4.0 #3
[ 852.728001] Hardware name: ZynqMP ZCU102 RevB (DT)
[ 852.732769] task: ffffffc0718ac180 ti: ffffffc0718b8000 task.ti: ffffffc0718b8000
[ 852.740248] PC is at __copy_from_user+0x8c/0x180
[ 852.744836] LR is at copy_from_iter+0x70/0x24c
[ 852.749261] pc : [<ffffffc00039210c>] lr : [<ffffffc0003a36a8>] pstate: 80000145
[ 852.756644] sp : ffffffc0718bba40
[ 852.759935] x29: ffffffc0718bba40 x28: ffffffc06a4bae00
[ 852.765228] x27: ffffffc0718ac820 x26: 000000000000000c
[ 852.770523] x25: 0000000000000014 x24: 0000000000000000
[ 852.775818] x23: ffffffc0718bbe08 x22: ffffffc0710eba38
[ 852.781112] x21: ffffffc0718bbde8 x20: 000000000000000c
[ 852.786407] x19: 000000000000000c x18: ffffffc000823020
[ 852.791702] x17: 0000000000000000 x16: 0000000000000000
[ 852.796997] x15: 0000000000000000 x14: 00000000c0a85f32
[ 852.802292] x13: 0000000000000000 x12: 0000000000000032
[ 852.807586] x11: 0000000000000014 x10: 0000000000000014
[ 852.812881] x9 : ffffffc0718bbcf8 x8 : 000000000000000c
[ 852.818176] x7 : ffffffc0718bbdf8 x6 : ffffffc0710eba2c
[ 852.823471] x5 : ffffffc0710eba38 x4 : 0000000000000000
[ 852.828766] x3 : 000000000000000c x2 : 000000000000000c
[ 852.834061] x1 : 0000007f82635584 x0 : ffffffc0710eba2c
[ 852.839355]
[ 852.840833] Process a4s2dmov_send (pid: 1870, stack limit = 0xffffffc0718b8020)
[ 852.848134] Stack: (0xffffffc0718bba40 to 0xffffffc0718bc000)
[ 852.853858] ba40: ffffffc0718bba90 ffffffc0006a1b2c 000000000000000c ffffffc06a9bdb00
[ 852.861676] ba60: 00000000000005dc ffffffc071a0d200 0000000000000000 ffffffc0718bbdf8
[ 852.869488] ba80: 0000000000000014 ffffffc06a959000 ffffffc0718bbad0 ffffffc0006a2358
[...]
[ 853.213212] Call trace:
[ 853.215639] [<ffffffc00039210c>] __copy_from_user+0x8c/0x180
[ 853.221284] [<ffffffc0006a1b2c>] ip_generic_getfrag+0xa4/0xc4
[ 853.227011] [<ffffffc0006a2358>] __ip_append_data.isra.43+0x80c/0xa70
[ 853.233434] [<ffffffc0006a3d50>] ip_make_skb+0xc4/0x148
[ 853.238642] [<ffffffc0006c9d04>] udp_sendmsg+0x280/0x740
[ 853.243937] [<ffffffc0006d38e4>] inet_sendmsg+0x7c/0xbc
[ 853.249145] [<ffffffc000651f5c>] sock_sendmsg+0x18/0x2c
[ 853.254352] [<ffffffc000654b14>] SyS_sendto+0xb0/0xf0
[ 853.259388] [<ffffffc000084470>] el0_svc_naked+0x24/0x28
[ 853.264682] Code: a88120c7 a8c12027 a88120c7 36180062 (f8408423)
[ 853.270791] ---[ end trace 30e1cd8e2ccd56c5 ]---
Segmentation fault
root@Xilinx-ZCU102-2016_2:~#
Странно то, что когда я просто читаю слова из буфера, это не вызывает ошибок выравнивания.
Кажется, что функция send неправильно использует функцию __copy_from_user, вызывая доступ к памяти без выравнивания. Вопрос: это ошибка ядра или я что-то сделал неправильно?
Однако, как правило, отправка блока данных, начинающегося не с 8-байтовой границы, не вызывает ошибку выравнивания. Проблема возникает с относительно низкой вероятностью. Я не смог выделить условия, которые приводят к ошибке.
Я обошел проблему, настроив MAX_DGRAM так, чтобы он был кратен 8. Однако я боюсь, что проблема может появиться снова, если данные в буфере mmapped будут подвергнуты более сложной обработке. Некоторые люди сообщали о похожих проблемах в архитектуре arm64, связанных с функцией memcpy (например, [ https://bugs.launchpad.net/linux-linaro/+bug/1271649]).
Каков правильный метод отображения когерентных буферов DMA в пространство пользователя, чтобы избежать ошибок выравнивания памяти?
1 ответ
Этот драйвер нуждается в обновлении. ARCH_HAS_DMA_MMAP_COHERENT
долгое время не определялся ничем иным, кроме PowerPC, и даже это выглядит как забытый остаток.
Там был общий dma_mmap_coherent()
реализация с 3.6, так что можно и нужно использовать безоговорочно. Результатом текущего кода является то, что благодаря #ifdef вы всегда выбираете другой путь, тогда благодаря pgprot_noncached()
в конечном итоге вы создаете строго упорядоченное отображение пользовательского пространства буфера (Device nGnRnE в терминах AArch64). Как правило, это плохая идея, поскольку код пользовательского пространства будет предполагать, что он всегда работает с обычной памятью (если явно не создан) и может безопасно выполнять такие операции, как невыровненный или монопольный доступ, которые могут плохо работать в памяти типа устройства., Я даже не собираюсь спрашивать, что за сумасшествие заканчивается тем, что ядро копирует данные обратно из отображения пользовательского пространствабуфера ядра *, но достаточно сказать, что ядро - черезcopy_{to,from,in}_user()
- также предполагает, что адреса в пространстве пользователя отображаются как обычная память и, таким образом, безопасны для не выровненного доступа. Честно говоря, я немного удивлен, что на 32-битном ARM это не происходит так же, так что я полагаю, что ваши данные всегда выровнены как минимум на 4 байта - это также объясняет, почему чтение слов (с 32-битным доступом) хорошо, если только 64-битный доступ к двойному слову потенциально может быть смещен.
Короче говоря, просто используйтеdma_mmap_coherent()
и избавиться от плохого эквивалента с открытым кодом. Это даст пользовательскому пространству нормальное не кешируемое отображение (или кешируемое отображение для аппаратно-связного устройства), которое будет работать так, как ожидается. Это также не сломано с точки зрения принятияdma_addr_t
это физический адрес, что, как кажется, делает код вашего драйвера - это еще одна вещь, которая может рано или поздно укусить вас за задницу (у ZynqMP есть системный MMU, так что вы можете предположительно обновить ядро до 4.9, подключить некоторые Идентификаторы потоков, добавьте их в DT и проследите, чтобы это предположение сработало по-новому.
* Хотя мне приходит в голову, что были некоторые обстоятельства, при которых копирование с самого конца страницы может иногда перечитывать на следующую страницу, что могло бы вызвать это непреднамеренно, если следующая страница оказалась Устройством / Сильно упорядоченным картографирование, которое привело к этому патчу в 4.5.Ответ Линуса на такие макеты памяти был "... и никто в здравом уме на самом деле не делает этого..."