LINUX.ORG.RU

Linux driver помогите с постоением архитектуры драйвера

 ,


0

3

Суть: создаю символьный драйвер он должен прокачивать 1Гбит/сек для этого имеется очередь DMA от PCI железа. Для приема я создал поток(kthread) и связал его с одним из ядер. Поток запускается и ждет события (wait_event_interrupt_timeout). Событие наступает по прерыванию(irq). Как только наступает прерывание irq делаем disable irq и выстовляем wake_up. Проснувшись поток работает пока все не вычитает. Читает и складывает в kfifo. Как только чтение окончено делаем enable irq, возвращаемся к ожиданию события. read драйвера считывает данные из kfifo. Так примерно работает драйвер. Я пытался создать нечто подобное сетевому драйверу c napi, чтобы мой поток был функцией опроса.

Некоторый нюанс по работе драйвера выяснился при отладке. При опросе можно получить что нет данных на чтение и перейти в ожидание события (irq), как бы норм, но выяснилось что драйвер не успевает все выгребать и очередь dma становиться переполненой -> железо падает. Дописан кастыль - приходиться опрашивать 100 раз есть ли данные, и если 100 раз нет, то только тогда перейти в ожидание. Кастыль помогает принять все данные. Но, выясняется другое, пользовательское приложение не успевает выгребать kfifo [не могу понять по какой причине](приложение, имеет поток на чтение, в котором стоит while(1) read(/dev/my_device)) в результате kfifo будет перезаписано потоком приема -> потеря данных.

Прошу высказать советы и пожелания по поводу улучшения или создания новой архитектуры драйвера.


А зачем делать disable irq? Я тоже нуб и многое не знаю, но просто со стороны хоста ты возможно должен сообщать что готов принимать новые порции данные (через MMIO например), иначе предположительно никаких очередей не напасешься.

Ссылка: моя тема по этому вопросу.

Кстати, если есть такая возможность, кидай код - я посмотрю и скажу.

I-Love-Microsoft ★★★★★
()
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)

Твой драйвер работает в СРВ? Если нет, то выставь какой-нибудь флаг от пользователя «готов к новой порции данных» и получай их от железки только после этого. И несколько похоже на то, что железка не справляется с таким потоком данных.

Fedorast
()

Твоя железка умеет автоматом Scatter-Gather list в DMA? Если умеет то лучше использовать его, если нет, то можно наколхозить самому. Так не нужно будет 100 раз опрашивать железку.

Но, выясняется другое, пользовательское приложение не успевает выгребать kfifo [не могу понять по какой причине](приложение, имеет поток на чтение, в котором стоит while(1) read(/dev/my_device)) в результате kfifo будет перезаписано потоком приема -> потеря данных.

Похоже уже нужен реалтайм. Можно начать с PREEMPT_RT, если не поможет, то Xenomai/RTAI (правда приложение и драйвер придется сильнее перепиливать)

Moncruist
()

приходиться опрашивать 100 раз есть ли данные, и если 100 раз нет

Что-то явно принципиально не так. Я например пробовал такой вариант: dma_map_single 4096 байт и шлю адрес в устройство (т.е. не SG list, а просто одна страничка), устройство туда пишет данные и делает interrupt, далее dma_unmap single и пошло по новой. Даже в таком варианте и таком крошечном буфере с пересылкой данных пользователю получаю 1 Гбит/с на старом компе без каких-либо напрягов переполнений потерь и костылей.

I-Love-Microsoft ★★★★★
()

А почему kthread? Он же работает в контексте процесса, что автоматически медленнее. Такие вещи нужно делать на tasklet + irq handlers (то есть в softirq), причем количество tasklet в идеале должно быть по количеству ядер, как и количество прерываний (если такое возможно аппаратно), потому что tasklet исполняется на том ядре, где был поставлен в очередь планировщика. При этом нет необходимости постоянно опрашивать данные, можно просто читать в тасклете из DMA пока не кончатся данные или не будет прочитано определенное количество, и затем решедулить тасклет, если осталось что читать. Также тасклет решедулить в irq handler.
kfifo очень медленная вещь, если хочется высокой производительности (а гигабит/с с символьного устройства это довольно высокие требования), почти всегда нужно прибегать к zero-copy структурам данных, в том числе и SG.

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

почти всегда нужно прибегать к zero-copy структурам данных, в том числе и SG

Позволяет ли zero-copy класть данные из устройства сразу в пользовательский процесс (user space)? Без copy_to_user.

I-Love-Microsoft ★★★★★
()
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Позволяет ли zero-copy класть данные из устройства сразу в пользовательский процесс (user space)? Без copy_to_user.

Пусть драйвер юзеру по запросу ммапит свой буфер.

mv ★★★★★
()

Для приема я создал поток(kthread) и связал его с одним из ядер. Поток запускается и ждет события (wait_event_interrupt_timeout). Событие наступает по прерыванию(irq). Как только наступает прерывание irq делаем disable irq и выстовляем wake_up.

Это очень медленно. Почитай про top и bottom half.

mv ★★★★★
()

Зачем нужна нить - из прерывания занести данные в kfifo невозможно?

tailgunner ★★★★★
()

1. Опубликуй свой модуль в удобно компиляемом виде на базе последнего ядра (релиза или rc), с исходниками тебе не прийдётся долго и нудно объяснять, как работает твой драйвер, достаточно будет объяснить замысел. Такой драйвер не может быть секретным, так как по описанию он тупой посредник в обмене данных между железом и userspace.

2. Сделай, чтобы драйвер сам умел производить поток данных нужной мощности, даже без железа. Либо это можно сделать с помощью qemu. Если удастся - поздравляю, ты сам себе здорово помог в отладке и ускорил свой процесс разработки, а также дал возможность людям тебе помочь не имея твоей железки.

3. Напиши на английском языке описание ситуации и способа тестирования, а также ссылку на код, на kernelnewbies@kernelnewbies.org и, если есть планы на проталкивание драйвера в апстрим, на devel@driverdev.osuosl.org

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

мой драйвер это переработанный http://lxr.free-electrons.com/source/drivers/net/ethernet/altera где используется msgdma. Это сетевой драйвер соответственно у меня нет netif_napi_add Рабочий код опубликую завтра, когда буду на работе

ri_mik
() автор топика

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

и да, PCI чипы уже лет десять как не производят. так что лучше сразу закладываться на PCIe.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 3)
Ответ на: комментарий от Iron_Bug

Не точно указал у меня именно PCIe 2x. Что касается dma, то в железе очередь из dma дескрипторов и надо успевать подгребать новый дескриптор, как только отработан хотябы один.

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

Просто tasklet не помог не успевает. Тасклет высокого приорите помогли на машине с процом i7 3.4 Г на 8 яред, все успевают А вот на машине «Эльбрус - 4c» где и должно все работать тоже не помогает.

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

а зачем там тогда какое-то копирование в fifo? железяка льёт данные тебе в память - ты их юзеру мапишь и всё. а с дескрипторами работаешь у себя внутри, у юзера уже конечные адреса в замапленном пространстве. обычно на железяках реализован кольцевой буфер и туда железяка льёт данные. а ты получаешь адреса в памяти и их транслируешь юзеру.

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

Думаю napi для сетевых драйверов не просто так придумали. вот прием данных

struct ring_buffer
{    
    char * real_buffer;
    dma_addr_t dma_addr;
    u32 len;   
};

struct msgdma_dev_t
{
    struct pci_dev *pci_device; //заполнить эту структуру с верхнего уровня    

    void *retainer_device; //основная структура данных

    /* mSGDMA Rx Dispatcher address space */
    void __iomem *rx_dma_csr;
    void __iomem *rx_dma_desc;
    void __iomem *rx_dma_resp;

    /* mSGDMA Tx Dispatcher address space */
    void __iomem *tx_dma_csr;
    void __iomem *tx_dma_desc;

    /* Rx buffers queue */
    struct ring_buffer *rx_ring;
    u32 rx_count_receive_ready; //счетчик отработанных записей на прием
    u32 rx_count_receive_last;

    u32 rx_ring_size;
    u32 rx_dma_buf_sz;

    /* Tx ring buffer */
    struct ring_buffer *tx_ring;
    u32 tx_count_transmit;      //счетчик записи в dma
    u32 tx_count_transmit_ready;//счетчик отработанных записей передачи в dma

    u32 tx_ring_size;

    // Rx DMA & interrupt control protection
    spinlock_t rxdma_irq_lock;

    //unsigned char  rx_flag;
    //очередь ожидания для read пока не произойдет прерывания
   // wait_queue_head_t rx_wait_queue;
    //структура для работы потока приема
    //struct task_struct *rx_thread;


    struct tasklet_struct copy_tasklet;
    //TODO debug
    unsigned int count_irq;
    unsigned int count_packet;
    unsigned int count_length;
    unsigned int count_len_not8192;
    //struct debug_level debug_level_array[200];
   // int debug_level_num;

};


// Put buffer to the mSGDMA RX FIFO

static void msgdma_add_rx_desc(struct msgdma_dev_t *p_msgdma_dev, struct ring_buffer *rxbuffer)
{
//    u32 len = p_msgdma_dev->rx_dma_buf_sz;
    u32 len = 0xFFFFFFFF;
    dma_addr_t dma_addr = rxbuffer->dma_addr;
    //u32 control = MSGDMA_DESC_CTL_RX_SINGLE;
    u32 control = (MSGDMA_DESC_CTL_END_ON_EOP
                    | MSGDMA_DESC_CTL_END_ON_LEN
                    | MSGDMA_DESC_CTL_TR_COMP_IRQ
                    | MSGDMA_DESC_CTL_EARLY_IRQ
                    | MSGDMA_DESC_CTL_TR_ERR_IRQ
                    | MSGDMA_DESC_CTL_GO);



    csrwr32(0, p_msgdma_dev->rx_dma_desc, msgdma_descroffs(read_addr_lo));
    csrwr32(0, p_msgdma_dev->rx_dma_desc, msgdma_descroffs(read_addr_hi));

    csrwr32(lower_32_bits(dma_addr), p_msgdma_dev->rx_dma_desc, msgdma_descroffs(write_addr_lo));
    csrwr32(upper_32_bits(dma_addr), p_msgdma_dev->rx_dma_desc, msgdma_descroffs(write_addr_hi));

    csrwr32(len, p_msgdma_dev->rx_dma_desc, msgdma_descroffs(len));
    csrwr32(0, p_msgdma_dev->rx_dma_desc, msgdma_descroffs(burst_seq_num));
    csrwr32(0x00010001, p_msgdma_dev->rx_dma_desc, msgdma_descroffs(stride));
    csrwr32(control, p_msgdma_dev->rx_dma_desc, msgdma_descroffs(control));
}
//function tasklet
void msgdma_do_tasklet(unsigned long int arg)
{

     struct msgdma_dev_t *p_msgdma_dev = (struct msgdma_dev_t *)arg;


     unsigned int entry;
     unsigned char *buffer;

     u32 length = 0;
     u32 status = 0;
     int resp;


     resp = csrrd32(p_msgdma_dev->rx_dma_csr, msgdma_csroffs(resp_fill_level)) & 0xffff;
     if(resp)
     {
         length = csrrd32(p_msgdma_dev->rx_dma_resp, msgdma_respoffs(bytes_transferred));
         status = csrrd32(p_msgdma_dev->rx_dma_resp, msgdma_respoffs(status));
     }


    p_msgdma_dev->count_packet++;
     p_msgdma_dev->count_length += (length-2);

     if(unlikely(status & 0xFF))
     {//error exit
         BLOCK_MES(" %s ERROR = %x\n", __func__, (status & 0xFF));
         return;
     }

     if((length-2) == 0)
     {
         BLOCK_MES("RESP = %u length = %u p_msgdma_dev->count_packet = %u\n", resp, length, p_msgdma_dev->count_packet);
         return; //normal exit
     }



     entry = p_msgdma_dev->rx_count_receive_ready % p_msgdma_dev->rx_ring_size;
//	BLOCK_MES("%s entry = %u length = %u", __func__, entry, length);
     buffer = p_msgdma_dev->rx_ring[entry].real_buffer;
     if (unlikely(!buffer))
     {
         BLOCK_MES("%s: Inconsistent Rx descriptor %d\n", __func__ , entry);
         return ;
     }

     //открепляем буффер от девайса
     dma_sync_single_for_cpu(&p_msgdma_dev->pci_device->dev,
                             p_msgdma_dev->rx_ring[entry].dma_addr,
                             p_msgdma_dev->rx_ring[entry].len,
                             DMA_FROM_DEVICE);

     //забираем буффер наверх
   //  write_to_fifo(p_msgdma_dev, buffer, length);


     dma_sync_single_for_device(&p_msgdma_dev->pci_device->dev,
                                 p_msgdma_dev->rx_ring[entry].dma_addr,
                                 p_msgdma_dev->rx_ring[entry].len,
                                 DMA_FROM_DEVICE);

     //добавляем обработанный буффер в очередь
     msgdma_add_rx_desc(p_msgdma_dev, &p_msgdma_dev->rx_ring[entry]);
     p_msgdma_dev->rx_count_receive_ready++;
}

//////////////////////////////////////////////////////////////////////////////////
// DMA RX FIFO interrupt routing

//////////////////////////////////////////////////////////////////////////////////
/// \brief altera_isr
/// \param irq
/// \param dev_id
/// \return
///Обработка прерывания очищаем статус и блокируем прерывание запускаем поток приема данных
static irqreturn_t altera_isr(int irq, void *dev_id)
{
    struct msgdma_dev_t *p_msgdma_dev = (struct msgdma_dev_t *)dev_id;


    //TODO debug

    //int resp, entry, rx_length, rx_status;

    if (unlikely(!p_msgdma_dev))
    {
        BLOCK_MES("%s: invalid dev pointer\n", __func__);
        return IRQ_NONE;
    }
    p_msgdma_dev->count_irq++;
    //заводим тасклет
    tasklet_hi_schedule(&p_msgdma_dev->copy_tasklet);
    spin_lock(&p_msgdma_dev->rxdma_irq_lock);
        // reset IRQs
    msgdma_clear_rxirq(p_msgdma_dev);
    //    msgdma_disable_rxirq(p_msgdma_dev); //блокируем прерывание
    spin_unlock(&p_msgdma_dev->rxdma_irq_lock);
    return IRQ_HANDLED;

    //    BLOCK_MES("RX IRQ !\n");
}
 
каждый пакет из dma по 8192 байта, пока я его нетрогаю, просто чекаю и добавляю в очередь дескрипторов новый дескриптор после теста я вывожу p_msgdma_dev->count_irq и p_msgdma_dev->count_packet если они не равны значит не успели.

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

Что касается dma, то в железе очередь из dma дескрипторов и надо успевать подгребать новый дескриптор, как только отработан хотябы один.

максимальная длина буфера у одного дескриптора какая ? если страница 4096 байт то шелезяка будет генерировать на гигабите примерно 30 тысяч прерываний в секунду

Думаю napi для сетевых драйверов не просто так придумали.

там есть ограничение - стандартный фрейм 1500 байт, а у тебя если есть возможность - увеличивай буфер DMA который пишется за раз и будет тебе счастье. И да - выкинь kfifo и бесполезные копирования, тут уже писали выше про зерокопи.

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

длинна одного за раз 8192 байта Я так понял надо копать в сторону mmap, так еще ни разу не делал надо почитать на эту тему, думаю ни че сложного.

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

а максимум могу 64к наверное отправить и принять если железяку доделают и она потянет такой буффер

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

а максимум могу 64к наверное отправить и принять если железяку доделают и она потянет такой буффер

так ты уж объясни им что изернет-фреймы можно просто дропать - протоколы верхнего уровня это отработают, а если они не хотят терять данные то надо как-то увеличивать время чтобы система успевала обрабатывать прерывания, при той же скорости передачи - это только за счет буферизации.

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