LINUX.ORG.RU

DMA (bus master) buffer from user-space.


0

1

Здравствуйте!

Есть задача: для 1-4 PCI плат выделить по 64Mб непрерывной памяти, иметь доступ к каждому из блоков этой памяти из user-space и его физический адрес для передачи соотвествующей плате. ОС: Ubuntu 11.01 x86 Kernel 3.0.0-12-generic.

Блоки памяти могут выделяться кусочно (сейчас необходимо сделать по 2 банка 32Мб для каждой платы, но хорошо бы иметь возможность изменять объем банков и их количество).

Я предполагаю, что необходимо делать это с помощью функции dma_alloc_coherent(), из которой будет получаться dma_addr_t* - физический адрес, который можно передать в устройство, и указатель на памяти в kernel-space. Далее этот указатель отправляется в user-space с помощью mmap().

Возможно, можно выбрать для выделения памяти kmalloc() с необходимыми флагами, а физический адрес получить из полученного указателя с помощью дополнительных функций.

Вопрос: какими средствами лучше сделать эту операцию, чем выделать память? Как лучше отправить указатель на выделенную память в user-space (с учетом того, что банков памяти может быть много и нужно иметь указатель на каждый из них)?

Спасибо.


Ядро может выделить только до 4 мб физически линейной памяти. Дальше либо девайс должен уметь scatter-gather dma и нужный объём памяти ему набивать постранично, либо можно извернуться и получить больше 4 линейных мб, но этого в LDD нет.

Ответы на остальные вопросы можно найти в LDD.

mv ★★★★★
()

+

LDD - это книга Linux Device Drivers

ttnl ★★★★★
()

может у тебя это memio бары?, тогда можно через /dev/mem к ним обращаться

dimon555 ★★★★★
()
Ответ на: комментарий от mv

либо можно извернуться и получить больше 4 линейных мб, но этого в LDD нет.

Можешь поделиться как это сделать? Ссылкой или кодом или словами описать, что к чему. Реально очень нужно. Спасибо.

karak
()

Если твоя железка не умеет scatter/gather - она либо говно, которое нужно выкинуть, либо ты должен поговорить с разработчиками, чтобы они добавили такую возможность.

Для DMA в/из userspace не нужны ни kmalloc, ни mmap.

tailgunner ★★★★★
()
Ответ на: комментарий от karak

кажется get_user_pages(...)

Да. Потом virt_to_page, sg_set_page, и дескрипторы В/В. Но без scatter/gather в железке это не имеет смысла.

tailgunner ★★★★★
()
Ответ на: комментарий от karak

Можешь поделиться как это сделать? Ссылкой или кодом или словами описать, что к чему. Реально очень нужно. Спасибо.

Товарищ buddy, который allocator, делит память кусками максимум по 1024 страницы, т.е. 4 мб. Если у него много раз спросить кусок по 4 мб, то многие куски будут иметь общую границу в физической адресации. Таким образом можно найти непрерывный набор страниц подходящей длины, подготовить его к использованию, освободить остальные, неподходящие страницы, io_remap_pfn_range в юзерский процесс, и всё.

Мы таким лыком успешно выделяем своему девайсу сотни мегабайт/гигабайты памяти в первых 4 гб (девайс sgdma сознательно не умеет, т.к. это не low latency). Если драйвер грузится не сразу после загрузки системы, то могут возникнуть проблемы с фрагментированием памяти, решаемые её компактированием, либо ребутом.

http://pastebin.com/HaCgMjSs

mv ★★★★★
()
Ответ на: комментарий от mv

Буду разбирать. А вот, то, что память выделяется в 4ГБ диапазоне как-то преодолеваемо? Просто имеющийся девайс может работать с 64-битными адресами.
Если в строчке
addr = __get_free_pages(GFP_DMA32, 10);
указать набор флагов по или прокатит или нет?
Как я понял, код по ссылке реализует указанный Вами алгоритм?
За ссылку и разъяснения спасибо.

karak
()
Ответ на: комментарий от karak

Буду разбирать. А вот, то, что память выделяется в 4ГБ диапазоне как-то преодолеваемо?

Да.

Просто имеющийся девайс может работать с 64-битными адресами. Если в строчке addr = __get_free_pages(GFP_DMA32, 10); указать набор флагов по или прокатит или нет?

Да.

Как я понял, код по ссылке реализует указанный Вами алгоритм?

Да.

mv ★★★★★
()
Ответ на: комментарий от mv

Выделяя много кусков памяти подряд в ядре я тоже заметил, что они имеют адреса идущие друг за другом, т.е. фактически непрерывны более большие объемы. Но у Вас уже написана функция. Спасибо.

1.Правильно я понимаю, что таким образом выделив один раз при загрузке большой объем памяти, можно потом получать на него указатель из user-space много раз, когда это потребуется (у меня приложение может много раз запускаться и закрываться во время работы)? Все это выполняется через mmap().

2.Другой вопрос - если этих банков памяти будет много (32 или любое другое число), как выполнить mmap() для всех них? Например, задавая offset при вызове?

3.Для чего выполняется SetPageReserved() ? Для того, чтобы страницы всегда оставались в памяти и не освобождались через своп на диске?

4.В LDD3 говорится про dma_alloc_coherent() - выделение с помощью этой, как я понял, ничем не отличается?

Еще в LDD3 упоминается про alloc_bootmem(), но ее использование не рекомендуется.

ksanto
() автор топика
Ответ на: комментарий от ksanto

1.Правильно я понимаю, что таким образом выделив один раз при загрузке большой объем памяти, можно потом получать на него указатель из user-space много раз, когда это потребуется (у меня приложение может много раз запускаться и закрываться во время работы)? Все это выполняется через mmap().

Правильно реализовать в драйвере char-девайс, у которого есть ioctl, через которые юзерспейс просит драйвер замапить ему в адресное пространство dma-буфер. Мапится в драйвере через io_remap_pfn_range. Про детали читайте LDD.

3.Для чего выполняется SetPageReserved() ? Для того, чтобы страницы всегда оставались в памяти и не освобождались через своп на диске?

Это нужно делать, чтобы странички всегда физически оставались на своих местах.

4.В LDD3 говорится про dma_alloc_coherent() - выделение с помощью этой, как я понял, ничем не отличается?

Нам не подходит.

Еще в LDD3 упоминается про alloc_bootmem(), но ее использование не рекомендуется.

Можно и через memmap= в параметрах ядру резервировать, но это отстой и не plug'n'play.

mv ★★★★★
()
Ответ на: комментарий от mv

Правильно реализовать в драйвере char-девайс, у которого есть ioctl, через которые юзерспейс просит драйвер замапить ему в адресное пространство dma-буфер. Мапится в драйвере через io_remap_pfn_range. Про детали читайте LDD.

У меня и реализован свой char-девайс для каждой подключенной PCI платы. Но, например, ресурсы PCI отправлял в user-space через .mmap. Спасибо за совет.

Сейчас кстати, по-моему, это называется .compat_ioctl и .unlocked_ioctl.

ksanto
() автор топика
Ответ на: комментарий от ksanto

Сейчас кстати, по-моему, это называется .compat_ioctl и .unlocked_ioctl.

Раньше ioctl ядро лочили.

mv ★★★★★
()
Ответ на: комментарий от mv

Правильно реализовать в драйвере char-девайс, у которого есть ioctl, через которые юзерспейс просит драйвер замапить ему в адресное пространство dma-буфер.

O_O

И чем это лучше mmap?

tailgunner ★★★★★
()
Ответ на: комментарий от mv

Да ТНБ с ТС, зачем

ioctl, через которые юзерспейс просит драйвер замапить ему в адресное пространство dma-буфер.

это прекрасно делается через православный mmap

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

это прекрасно делается через православный mmap

У нас память бьётся на несколько сегментов различного назначения, поэтому отдельные ioctl. Сия особенность затмила мой разум.

mv ★★★★★
()
Ответ на: комментарий от mv

У нас память бьётся на несколько сегментов различного назначения, поэтому отдельные ioctl

Не, ты так просто не отмажешься %) Что мешало сделать mmap разных сегментов с разных адресов? Сделать несколько device node'ов?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Не, ты так просто не отмажешься %) Что мешало сделать mmap разных сегментов с разных адресов? Сделать несколько device node'ов?

Зачем? По ioctl-реквесту на задачу прямее. А танцы вокруг специальных значений address hint у mmap - это кривизна в дизайне.

mv ★★★★★
()
Ответ на: комментарий от mv

По ioctl-реквесту на задачу прямее.

Там, где задача нормально ложится на семантику существующего системного вызова, следует его и использовать.

А танцы вокруг специальных значений address hint у mmap - это кривизна

offset, не address. И, как по мне, реальная кривизна - это mmap, замаскированный под ioctl.

в дизайне.

В реализации.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Там, где задача нормально ложится на семантику существующего системного вызова, следует его и использовать.

Ok, у нас не ложится.

offset, не address. И, как по мне, реальная кривизна - это mmap, замаскированный под ioctl.

Ты толкаешь ioctl, замаскированный под mmap. Замапленная память в нашем юзерском процессе - это вообще чуть ли не сайд-эффект.

mv ★★★★★
()
Ответ на: комментарий от ttnl

А как же arm, cris, powerpc, s390 и sh? __get_free_pages - не портабельно? Планируется использовать драйвер на arm. Сейчас реализация или __get_free_pages или dma_alloc_coherent (на этапе ). Подскажите, на arm алгоритм указанный mv будет работать? Спасибо.

karak
()
Ответ на: комментарий от ttnl

А как же arm, cris, powerpc, s390 и sh?

Нам - пофиг, ибо не то, что x86, а обязательно i7, желательно песочный мост, желательно на чипсете с ioat.

А что с этими платформами не так? На них разве buddy нет?

mv ★★★★★
()
Ответ на: комментарий от karak

Планируется использовать драйвер на arm

Используй (MAX_ORDER-1) вместо всех десяток. На этом моменте мы плавно переходим к разговору о пользе дефайнов.

ttnl ★★★★★
()
Ответ на: комментарий от mv

Хотя если тебе только для i7, то я тут не берусь судить

ttnl ★★★★★
()
Ответ на: комментарий от ttnl

Если я правильно понял, то все-таки нужно выделять так:

addr = __get_free_pages(GFP_DMA32, MAX_ORDER-1);

На некоторых архитектурах не будет работать, на некоторых - будет, но неоптимально.

Не могли бы Вы пояснить это. Хотя моя цель - это x86 и x86-64, но все же интересно для общего развития.

Еще такой вопрос - как сделать, чтобы выделенный буфер был доступен только одному процессу из user-space одновременно? Как вариант, например, передавать id этого процесса (если маппировать с помощью .ioctl). По завершению этого процесса передавать другой ioctl с id и разблокировать возможность маппирования другими процессами. Но в таком случае, что делать, если процесс падает (некорректно завершается) и не разблокирует возможность маппирования?

ksanto
() автор топика
Ответ на: комментарий от ksanto

addr = __get_free_pages(GFP_DMA32, MAX_ORDER-1);

Типа того. Более маленькие order запрашивать смысла нет, так как если смежной памяти достаточно, то аллокатор уже собрал её в список кусков с более крупным order.

NB: Здесь мы не расматриваем всякие пограничные случаи. Их можно выкинуть практически без ограничения общности. Никто в здравом уме не будет пытаться выделять себе память одновременно с разными order.

Не могли бы Вы пояснить это. Хотя моя цель - это x86 и x86-64, но все же интересно для общего развития.

Оптимально выделять большими кусками, что тут непонятного. Меньше вызовов __get_free_pages - меньше блокировок и т.д.

Выделить кусок с order >= MAX_ORDER вообще невозможно.

Еще такой вопрос - как сделать, чтобы выделенный буфер был доступен только одному процессу из user-space одновременно?

Буфер доступен тому процессу, который делает mmap. Другим недоступен. Соответственно, если есть необходимость ограничить круг пользователей, то вводишь проверку в .open или .mmap.

Но в таком случае, что делать, если процесс падает (некорректно завершается) и не разблокирует возможность маппирования?

Выделяешь память в probe. В open ограничиваешь круг пользователей одним процессом (увеличиваешь счетчик). В close уменьшаешь счетчик.

ttnl ★★★★★
()
Ответ на: комментарий от ttnl

если есть необходимость ограничить круг пользователей, то вводишь проверку в .open или .mmap.

Думаю, проверка в open не сработает после fork.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Короче, выход такой - дополнительно добавлять флаг VM_DONTCOPY при маппировании

ttnl ★★★★★
()
Ответ на: комментарий от ttnl

что-нибудь придумаем

Да что тут придумывать... придумывать тут нечего, потому что mmap наследуются при форке :) IIUC, от такого защатиться невозможно, да и стоит ли? Сделать обычный single-open «на отвяжись».

tailgunner ★★★★★
()
Ответ на: комментарий от ttnl

А про счетчики, которые Вы говорите, это касается счетчиков маппирования, а не открытия файла? Т.е. что-то вроде этого:

int mmap_open = 0;
void vma_open(struct vm_area_struct *vma)
{
mmap_open++;
}
void simple_vma_close(struct vm_area_struct *vma)
{
mmap_open--;
}

static struct vm_operations_struct vm_ops = {
.open = vma_open,
.close = vma_close,
};

static int mmap(struct file *filp, struct vm_area_struct *vma)
{
if (mmap_open)
return -EBUSY;

vma->vm_flags |= VM_DONTCOPY;

if (io_remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff,
vma->vm_end - vma->vm_start,
vma->vm_page_prot))
return -EAGAIN;

vma->vm_ops = &vm_ops;
vma_open(vma);
return 0;
}


Правильно я понимаю, что это таким образом оформленное маппирование позволит только одному процессу маппировать буфер. А если этот процесс некорректно завершится (без вызова unmap()), то буфер все равно станет доступен, т.к. будет вызвана vma_close()?

ksanto
() автор топика
Ответ на: комментарий от ksanto
static spinlock_t spinlock;
static struct task_struct *owner = NULL;

void open(...)
{
       ret = 0;

       lock(spinlock);
       if (owner) {
           ret = -EBUSY;
           goto unlock;
       }
       owner = current;
unlock:
       unlock(spinlock);
       return ret;        
}
void close(...)
{
       lock(spinlock);
       /* Если release вызывается один раз, когда все процессы освободят 
          эту шнягу, то удали проверку */
       if (owner == current)
             owner = NULL;
       unlock(spinlock);
}

static struct file_operations_struct file_ops = {
.open = open,
.release = close,
};

ttnl ★★★★★
()
Ответ на: комментарий от ttnl

Эти закрытия и открытия относятся к file_operations_struct, т.е. к файлу. Одновременный доступ процессов к файлу символьного устройства мне наоборот нужно иметь. Нужно, чтобы выделенный буфер единовременно мог отмаппировать только один процесс. Или я чего-то не понимаю в написанном коде.

Как-то это не сочетается с __get_free_pages

А как тогда правильно маппировать в user-space выделенный буфер?

ksanto
() автор топика
Ответ на: комментарий от mv

Большой кусок линейой памяти мапят обычно для DMA. Зачем при этом мапить память устройства для (очень) медленного MMIO? Я бы понял, если бы ТС хотел выделить память, сделать DMA в нее, и потом замапить ее для доступа из userspace (ну или аналогично для случая write), но для userspace мапится именно выделенная через __get_free_pages память. А io_remap* в этот сценарий как-то не вписывается.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Большой кусок линейой памяти мапят обычно для DMA. Зачем при этом мапить память устройства для (очень) медленного MMIO?

А откуда MMIO выплыло? Вроде речь как раз про DMA идёт.

mv ★★★★★
()
Ответ на: комментарий от mv

Ok, remap_pfn_range. Даже если это одно и то же :)

IIRC, зависит от архитектуры. Кроме того:

Блоки памяти могут выделяться кусочно

так что я даже не уверен в применимости remap_pfn_range здесь (да и вообще, разрешено ли использовать remap_pfn_range на результатах get_free_pages? Речь не о x86).

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.