Вопрос или проблема
Я пытаюсь выделить 16 ГБ системной памяти с помощью vm_mmap и get_user_pages_fast и передать эту память в dma_map_sg. Однако длина всех sg после dma_map_sg меньше 16 ГБ, и она будет равна этому значению, если я отключу iommu. Мой тестовый код представлен ниже:
#include <linux/memblock.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/mman.h>
#include <linux/dma-mapping.h>
#define __SIZE_TO_PAGE_NUM(a) ((a) >> PAGE_SHIFT)
#define XFER_PAGE_NUM __SIZE_TO_PAGE_NUM(xfer->size)
#define MAX_PIN_SIZE SZ_1G
#define MAX_PIN_PAGE_NUM __SIZE_TO_PAGE_NUM(MAX_PIN_SIZE)
static struct sg_table sgt;
static struct page** pages = NULL;
static int probe_status = 0;
static u64 addr = 0;
static u64 size = SZ_16G;
void dma_test_remove(struct pci_dev *pdev);
static struct pci_device_id dma_test_dev_id[] = {
{
PCI_DEVICE(0x1e3e, 0x2),
},
{}
};
static int dma_test_probe(struct pci_dev *pdev,
const struct pci_device_id *pent)
{
u64 buf;
u32 page_num;
u32 index;
int n;
int ret;
u32 page_num_pinned = 0;
int i;
struct scatterlist *sg;
u64 dma_len = 0;
int count1 = 0;
int count0 = 0;
/* инициализация dma */
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(48));
if (ret) {
printk("dma_set_mask не удалась\n");
return ret;
}
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(48));
if (ret) {
printk("dma_set_coherent_mask не удалась\n");
return ret;
}
// выделение va
addr = vm_mmap(NULL, 0, size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED | MAP_POPULATE, 0);
if (IS_ERR(ERR_PTR(addr))) {
printk("vm_mmap не удалось выделить va\n");
return -EFAULT;
}
printk("выделение va успешно\n");
page_num = size >> PAGE_SHIFT;
pages = vmalloc(page_num * sizeof(struct pages *));
if (!pages) {
vm_munmap(addr, size);
printk("vmalloc не удалось выделить память\n");
return -ENOMEM;
}
// закрепление памяти
buf = addr;
while (page_num) {
// n = min_t(typeof(n), page_num, MAX_PIN_PAGE_NUM);
n = get_user_pages_fast(buf, page_num, FOLL_WRITE, (pages + page_num_pinned));
if (n < 0) {
printk("неудача при закреплении страниц %d\n", n);
goto unpin_dma_pages;
}
page_num_pinned += n;
page_num -= n;
buf += (unsigned long)n << PAGE_SHIFT;
}
printk("закрепление страниц успешно\n");
/* создание sg таблицы */
ret = sg_alloc_table_from_pages(&sgt, pages, size >> PAGE_SHIFT,
0, size, GFP_KERNEL);
if(ret) {
printk("выделение sg_table не удалось %d\n", ret);
goto unpin_dma_pages;
}
printk("выделение sg таблицы успешно\n");
for_each_sg(sgt.sgl, sg, sg_nents(sgt.sgl), i){
dma_len += sg_dma_len(sg);
count0++;
if (sg_dma_len(sg) == 0)
count1++;
}
printk("count0 %d count1 %d\n", count0, count1);
if (dma_len < size) {
printk("ошибка длины dma в sg_alloc_table dma_len %llx size %llx\n", dma_len, size);
}
// dma карта
ret = dma_map_sg(&pdev->dev, sgt.sgl, sg_nents(sgt.sgl), DMA_BIDIRECTIONAL);
if (unlikely(!ret)) {
printk("неудача при отображении sg %d\n", ret);
ret = -ENOMEM;
goto free_sg;
}
probe_status = 1;
dma_len = 0;
i = 0;
count0 = 0;
count1 = 0;
for_each_sg(sgt.sgl, sg, sg_nents(sgt.sgl), i){
dma_len += sg_dma_len(sg);
count0++;
if (sg_dma_len(sg) == 0)
{
//printk("длина dma карты sg равна 0\n");
count1++;
}
}
printk("count0 %d count1 %d dma_len %llx запрашиваемая длина %llx\n", count0, count1, dma_len, size);
if (dma_len < size) {
printk("ошибка длины dma в dma_map_sg dma_len %llx size %llx\n", dma_len, size);
}
printk("dma карта успешно\n");
return 0;
free_sg:
sg_free_table(&sgt);
unpin_dma_pages:
for (index = 0; index < page_num_pinned; index++) {
put_page(pages[index]);
}
vfree(pages);
vm_munmap(addr, size);
return ret;
}
void dma_test_remove(struct pci_dev *pdev)
{
int index;
if (probe_status)
dma_unmap_sg(&pdev->dev, sgt.sgl, sg_nents(sgt.sgl), DMA_BIDIRECTIONAL);
sg_free_table(&sgt);
for (index = 0; index < (size >> PAGE_SHIFT); index++) {
put_page(pages[index]);
}
if (addr)
vm_munmap(addr, size);
if (pages)
vfree(pages);
printk("удаление модуля dma-test\n");
}
static struct pci_driver dma_test_pci_driver = {
.name = "dma-test",
.id_table = dma_test_dev_id,
.probe = dma_test_probe,
.remove = dma_test_remove,
};
static int __init dma_map_test_init(void)
{
return pci_register_driver(&dma_test_pci_driver);
}
static void __exit dma_map_test_exit(void)
{
pci_unregister_driver(&dma_test_pci_driver);
}
module_init(dma_map_test_init);
module_exit(dma_map_test_exit);
MODULE_DEVICE_TABLE(pci, dma_test_dev_id);
MODULE_AUTHOR("zhanged");
MODULE_DESCRIPTION("zhanged Тест dma_map_sg для большого размера");
MODULE_LICENSE("GPL и дополнительные права");
Когда я загружаю этот модуль ядра в первый раз, dma_len равен 16 ГБ, но dma_len становится меньше 16 ГБ, если загружать модуль ядра во второй раз. Я попытался отключить iommu, отредактировав /etc/default/grub, и это явление исчезает независимо от того, сколько раз я это выполняю. Информация о моем сервере:
dma_test # lsb_release -a
Нет доступных модулей LSB.
Идентификатор дистрибьютора: Ubuntu
Описание: Ubuntu 24.04 LTS
Версия: 24.04
Кодовое имя: noble
dma_test # cat /proc/version
Версия Linux 6.8.0-48-generic (buildd@lcy02-amd64-010) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #48-Ubuntu SMP PREEMPT_DYNAMIC Пт Сен 27 14:04:52 UTC 2024
dma_test # cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-6.8.0-48-generic root=UUID=c8ffbdad-f578-4acd-a77b-320d315a9b80 ro quiet splash 0 intel_iommu=on memmap=20G!28G vt.handoff=7
dma_test # free -tg
всего использовано свободно общего доступа buff/cache доступно
Mem: 27 3 18 0 5 23
Swap: 7 0 7
Всего: 35 3 26
dma_test # lscpu
Архитектура: x86_64
Режимы работы CPU: 32-бит, 64-бит
Размеры адресов: 39 бит физический, 48 бит виртуальный
Порядок байтов: Младший конец
CPU(s): 16
Список онлайн-CPU(s): 0-15
Идентификатор вендора: GenuineIntel
Идентификатор вендора BIOS: Intel(R) Corporation
Название модели: Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
Название модели BIOS: Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz Заполнить O.E.M. CPU @ 2.8GHz
Семейство CPU BIOS: 198
Семейство CPU: 6
Модель: 165
Потоков на ядро: 2
Ядер на сокет: 8
Сокетов: 1
Ревизия: 5
Частота CPU(n): 30%
Максимальная частота CPU: 4800.0000
Минимальная частота CPU: 800.0000
BogoMIPS: 5799.77
Флаги: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe sysca
ll nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmu
lqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_
timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexp
riority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt intel_pt xsaveopt
xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp vnmi pku ospke md_clear flush_l1d arch_c
apabilities
Виртуализация:
Виртуализация: VT-x
Кэш (сумма всех):
L1d: 256 KiB (8 экземпляров)
L1i: 256 KiB (8 экземпляров)
L2: 2 MiB (8 экземпляров)
L3: 16 MiB (1 экземпляр)
NUMA:
NUMA узлов: 1
NUMA узел0 CPU(s): 0-15
Уязвимости:
Собирание выборки данных: Смягчение; Микрокод
Itlb multihit: KVM: Смягчение: VMX отключен
L1tf: Не затронут
Mds: Не затронут
Meltdown: Не затронут
Старая информация Mmio: Смягчение; Очистка буферов CPU; Уязвимость SMT
Собирание данных файлов реестра: Не затронут
Retbleed: Смягчение; Улучшенный IBRS
Переполнение стека rstack: Не затронут
Обход Хранения Спецификаций: Смягчение; Обход Спецификации Хранения отключен через prctl
Спектр v1: Смягчение; барьеры usercopy/swapgs и очистка указателей __user
Спектр v2: Смягчение; Улучшенный / Автоматический IBRS; Условный IBPB; Заполнение RSB; PBRSB-eIBRS SW последовательность; BHI SW цикл, KVM SW цикл
Srbds: Смягчение; Микрокод
Асинхронный Abort Tsx: Не затронут
Я пытался установить несколько других ядер linux, но проблема все еще существует. Только отключение iommu (добавление intel_iommu=off к команде) может решить мою проблему.
Есть ли что-то неправильное в моем тестовом коде?
Ответ или решение
Заданные вами обстоятельства, включая процедуру выделения памяти, связывание с DMA и управление IOMMU, являются сложным и многофакторным случаем, поэтому давайте проанализируем вашу проблему подробно и предложим возможные решения.
Описание проблемы
В вашем коде вы пытаетесь выделить 16 ГБ системной памяти с использованием функции vm_mmap
и затем передать выделенную память в dma_map_sg
. Однако, после выполнения dma_map_sg
длина всех сегментов (scatter-gather) (sg
) в sg_table
оказывается меньше 16 ГБ, что является неожиданным. Вы также упомянули, что это поведение устраняется, когда вы отключаете IOMMU, добавив intel_iommu=off
в командную строку при загрузке.
Причины и возможные решения
-
Проблемы с IOMMU:
- IOMMU (Input/Output Memory Management Unit) служит для управления отображением виртуальных адресов памяти на физическую память, что особенно полезно для защиты от ошибок DMA и для обеспечения безопасного доступа к памяти. Когда IOMMU включен, он может накладывать ограничения на размер и адресацию используемой памяти, а также на выгрузку областей памяти.
- Попробуйте проверить настройки IOMMU в BIOS вашей системы. Убедитесь, что они правильно настроены и могут справляться с вашей конфигурацией.
-
Размер секций памяти:
- IOMMU может разбивать адресное пространство на страницы (например, 4K), и если выделенная область вашей памяти формируется в соответствии с этими страницами, это может привести к ситуации, когда физически свободные страницы недоступны для DMA. Проверьте размер страниц и порядок выделения памяти.
-
Изменения в конфигурации памяти:
- Если переключение между включением и отключением IOMMU вызывает изменение в работе вашей подсистемы, это может указывать на то, что система управления памятью (например,
get_user_pages_fast
) неполностью подходит для большей области памяти, когда IOMMU активно. - Измените подход к выделению страниц. Вместо использования
vm_mmap
попробуйте выделить память с помощьюkmalloc
илиvmalloc
, чтобы избежать потенциальных ограничений при использованииget_user_pages
.
- Если переключение между включением и отключением IOMMU вызывает изменение в работе вашей подсистемы, это может указывать на то, что система управления памятью (например,
-
Логика DMA и SG-таблиц:
- Ваш код проверяет длины сегментов, однако вам следует уделить особое внимание тому, как именно вы создаёте SG-таблицу (
sg_alloc_table_from_pages()
). Убедитесь, что параметры корректные и не приводят к потере памяти в результате защиты, реализуемой IOMMU. - Убедитесь, что каждая сгенерированная SG-таблица корректно отображает все выделенные сегменты.
- Ваш код проверяет длины сегментов, однако вам следует уделить особое внимание тому, как именно вы создаёте SG-таблицу (
-
Отладка:
- Добавьте больше выводов отладки для проверки значений после каждой критической операции, чтобы лучше понять, где именно происходит сбой.
- Например, проверьте количество доступных страниц памяти с помощью
numa_available()
иget_user_pages()
после каждой вашей операции, чтобы видеть, сколько страниц было фактически зафиксировано для DMA.
Заключение
Система управления памятью и взаимодействие с DMA в контексте использования IOMMU представляет собой сложную область. Если вышеуказанные шаги не приведут к желаемым результатам, рассмотрите возможность консультации с документацией к вашему оборудованию или поддержкой соответствующего дистрибутива Linux.
Было бы полезно также создать более простую тестовую программу, которая изолировала бы одну из частей вашего кода, чтобы минимизировать количество переменных и лучше понять природу проблемы.