Linux. Драйвер. Память.
От: unkn2000  
Дата: 18.12.03 15:32
Оценка:
Как в драйвере (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'ю или как там у тебя.
б) если читать, то всё наоборот

Кажись всё. Удачи!
Re[2]: Linux. Драйвер. Память.
От: unkn2000  
Дата: 22.12.03 09:49
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Вариант:


А>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);
}

Что я сделал не так?
Где ошибся?
Re[3]: Linux. Драйвер. Память.
От: vitka Россия  
Дата: 22.12.03 11:42
Оценка:
Здравствуйте, 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 чистит память.
Re[4]: Linux. Драйвер. Память.
От: unkn2000  
Дата: 22.12.03 12:42
Оценка:
Здравствуйте, 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. Но результаты те же.
Re[5]: Linux. Драйвер. Память.
От: vitka Россия  
Дата: 23.12.03 09:25
Оценка:
Здравствуйте, unkn2000, Вы писали:
U>Я изменил код:
U>
U>    int RC = remap_page_range(vma->vm_start, 
U>                              __pa(KBuffer), 
U>                              size, 
                              vma->>>vm_page_prot);

U>    for(i = 0; i < 256; i++) KBuffer[i] = 55;

U>    return RC;

U>


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 ] );
Re[6]: Linux. Драйвер. Память.
От: unkn2000  
Дата: 23.12.03 13:09
Оценка:
Здравствуйте, 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( ... );
}
Re[8]: Linux. Драйвер. Память.
От: unkn2000  
Дата: 23.12.03 16:48
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Пример тут займёт пару экранов. Напиши email — скину. Одна только разница, что я не использую kmalloc ( работаю с bigphysarea ( нужны буфера под 120 Mb ) ).

А>А что касается флагов, то
А>{
vma->>vm_flags |= VM_RESERVED; /* Не надо нам ничего выгружать */
А> return remap_page_range( ... );
А>}

Вышли, пожалуйста на elephant@hotbox.ru.
Как работать с bigphysarea то же было бы полезно посмотреть.
Спасибо.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.