Jump to content

    

Драйвер

Стало необходимо написать свой драйвер SPI устройства. Драйвер для работы из пользовательского пространства не годится, так как данные нужно принимать от АЦП по SPI в DMA буфер непрерывно(пропуски в данных недопускаются) со скоростью 100К семплов в секунду, чего нельзя добится из user space. Из пользовательского пространства я планирую только забирать блоками через драйвер.

Для моей платформы есть файлы для работы с spi в папке /drivers/spi/

 

Вопрос:

файлы в /drivers/spi - это всё что нужно для написания своего драйвера(модуля ядра)?

 

Просто ранее я писал только standalone приложения и смущает отсутствие в Linux процедур прямого обращения к регистрам (здесь всё через обёртки какие то мудрёные).

В линукс смущает то что нету так привычных мне *.h файлов где через дефайны описаны адреса всех регистров (по мануалу).

Edited by Dubov

Share this post


Link to post
Share on other sites
Просто ранее я писал только standalone приложения и смущает отсутствие в Linux процедур прямого обращения к регистрам (здесь всё через обёртки какие то мудрёные).

Это не так, именно через прямое обращение к регистрам SoC в адресном пространстве и происходит взаимодействие с железом.

 

В линукс смущает то что нету так привычных мне *.h файлов где через дефайны описаны адреса всех регистров (по мануалу).

Они как правило есть, но не всегда на _все_ регистры.

 

Для написания драйвера _устройства_ подключённого через spi слейвом, обращаться напрямую к железу не надо - это делает драйвер spi контроллера (мастера).

Share this post


Link to post
Share on other sites

Сначала ознакомьтесь с принципами функционирования подсистемы SPI в ОС GNU/Linux. Инфа есть в каталоге Documentation/spi исходников ядра или например в книге Essential Linux Device Drivers.

Также для примера можете подсмотреть как соведуют делать для семейства чипов AT91SAM9X link

Share this post


Link to post
Share on other sites
Это не так, именно через прямое обращение к регистрам SoC в адресном пространстве и происходит взаимодействие с железом.

 

 

Они как правило есть, но не всегда на _все_ регистры.

 

Для написания драйвера _устройства_ подключённого через spi слейвом, обращаться напрямую к железу не надо - это делает драйвер spi контроллера (мастера).

так вот этот драйвер мастера, насколько я могу понять, не может складывать данные в буфер произвольной длины, а только работает через read() или write(). Таким образом считать данные из ацп в пространстве пользователя нельзя, точнее можно, но данные будут не нерпрерывные, а спотреями, ввиду латентности операционки.

 

Выход: написать драйвер "с нуля" на уровне ядра.

как? пока сложно себе представляю... увидеть бы пример работы с SPI через DMA в драйвере.

 

Сначала ознакомьтесь с принципами функционирования подсистемы SPI в ОС GNU/Linux. Инфа есть в каталоге Documentation/spi исходников ядра или например в книге Essential Linux Device Drivers.

Также для примера можете подсмотреть как соведуют делать для семейства чипов AT91SAM9X link

подсистема iio - мутная вещь. По моему намного проще обращаться к регистрам как в standalone приложении. Как раз для SAM9260 писал именно "напрямую" через SPI DMA, взяв пример кода из проекта плейера (тоже без ос).

Получилось просто и быстро: standalone процедура превратилась в драйвер уровня ядра. Тем более, в книге Linux Device drivers написано что на уровне ядра к регистрам можно обращаться напрямую.

 

IIO выдумали якобы для унифицирования, но мне показалось там слишком всё усложнено.Тем более из общения с разработчиками драйверов из AD я так и не смог выяснить зачем они написали софтовое тактирование АЦП из юзерспейс. Это же идиотизм: тактировать промышленный АЦП клоком с непредсказуемым джиттером.

 

Отвлёкся от темы.

Share this post


Link to post
Share on other sites
так вот этот драйвер мастера, насколько я могу понять, не может складывать данные в буфер произвольной длины, а только работает через read() или write()

а как должно происходить чтение? вот есть, например, spi_w8r16 два байта читает - почему она не подходит?

Edited by Idle

Share this post


Link to post
Share on other sites
а как должно происходить чтение? вот есть, например, spi_w8r16 два байта читает - почему она не подходит?

Вообще планировалось сделать так:

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

В приложении же отобразить буфер в юзерспейс и читать указатель, который возвращает драйвер.

 

Иными словами: User mode SPI device driver support - не подходит.

 

P.S. Возможно я чего-то не понимаю.

 

Все реализации что я до этого видел предлагают править файл борды и активировать User mode SPI device driver support. Но это не подходит.

 

а как должно происходить чтение? вот есть, например, spi_w8r16 два байта читает - почему она не подходит?

и это как я понял, не DMA чтение

Share this post


Link to post
Share on other sites
Вообще планировалось сделать так:

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

тогда можно выделить в драйвере буфер, периодически дёргать (через таймер, например) чтение по spi, складывать полученные данные в буфер

создать в драйвере chardev и отдавать буфер при чтении из созданного файла

как такой вариант?

Share this post


Link to post
Share on other sites

тогда можно выделить в драйвере буфер, периодически дёргать (через таймер, например) чтение по spi, складывать полученные данные в буфер

создать в драйвере chardev и отдавать буфер при чтении из созданного файла

как такой вариант?и(получается никаких spi_write()).

Насколько я понял мне все пытаются объяснить что линукс предоставляет независимость от платформы даже в таких задачах (чтиение зSPI по DMA в модуле ядра).

 

Все примеры и статьи в интеренете только для вариантов когда пофиг когда данные придут. А мне нужно потоковое накопление в буфере и периодическая отдача в юзерспейс. Со вторым я понимаю как разобраться... с первым - засада.

Share this post


Link to post
Share on other sites
Насколько я понял мне все пытаются объяснить что линукс предоставляет независимость от платформы даже в таких задачах (чтиение зSPI по DMA в модуле ядра).

нет, пишу как я бы это делал

 

А мне нужно потоковое накопление в буфере и периодическая отдача в юзерспейс.

ну вот и накапливайте в буфере периодически читая при помощи дравера мастера, а в чём разница при чтении из полностю своего кода и при использовании драйвера?

 

 

upd

я понял - вы хотите зарядить dma на чтение по spi :)

тогда линуксовая подсистема spi тут не нужна совсем, смотрите на реализацию мастера для своего SoC и пишите что нужно

работа с регистрами происходит так же как и на bare metal - они в памяти

работа с dma - через линуксовый api, с ним мне работать не приходилось

Edited by Idle

Share this post


Link to post
Share on other sites
ну вот и накапливайте в буфере периодически читая при помощи дравера мастера, а в чём разница при чтении из полностю своего кода и при использовании

 

Я думал всегда что в скорости. Если считывать из юзерспейс то можно пропустить данные. Частота дисркетизации АЦП 100kSps.

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

 

Как вариант, можно на ПЛИС сделать буфер, чтобы просто из юзерспейс забирать даные, но это за рамками топика.

 

Вот и пытаюсь:

 

1) найти примеры для OMAP DMA SPI без ОС (standalone), чтобы оформить как модуль ядра.

либо

2) какие-то средства линукса (api для DMA на уровне ядра)

 

работа с регистрами происходит так же как и на bare metal - они в памяти

я пока даже *.h-файл описания регистров для OMAP не нашёл. Настолько всё запутано в исходниках.

Эх, у Atmela в этом было много проще и нагляднее (AT91SAM9260.h - и всё)

Edited by Dubov

Share this post


Link to post
Share on other sites
Я думал всегда что в скорости. Если считывать из юзерспейс то можно пропустить данные. Частота дисркетизации АЦП 100kSps.

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

Вам Idle так и предлагает - создать большой ядерный буфер и пихать туда данные непрерывно без пропусков. А потом из юзерспейса через read() накопленные в ядерном буфере данные выгребать. Вы говорили, что Вам не нравится получать данные путем чтения файла устройства. Почему? Фактически, такое чтение - просто копирование данных из одного участка памяти в другой...

Share this post


Link to post
Share on other sites
Вам Idle так и предлагает - создать большой ядерный буфер и пихать туда данные непрерывно без пропусков. А потом из юзерспейса через read() накопленные в ядерном буфере данные выгребать. Вы говорили, что Вам не нравится получать данные путем чтения файла устройства. Почему? Фактически, такое чтение - просто копирование данных из одного участка памяти в другой...

хорошо, чтение - это просто метод забрать данные.

 

Мне нужен метод положить данные в буфер. Причём в большой буфер.

пока нашёл вот что: spi-omap2-mcspi.c

 

как настроить на приём по DMA ума не приложу(

Edited by Dubov

Share this post


Link to post
Share on other sites
Вам Idle так и предлагает - создать большой ядерный буфер и пихать туда данные непрерывно без пропусков. А потом из юзерспейса через read() накопленные в ядерном буфере данные выгребать. Вы говорили, что Вам не нравится получать данные путем чтения файла устройства. Почему? Фактически, такое чтение - просто копирование данных из одного участка памяти в другой...

ему надо опрашивать ацп со скоростью 100KS/s т.е. каждые 10 мкс

программно через таймеры или delayed work это не получится т.к. слишком часто

вопрос тут - как написать свой драйвер spi мастера для конкретного железа под эти нужды

 

Share this post


Link to post
Share on other sites
ему надо опрашивать ацп со скоростью 100KS/s т.е. каждые 10 мкс

программно через таймеры или delayed work это не получится т.к. слишком часто

вопрос тут - как написать свой драйвер spi мастера для конкретного железа под эти нужды

Именно так.

Например подобное делал для SAM9, взяв пример из проекта wav-плейера. Там по DMA с флешки по SPI считывание происходило.

 

//------------------------------------------------------------------------------
/// Use PDC for SPI data transfer.
/// Return 0 if no error, otherwise return error status.
/// \param pSdSpi  Pointer to a SdSpi instance.
/// \param pData  Data pointer.
/// \param size  Data transfer byte count.
//------------------------------------------------------------------------------
unsigned char SDSPI_PDC_read(SdSpi *pSdSpi, unsigned char *pData, unsigned int size)
{
   AT91PS_SPI pSpiHw = pSdSpi->pSpiHw;
   unsigned int spiIer;

   // Enable the SPI clock
   AT91C_BASE_PMC->PMC_PCER = (1 << pSdSpi->spiId);

   // Disable transmitter and receiver
   pSpiHw->SPI_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS;

   // Receive Pointer Register
   pSpiHw->SPI_RPR = (unsigned long)pData;
   // Receive Counter Register
   pSpiHw->SPI_RCR = size;
   // Transmit Pointer Register
   pSpiHw->SPI_TPR = (unsigned long)dummy_ff_block;
   // Transmit Counter Register
   pSpiHw->SPI_TCR = size;

  // spiIer = AT91C_SPI_RXBUFF;

   // Enable transmitter and receiver
   pSpiHw->SPI_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN;

while(! (pSpiHw->AT91C_SPI_SR & AT91C_SPI_ENDRX));	
pSpiHw->AT91C_SPI_PTCR = AT91C_PDC_RXTDIS;
pSpiHw->AT91C_SPI_PTCR = AT91C_PDC_TXTDIS;
   // Interrupt enable shall be done after PDC TXTEN and RXTEN
  // pSpiHw->SPI_IER = spiIer;

   return 0;
}

 

вобщем там свелось всё к умелому обращение с регистрами SPI_RPR, SPI_RCR, SPI_TPR, SPI_TCR.

Для OMAP не думаю что сложнее, но, зараза, не могу понять...

 

 

Другой вопрос: почему в Linux драйверах всё так сложно? никогда не увидишь имён регистров прямых, всё через структуры структур структур и т.д. ?

безопасность? универсальность?

Edited by Dubov

Share this post


Link to post
Share on other sites

Простейший способ "в лоб" - это char device, внутри которого 2 буфера, в один пишем, другой готов к чтению

из приложения через read(). При заполнении одного буфера DMA перезапускается на другой буфер. Либо "дмашить"

в кольцевой буфер. Как сделать лучше - см. спеки на железо.

 

Есть нюансы :) Во-первых, вам нужно перезапустить DMA за 10мкс (по прерыванию, например). Не факт, что вы

успеете, т.к. если обращение к регистру занимает (к примеру) 1мкс, а вам нужно перезаписать 5шт - это уже 10мкс.

Некоторые DMA умны настолько, что могут сами брать дескрипторы из памяти и закольцовываться по их списку.

В этом случае перезапускать DMA нет необходимости. Если это не так и терять данные нельзя вообще - то вы

близки к теоретическому пределу железа - опять же, см. спеки на железо.

Во-вторых, при использовании своего char dev нужно убедиться, что никто через линуксовый SPI-фреймворк не

обращается к SPI контроллеру (и через DMA фреймворк тоже).

 

Что касается "сложности" драйверов в linux - это нормально :) На самом деле там все довольно просто - почти

все разбито на слои и фреймворки для облегчения портируемости и расширения. Фактически, там используется ООП

подход, со всеми вытекающими. В итоге вход в это хозяйство усложняется (и кажется странным для людей

работавших до этого с МК осями/bare metal), но иначе никак. Вообще, такой подход характерен для всех

не-tiny мультиплатформенных ОС.

 

P.S. Есть фреймворк comedi, все еще стандарт де-факто для data acquisition. Но 100К семплов/с - немалая величина,

и с ней оверхед фреймворка может стать неприемлемым.

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now