Jump to content

    

свой драйвер АЦП

Надобно написать драйвер(модуль ядра) АЦП.

 

Прочитав Linux Device Drevers вижу очевидный путь: пишу модуль ядра, который представляет собой standalone программу, принимающую данные от АЦП поп рерыванию от АЦП(сигнал когда данные готовы), и оформленную как собственно модуль/драйвер для Linux (благо доступ к регистрам есть из пространства ядра)

 

Но! Вижу что устройства "по-хорошему" регистрируются в sysfs, а вот этот путь не совсем понятен.

 

Вопрос: чем чреват первый путь и и как писать для второго(статейки может какие есть с примерами...)?

Edited by TigerSHARC

Share this post


Link to post
Share on other sites

Ну вообще доступ к регистрам можно и из пользовательского режима получить, с mmap. Что вы называете "standalone" программой которая к тому же модуль ядра ? Драйве - он и в Африке драйвер. Даете драйверу пулы памяти, делаете нужный ioctl и пусть драйвер заполняет данными буфера, и возвращает их. Вобщем-то лучше бы он их и выделял. Все эти /proc и sysfs - линуксоиды сами с ними разобраться не могут, я по старинке через mknod и файл устройства работаю. Геммора меньше, да и Техасовские дрова так и написаны (и не только они). Что-то вызывающее ясность есть например тут http://www.linuxdevcenter.com/pub/a/linux/...nder-linux.html

 

Share this post


Link to post
Share on other sites
Ну вообще доступ к регистрам можно и из пользовательского режима получить, с mmap...[/url]

 

 

нет, ну зачем же тогда драйвер? в драйвере я по прерыванию заполняю буфер, настраиваю порт (в частности SPI) - и это всё работая через макросы регистров. Разве в юзерспейс так можно? (и с той же скоростью...)

Share this post


Link to post
Share on other sites

Не, так делать не надо. Но можно.. Мне если лень драйвер компилить на очередном проце, а надо понять, что делает тот или иной регистр - делаю так. А вы пишите драйвер. Вообще посмотрите лучше реализацию v4l video 4 linux - там capture drivers - они ж тоже самое делают. Модифицировать их как надо, и дело в шляпе.

Share this post


Link to post
Share on other sites

С sysfs пробовал немного, все же это не совсем файловая система, это скорее некая обертка для kernel objects. А с procfs все достаточно просто.

Сначала нужно в своем модуле в методе init зарегистрировать драйвер устройства. Проще всего это сделать для platform_bus, так как для этой шины правило соответствия довольно простое - одинаковые имена у драйвера и устройства. Итого:

static int __init my_driver_init(void)
{
    int rc;

    my_proc_dir = proc_mkdir("my_dir", 0);
    if (!my_proc_dir)
        bail_out...

    rc = platform_driver_register(&my_driver);
    if (rc!=0)
        bail_out...

    // тут же можно зарегистрировать и само устройство, если нет более подходящего места.
    // вообще говоря, LDM подразумевает, что устройство регистрируется на шине неким сканером шины
    // для реализации механизма plug and play, но это необязательно.

    return 0;
}

 

В данном коде все довольно просто, системе зарегистрировали базовый каталог в proc и драйвер для устройства. Драйвер устройства теперь будет проверяться всякий раз, когда в системе происходят изменения на шине platform_bus. Если драйвер найдет устройство, то будет вызван метод probe драйвера, где он сможет устройство взять под свой контроль. Там же происходит инициализация рабочих структур управления устройством: выделение памяти, ремаппинг адресного пространства ввода-вывода, назначение irq, а также регистрация в procfs файлов для управления устройством из пользовательского приложения. Вот примерно так:

 

static int my_driver_probe(struct platform_device *pdev)
{
        struct resource  *r_mem, *r_irq;
        my_device_t       *ctx;
        int               rc;

        pdev_info(pdev, "starting device...\n");

        r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
        if (!r_mem || !r_irq)
                return -EINVAL;

        ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
        if (!ctx)
                return -ENOMEM;

        platform_set_drvdata(pdev, ctx);
        ctx->pdev = pdev;

        /* check the resources */
        ctx->irq = r_irq->start;
        ctx->mem_base = r_mem->start;
        ctx->mem_size = r_mem->end - r_mem->start + 1;

        /* registering module base directory */
        snprintf(ctx->name_base, sizeof(ctx->name_base), "%u", pdev->id);
        ctx->proc_base = proc_mkdir(ctx->name_base, my_proc_dir);
        if (!ctx->proc_base) {
                rc = -ENOMEM;
                goto fail_proc_base;
        }

        /* registering interface to work with ADC */
        ctx->proc_adc = create_proc_entry("adc_data", 0644, ctx->proc_base);
        if (!ctx->proc_adc) {
                rc = -ENOMEM;
                goto fail_proc_adc;
        } else {
                /* fill in the table of registers operations */
                extern struct file_operations  mors8_registers_fops;

                ctx->proc_regs->data = ctx;
                ctx->proc_regs->size = 256;  // размер файла АЦП, если имеет смысл
                ctx->proc_regs->proc_fops = &my_adc_fops;
                atomic_set(&ctx->open_adc, 0);
        }

        // аналогично создаем прочие файловые интерфейсы к драйверу
        ...

        /* now register irq handler */
        rc = request_irq(ctx->irq, my_interrupt_handler, IRQF_SHARED, "my_adc", ctx);
        if (rc) {
                pdev_err(pdev, "unable to set up irq %d, error %d\n", ctx->irq, rc );
                goto fail_irq;
        } else {
                /* setup interrupt enable for the device */
        }

        return 0;

fail_irq:
fail_proc_adc:
        remove_proc_entry(ctx->name_base, my_proc_base);
fail_proc_base:
        return rc;
}


static int my_driver_remove(struct platform_device *pdev)
{
        my_device_t *ctx;

        ctx = platform_get_drvdata(pdev);

        pdev_info(pdev, "stopping device...\n");

        /* removing interrupt handler */
        free_irq(ctx->irq, ctx);                    /* free handler */

        /* removing file adc_data */
        if (ctx->proc_adc)
                remove_proc_entry("adc_data", ctx->proc_base);

        return 0;
}

 

Дальше нужно описать файловый интерфейс управления АЦП. Приведу лишь только процедуру чтения и структуру дескриптора:

static int fops_adc_read(struct file *f, char __user *buf, size_t n, loff_t *pos)
{
        my_device_t *ctx = (my_device_t *) f->private_data;
        loff_t      offs = (*pos) & ~3;
        size_t      total = 0;

        if (offs >= ctx->proc_adc->size)
                return 0;

        if (offs + n >= ctx->proc_adc->size )
                n = ctx->proc_adc->size - offs;

        n &= ~3;

        while(n > 0) {
                u32 data;
                int rc;

                rc = my_read_adc (ctx, &data);
                if (rc)
                        break;

                // copy data to user
                copy_to_user(buf, &data, 4);

                // take next word
                buf   += 4;
                offs  += 4;
                total += 4;
                n -= 4;
        }

        *pos = offs;

        return total;
}


struct file_operations my_adc_fops = {
        .owner =        THIS_MODULE,
        .open =         fops_adc_open,
        .release =      fops_adc_close,
        .read =         fops_adc_read,
        .write =        fops_adc_write,
        .ioctl =        fops_adc_ioctl,
        .llseek =       fops_adc_seek,
};

 

Собственно и все. После insmod такого драйвера в системе появится "файл" в пути /proc/my_dir/0/adc_data, который можно открыть и читать любыми средствами, доступными в линукс. Например, можно сделать dd if=/proc/my_dir/0/adc_data of=/mnt/nfs/test bs=512 count=1, и тогда можно скинуть в файлик на NFS результаты АЦП, и потом их глазками быстро посмотреть, скажем в mc или через QUI.

Edited by Hoodwin

Share this post


Link to post
Share on other sites
Вижу что устройства "по-хорошему" регистрируются в sysfs, а вот этот путь не совсем понятен.

"По-хорошему" для АЦП скорее всего есть уже есть отдельная подсистема в которой надо зарегистироваться и реализовать методы и sysfs будет "из коробки".

Здесь Dubov создавал несколько тем про драйвер АЦП.

Share this post


Link to post
Share on other sites

Как-то пользовался драйвером АЦП для at91-adc, для его 4-х каналов, для arm926-го (at91sam9m10g45ek). В этом драйвере есть и регистрация файлов в sysfs с чтением и записью для созданного misc-устройства, реализованы системные вызовы open/read/write/poll/close, используется dma. Выложил патч, добавляющий этот драйвер в ядро, на pastebin - может быть пригодится. Да, драйвер проверенный и рабочий.

А так procfs - да, проще в реализации, можно работать с ней откуда угодно, не тянет за собой ничего (например). Именно поэтому procfs в любой рабочей системе уже стала похожа на винегрет "из всего, что только в системе есть". :biggrin:

Драйверы для spi-ных АЦП тоже имеются (например, для AD7887). Может быть даже готовый есть для нужного АЦП.

Share this post


Link to post
Share on other sites
Как-то пользовался драйвером АЦП для at91-adc, для его 4-х каналов, для arm926-го (at91sam9m10g45ek). В этом драйвере есть и регистрация файлов в sysfs с чтением и записью для созданного misc-устройства, реализованы системные вызовы open/read/write/poll/close, используется dma. Выложил патч, добавляющий этот драйвер в ядро, на pastebin - может быть пригодится. Да, драйвер проверенный и рабочий.

А так procfs - да, проще в реализации, можно работать с ней откуда угодно, не тянет за собой ничего (например). Именно поэтому procfs в любой рабочей системе уже стала похожа на винегрет "из всего, что только в системе есть". :biggrin:

Драйверы для spi-ных АЦП тоже имеются (например, для AD7887). Может быть даже готовый есть для нужного АЦП.

Мне кажется procfs/sysfs не лучшие интерфейсы для большого обьема данных или данных которые идут непрерывно и слишком быстро. Большой оверхед на открытие-закрытие и syscall вызовы.

IMHO удобнее mmap с каким-нить ring buffer, которое удобно обрабатывать через readv.

Share this post


Link to post
Share on other sites
Мне кажется procfs/sysfs не лучшие интерфейсы для большого обьема данных или данных которые идут непрерывно и слишком быстро. Большой оверхед на открытие-закрытие и syscall вызовы.

IMHO удобнее mmap с каким-нить ring buffer, которое удобно обрабатывать через readv.

Открытие-закрытие можно делать только один раз - расходы на них сокращаются в ноль по сравнению с временем работы. В приведенном по ссылке драйвере - имеется двойная буферизация с поочередным дма-заполнением каждого, а скорость считывания такова, что системный вызов будет "срабатывать" раз в несколько десятков мс. Накладные расходы - нивелируются. Ко всему - с mmap, наверное, будет не очень удобно реализовывать блокирующее чтение, которое есть в этом драйвере. К тому же - при больших частотах дискретизации возможны потери данных, если читать малыми порциями через опрос регистров. Выигрыш mmap кажется сомнительным. В добавок, в драйвер можно удобно вставить предобработку данных с АЦП без пересборки приложения (например, скользящее среднее, медиана).

Может я не совсем уловил высказанную идею и её преимущества. Можно описать подробней, какая связка mmap-а, ring-buffer-a и readv имеется ввиду?

В целом, конечно, приличней работать через какое-нибудь символьное устройство в devfs, но не уверен, что по скорости работа через него будет отличаться, например, от sysfs (те же copy_{to,from}_user(..)).

Share this post


Link to post
Share on other sites
Можно описать подробней, какая связка mmap-а, ring-buffer-a и readv имеется ввиду?

 

Например как написал DASM - v4l2 streaming

http://lwn.net/Articles/240667/

 

 

Share this post


Link to post
Share on other sites
В ядре (хоть и в staging) есть специальный фреймворк для подобных девайсов, ставший уже стандартом де-факто: http://www.comedi.org/doc/index.html

 

Для АЦП ближе тогда уж

http://wiki.analog.com/software/linux/docs/iio/iio

 

вот еще есть интересный фреймворк

http://www.ohwr.org/projects/zio

 

так что стандартом comedi сложно назвать

Share this post


Link to post
Share on other sites
Собственно и все. После insmod такого драйвера в системе появится "файл" в пути /proc/my_dir/0/adc_data, который можно открыть и читать любыми средствами, доступными в линукс. Например, можно сделать dd if=/proc/my_dir/0/adc_data of=/mnt/nfs/test bs=512 count=1, и тогда можно скинуть в файлик на NFS результаты АЦП, и потом их глазками быстро посмотреть, скажем в mc или через QUI.

Тоже задумался над написанием драйвера , который будет принимать поток с 8 каналов АЦП по msasp в режиме tdm на пладе Beaglebone. Вроде бы понятно как создать устройство в /proc, создать файловый интерфейс для записи чтения.

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

Остался один не понятный момент, как внутри драйвера организовать функцию, в которой постоянно будет проверяться сколько в буфере DMA есть данных . Как в драйвере можно организовать функцию, в которую гарантированно буду попадать не реже ~10 ms? Какие есть стандартные средства для этого?

 

И ещё вопрос. Возможен ли такой вариант: в драйвере заряжаю DMA на определённую область памяти и уже из USER_SPACE получаю доступ к ней. А информацию по поводу по какому адресу делать mmap зачитаваю из файла устройства в /proc. Так же из файлов читаю текущее положение кольцевых указателей.

 

Заранее спасибо за ответы.

Edited by Муравей

Share this post


Link to post
Share on other sites
Открытие-закрытие можно делать только один раз - расходы на них сокращаются в ноль по сравнению с временем работы. В приведенном по ссылке драйвере - имеется двойная буферизация с поочередным дма-заполнением каждого, а скорость считывания такова, что системный вызов будет "срабатывать" раз в несколько десятков мс. Накладные расходы - нивелируются. Ко всему - с mmap, наверное, будет не очень удобно реализовывать блокирующее чтение, которое есть в этом драйвере. К тому же - при больших частотах дискретизации возможны потери данных, если читать малыми порциями через опрос регистров. Выигрыш mmap кажется сомнительным. В добавок, в драйвер можно удобно вставить предобработку данных с АЦП без пересборки приложения (например, скользящее среднее, медиана).

Может я не совсем уловил высказанную идею и её преимущества. Можно описать подробней, какая связка mmap-а, ring-buffer-a и readv имеется ввиду?

В целом, конечно, приличней работать через какое-нибудь символьное устройство в devfs, но не уверен, что по скорости работа через него будет отличаться, например, от sysfs (те же copy_{to,from}_user(..)).

Если читать единственное значение, то да, sysfs/procfs может и подойдет. А если скажем ADC с определенной частотой дискретизации заполняет буфер и программе иногда приходится вычитывать несколько значений сразу?

Тогда скорее всего это будет много структур { time, value }, возможно метка состояния структуры/флаг (кому сейчас принадлежит, ядру или userspace процессу) и т.п.

Принцип работы приблизительно описан в V4L, или сложнее - в PF_RING.

Почитайте тут: http://www.linuxjournal.com/article/6345 , относительно кратко расписано.

Хотя в чем-то с вами согласен, можно сделать и через read, но мне кажется накладных расходов больше (если судить по статье).

Share this post


Link to post
Share on other sites
Тоже задумался над написанием драйвера , который будет принимать поток с 8 каналов АЦП по msasp в режиме tdm на пладе Beaglebone. Вроде бы понятно как создать устройство в /proc, создать файловый интерфейс для записи чтения.

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

Остался один не понятный момент, как внутри драйвера организовать функцию, в которой постоянно будет проверяться сколько в буфере DMA есть данных . Как в драйвере можно организовать функцию, в которую гарантированно буду попадать не реже ~10 ms? Какие есть стандартные средства для этого?

 

И ещё вопрос. Возможен ли такой вариант: в драйвере заряжаю DMA на определённую область памяти и уже из USER_SPACE получаю доступ к ней. А информацию по поводу по какому адресу делать mmap зачитаваю из файла устройства в /proc. Так же из файлов читаю текущее положение кольцевых указателей.

 

Заранее спасибо за ответы.

Господа, вам охота время терять, или просто хотите убедиться, что Линукс не дураками писан. V4L - есть там все это. И не надо циклических буферов . " в драйвере заряжаю DMA на определённую область памяти и уже из USER_SPACE получаю доступ к ней. " - это называется io_user_ptr , как-то так, большинство того, с чем я имел дело - с этим не работают. Чем вам mmap не по душе ? Накладных - ноль. ноль. Память выделять должен драйвер, имхо.

Память для DMA вызывается по dma_alloc_coherent, непрерывным цельным куском, но это функция ядра. Из пользовательского режима если подсовывать свою память - честно скажу не пробовал, мне и рабочие драйвера не попадались такие, обычно все проще, просишь драйвер создать буфер, просишь заполнить, просишь отдать. Достаточно прозрачно. А вот про user ptr обычно вижу только // TODO

Хотя возможно я вас не так понял, заряжать драйвер на память вы собрались именно внутри драйвера. Тогда зачем вам /proc вообще ? Короче как-то сложно выходит, через ioctl прекрасно работает. Я несколько сумбурно говорю, но и ваша идея тоже сумбурна. Да и непонятно зачем нужна. Рекомендованной статье 10 лет, потрасируйте через GDB традиционный вариант mmap - там на деле zero copy и выходит

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