Как в драйвере (kernel mode) организовать передачу данных из устройства непосредственно в буфер процесса (user mode), минуя буфер в драйвере?
ОС Linux 2.4.18.
PCI устройство в режиме мастеринга (без участия процессора, как в DMA) заливает данные в буфер памяти. Адрес начала буфера и его размер я пишу в порты в\в устройства.
Хотелось бы передавать устройству физический адрес буфера процесса запросившего данные.
Объемы данных (несколько аудио и видео потоков) большие и поэтому лишнее копирование из устройства в буфер драйвера и оттуда в буфер процесса крайне не желательно. В то же время использовать для перекачки данных процессор, когда драйвер читает данные из порта и передаёт их процессу ( что-то типа put_user(inb(PORT), buf++); ) то же достаточно накладно.
Как можно реализовать схему при которой данные из устройства сразу поступают в адресное пространство процесса ждущего данные?
Что-то типа следующего:
Процесс получает буфер (malloc) и передаёт указатель на него и его размер в драйвер (при помощи ioctl). Процесс засыпает в ioctl (при помощи очереди ожидания). В драйвере определяется физический адрес этого буфера и передаётся устройству. Устройство заполняет буфер данными и генерирует прерывание. В обработчике прерывания процесс пробуждается и при выходе из ioctl буфер либо заполнен либо возвращается код ошибки.
Подскажите хоть в каком направлении рыть.
Re: Linux. Драйвер. Память.
От:
Аноним
Дата:
18.12.03 17:08
Оценка:
Здравствуйте, unkn2000, Вы писали:
U>Как в драйвере (kernel mode) организовать передачу данных из устройства непосредственно в буфер процесса (user mode), минуя буфер в драйвере? U>ОС Linux 2.4.18. U>PCI устройство в режиме мастеринга (без участия процессора, как в DMA) заливает данные в буфер памяти. Адрес начала буфера и его размер я пишу в порты в\в устройства. U>Хотелось бы передавать устройству физический адрес буфера процесса запросившего данные. U>Объемы данных (несколько аудио и видео потоков) большие и поэтому лишнее копирование из устройства в буфер драйвера и оттуда в буфер процесса крайне не желательно. В то же время использовать для перекачки данных процессор, когда драйвер читает данные из порта и передаёт их процессу ( что-то типа put_user(inb(PORT), buf++); ) то же достаточно накладно. U>Как можно реализовать схему при которой данные из устройства сразу поступают в адресное пространство процесса ждущего данные? U>Что-то типа следующего: U>Процесс получает буфер (malloc) и передаёт указатель на него и его размер в драйвер (при помощи ioctl). Процесс засыпает в ioctl (при помощи очереди ожидания). В драйвере определяется физический адрес этого буфера и передаётся устройству. Устройство заполняет буфер данными и генерирует прерывание. В обработчике прерывания процесс пробуждается и при выходе из ioctl буфер либо заполнен либо возвращается код ошибки. U>Подскажите хоть в каком направлении рыть.
Вариант:
1. В kernel выделяешь кусок памяти ( DrvBuf ) ( kmalloc или ставь патч bigphysarea с http://www.polyware.nl/~middelink/En/hob-v4l.html#bigphysarea )
2. Регистрируешь в драйвере ( file_operations ) обработчик mmap, вида
int my_mmap( struct file* file, struct vma_area_struct* vma )
{
unsigned long size = vma->vm_end — vma->vm_start; /* == BufferLength см. ниже */
...
return remap_page_range( vma_vm_start, __pa( DrvBuf ), size, vma->vm_page_prot );
}
3. Вызываешь из приложения mmap( NULL, BufferLength, PROT_READ | PROT_WRITE, MAP_SHARED, driverFD, 0 ). Этот вызов подцепить то, что ты зарегистрируешь в п.2
4. mmap возвратит приложению виртуальный отмапенный( ну и слово ) на userspace адрес буфера драйвера ( UserBuf ).
5. Далее
а) если писать в устройсво
Приложение записывает по UserBuf всё что нужно и пинает драйвер. Драйвер скармливает __pa( DrvBuf ) dma'ю или как там у тебя.
б) если читать, то всё наоборот
Здравствуйте, Аноним, Вы писали:
А>Вариант:
А>1. В kernel выделяешь кусок памяти ( DrvBuf ) ( kmalloc или ставь патч bigphysarea с http://www.polyware.nl/~middelink/En/hob-v4l.html#bigphysarea ) А>2. Регистрируешь в драйвере ( file_operations ) обработчик mmap, вида А>int my_mmap( struct file* file, struct vma_area_struct* vma ) А>{ А> unsigned long size = vma->vm_end — vma->vm_start; /* == BufferLength см. ниже */ А> ... А> return remap_page_range( vma_vm_start, __pa( DrvBuf ), size, vma->vm_page_prot ); А>} А>3. Вызываешь из приложения mmap( NULL, BufferLength, PROT_READ | PROT_WRITE, MAP_SHARED, driverFD, 0 ). Этот вызов подцепить то, что ты зарегистрируешь в п.2 А>4. mmap возвратит приложению виртуальный отмапенный( ну и слово ) на userspace адрес буфера драйвера ( UserBuf ). А>5. Далее А> а) если писать в устройсво А> Приложение записывает по UserBuf всё что нужно и пинает драйвер. Драйвер скармливает __pa( DrvBuf ) dma'ю или как там у тебя. А> б) если читать, то всё наоборот
А>Кажись всё. Удачи!
Большое спасибо за помощь.
Для проверки предложенного вами варианта я написал небольшой драйверок и тестовое приложение.
В драйвере в mmap с помощью kmalloc выделяю память (256 байт) и пишу в каждый байт буфера число 55. В приложении вызываю mmap и пытаюсь читать память, выделенную в драйвере. Но чего-то не получается. Вместо записанного числа 55 выводятся одни нули.
В драйвере:
#include <linux/kernel.h>
#include <linux/mm.h>
#include"common.h"char *KBuffer;
int DrvMmap(struct file* File, struct vm_area_struct* vma)
{
unsigned long i = 0;
unsigned long size = vma->vm_end - vma->vm_start;
/*
Мне необходимы буферы от 3 - 100 KB
Поэтому мне достаточно использовать kmalloc (как я понял, эта функция может выделять до 131056 байт)
*/
KBuffer = kmalloc(size, GFP_KERNEL);
if(NULL == KBuffer)
{
printk("testdrv: error! kmalloc can not alloc memory.\n");
return (-1);
}
for(i = 0; i < 256; i++) KBuffer[i] = 55;
return remap_page_range(vma->vm_start,
__pa(KBuffer),
size,
vma->vm_page_prot);
}
В приложении:
#define DEVICE "/dev/testdrv"int Fd = -1;
char *AddrBuff;
int main(void)
{
mknod(DEVICE, S_IFCHR|S_IRWXU|S_IRWXO, (32<<8) | 0);
Fd = open(DEVICE, O_RDWR|O_NONBLOCK|O_NOCTTY);
AddrBuff = (char*) mmap(NULL,
256,
PROT_READ|PROT_WRITE,
MAP_SHARED,
Fd,
0);
if(AddrBuff == MAP_FAILED)
{
perror("mmap");
if( -1 == unlink(DEVICE)) perror("unlink");
return -1;
}
for(int i = 0; i < 256; i++) printf("%u, AddrBuff[i]);
return (0);
}
Здравствуйте, unkn2000, Вы писали:
U>Большое спасибо за помощь. U>Для проверки предложенного вами варианта я написал небольшой драйверок и тестовое приложение. U>В драйвере в mmap с помощью kmalloc выделяю память (256 байт) и пишу в каждый байт буфера число 55. В приложении вызываю mmap и пытаюсь читать память, выделенную в драйвере. Но чего-то не получается. Вместо записанного числа 55 выводятся одни нули.
U>В драйвере: U>
U>#include <linux/kernel.h>
U>#include <linux/mm.h>
U>#include"common.h"
U>char *KBuffer;
U>int DrvMmap(struct file* File, struct vm_area_struct* vma)
U>{
U> unsigned long i = 0;
U> unsigned long size = vma->vm_end - vma->vm_start;
U>/*
U> Мне необходимы буферы от 3 - 100 KB
U> Поэтому мне достаточно использовать kmalloc (как я понял, эта функция может выделять до 131056 байт)
U>*/
U> KBuffer = kmalloc(size, GFP_KERNEL);
U> if(NULL == KBuffer)
U> {
U> printk("testdrv: error! kmalloc can not alloc memory.\n");
U> return (-1);
U> }
U> for(i = 0; i < 256; i++) KBuffer[i] = 55;
U> return remap_page_range(vma->vm_start,
U> __pa(KBuffer),
U> size,
vma->>vm_page_prot);
U>}
U>В приложении:
U>#define DEVICE "/dev/testdrv"
U>int Fd = -1;
U>char *AddrBuff;
U>int main(void)
U>{
U> mknod(DEVICE, S_IFCHR|S_IRWXU|S_IRWXO, (32<<8) | 0);
U> Fd = open(DEVICE, O_RDWR|O_NONBLOCK|O_NOCTTY);
U> AddrBuff = (char*) mmap(NULL,
U> 256,
U> PROT_READ|PROT_WRITE,
U> MAP_SHARED,
U> Fd,
U> 0);
U> if(AddrBuff == MAP_FAILED)
U> {
U> perror("mmap");
U> if( -1 == unlink(DEVICE)) perror("unlink");
U> return -1;
U> }
U> for(int i = 0; i < 256; i++) printf("%u, AddrBuff[i]);
U> return (0);
U>}
U>
U>Что я сделал не так? U>Где ошибся?
Всё правильно, т.к. remap_page_range чистит память.
Здравствуйте, vitka, Вы писали:
V>Всё правильно, т.к. remap_page_range чистит память.
Я изменил код:
int RC = remap_page_range(vma->vm_start,
__pa(KBuffer),
size,
vma->>vm_page_prot);
for(i = 0; i < 256; i++) KBuffer[i] = 55;
return RC;
Теперь remap_page_range не может повлиять на содержимое буфера, но результат тот же.
Как быть?
Я пробовал вообще выносить весь код выделения, отображение и инициализации памяти в функцию probe (она вызывается при загрузке драйвера, когда подключается устройство до вызова мной mmap). А в DrvMmap только возвращал значение возращённое remap_page_range. Но результаты те же.
U>Теперь remap_page_range не может повлиять на содержимое буфера, но результат тот же. U>Как быть?
U>Я пробовал вообще выносить весь код выделения, отображение и инициализации памяти в функцию probe (она вызывается при загрузке драйвера, когда подключается устройство до вызова мной mmap). А в DrvMmap только возвращал значение возращённое remap_page_range. Но результаты те же.
Хмм... Ну вообще-то инициализация в теле mmap хука не есть good. Что дальше делает ядро с vma_struct с ходу не могу сказать. Хотя надо как-нть в этом покопаться. Я делаю всю инициализацию уже после всего, регистрируя некий ioctl.
int fd = open( ... );
char* ubuf = ( char* ) mmap( ... );
...
unsigned int junk;
int rc = ioctl( fd, INIT_CMD, &junk );
for(i = 0; i < 256; i++) fprintf( stderr, "%d ", ubuf[ i ] );
Здравствуйте, vitka, Вы писали:
V>Хмм... Ну вообще-то инициализация в теле mmap хука не есть good. Что дальше делает ядро с vma_struct с ходу не могу сказать. Хотя надо как-нть в этом покопаться. Я делаю всю инициализацию уже после всего, регистрируя некий ioctl. V>int fd = open( ... ); V>char* ubuf = ( char* ) mmap( ... ); V>... V>unsigned int junk; V>int rc = ioctl( fd, INIT_CMD, &junk ); V>for(i = 0; i < 256; i++) fprintf( stderr, "%d ", ubuf[ i ] );
Я попробовал так сделать, но, к сожалению, на результат это не повлияло.
Код не привожу, т.к. он от первоначального варианта отличается только тем, что цикл инициализации вынесен в функцию ioctl .
Не мог ли бы вы показать пример своего работающего кода с реализацией этой идеи?
Чувствую, что идея верная, но видно где-то я что-то упускаю.
Слышал я так же про флаги VM_RESERVED или VM_LOCKED, которые надо устанавливать, что бы страницы не выгружались.
Можете написать, что-нибудь об этом?
Re[7]: Linux. Драйвер. Память.
От:
Аноним
Дата:
23.12.03 16:17
Оценка:
Здравствуйте, unkn2000, Вы писали:
U>Я попробовал так сделать, но, к сожалению, на результат это не повлияло. U>Код не привожу, т.к. он от первоначального варианта отличается только тем, что цикл инициализации вынесен в функцию ioctl . U>Не мог ли бы вы показать пример своего работающего кода с реализацией этой идеи? U>Чувствую, что идея верная, но видно где-то я что-то упускаю. U>Слышал я так же про флаги VM_RESERVED или VM_LOCKED, которые надо устанавливать, что бы страницы не выгружались. U>Можете написать, что-нибудь об этом?
Пример тут займёт пару экранов. Напиши email — скину. Одна только разница, что я не использую kmalloc ( работаю с bigphysarea ( нужны буфера под 120 Mb ) ).
А что касается флагов, то
{ vma->vm_flags |= VM_RESERVED; /* Не надо нам ничего выгружать */
return remap_page_range( ... );
}
Здравствуйте, Аноним, Вы писали:
А>Пример тут займёт пару экранов. Напиши email — скину. Одна только разница, что я не использую kmalloc ( работаю с bigphysarea ( нужны буфера под 120 Mb ) ). А>А что касается флагов, то А>{ vma->>vm_flags |= VM_RESERVED; /* Не надо нам ничего выгружать */ А> return remap_page_range( ... ); А>}
Вышли, пожалуйста на elephant@hotbox.ru.
Как работать с bigphysarea то же было бы полезно посмотреть.
Спасибо.