|
свой драйвер АЦП, два пути |
|
|
|
 |
Ответов
(1 - 14)
|
Jun 13 2013, 21:50
|
Знающий
   
Группа: Участник
Сообщений: 881
Регистрация: 21-03-10
Из: _// \\_
Пользователь №: 56 107

|
С 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.
Сообщение отредактировал Hoodwin - Jun 14 2013, 06:58
|
|
|
|
|
Jun 18 2013, 03:57
|
Местный
  
Группа: Участник
Сообщений: 351
Регистрация: 5-04-05
Пользователь №: 3 874

|
Цитата(TigerSHARC @ Jun 13 2013, 15:29)  Вижу что устройства "по-хорошему" регистрируются в sysfs, а вот этот путь не совсем понятен. "По-хорошему" для АЦП скорее всего есть уже есть отдельная подсистема в которой надо зарегистироваться и реализовать методы и sysfs будет "из коробки". Здесь Dubov создавал несколько тем про драйвер АЦП.
|
|
|
|
|
Jun 28 2013, 13:13
|
Участник

Группа: Участник
Сообщений: 21
Регистрация: 18-05-10
Из: Ярославль
Пользователь №: 57 355

|
Как-то пользовался драйвером АЦП для at91-adc, для его 4-х каналов, для arm926-го (at91sam9m10g45ek). В этом драйвере есть и регистрация файлов в sysfs с чтением и записью для созданного misc-устройства, реализованы системные вызовы open/read/write/poll/close, используется dma. Выложил патч, добавляющий этот драйвер в ядро, на pastebin - может быть пригодится. Да, драйвер проверенный и рабочий. А так procfs - да, проще в реализации, можно работать с ней откуда угодно, не тянет за собой ничего ( например). Именно поэтому procfs в любой рабочей системе уже стала похожа на винегрет "из всего, что только в системе есть". Драйверы для spi-ных АЦП тоже имеются (например, для AD7887). Может быть даже готовый есть для нужного АЦП.
|
|
|
|
|
Jun 29 2013, 01:38
|
Частый гость
 
Группа: Свой
Сообщений: 111
Регистрация: 5-05-10
Из: Lebanon, Beirut
Пользователь №: 57 093

|
Цитата(Ya_Mike @ Jun 28 2013, 16:13)  Как-то пользовался драйвером АЦП для at91-adc, для его 4-х каналов, для arm926-го (at91sam9m10g45ek). В этом драйвере есть и регистрация файлов в sysfs с чтением и записью для созданного misc-устройства, реализованы системные вызовы open/read/write/poll/close, используется dma. Выложил патч, добавляющий этот драйвер в ядро, на pastebin - может быть пригодится. Да, драйвер проверенный и рабочий. А так procfs - да, проще в реализации, можно работать с ней откуда угодно, не тянет за собой ничего ( например). Именно поэтому procfs в любой рабочей системе уже стала похожа на винегрет "из всего, что только в системе есть". Драйверы для spi-ных АЦП тоже имеются (например, для AD7887). Может быть даже готовый есть для нужного АЦП. Мне кажется procfs/sysfs не лучшие интерфейсы для большого обьема данных или данных которые идут непрерывно и слишком быстро. Большой оверхед на открытие-закрытие и syscall вызовы. IMHO удобнее mmap с каким-нить ring buffer, которое удобно обрабатывать через readv.
|
|
|
|
|
Jul 4 2013, 13:24
|
Участник

Группа: Участник
Сообщений: 21
Регистрация: 18-05-10
Из: Ярославль
Пользователь №: 57 355

|
Цитата(denyslb @ Jun 29 2013, 05:38)  Мне кажется procfs/sysfs не лучшие интерфейсы для большого обьема данных или данных которые идут непрерывно и слишком быстро. Большой оверхед на открытие-закрытие и syscall вызовы. IMHO удобнее mmap с каким-нить ring buffer, которое удобно обрабатывать через readv. Открытие-закрытие можно делать только один раз - расходы на них сокращаются в ноль по сравнению с временем работы. В приведенном по ссылке драйвере - имеется двойная буферизация с поочередным дма-заполнением каждого, а скорость считывания такова, что системный вызов будет "срабатывать" раз в несколько десятков мс. Накладные расходы - нивелируются. Ко всему - с mmap, наверное, будет не очень удобно реализовывать блокирующее чтение, которое есть в этом драйвере. К тому же - при больших частотах дискретизации возможны потери данных, если читать малыми порциями через опрос регистров. Выигрыш mmap кажется сомнительным. В добавок, в драйвер можно удобно вставить предобработку данных с АЦП без пересборки приложения (например, скользящее среднее, медиана). Может я не совсем уловил высказанную идею и её преимущества. Можно описать подробней, какая связка mmap-а, ring-buffer-a и readv имеется ввиду? В целом, конечно, приличней работать через какое-нибудь символьное устройство в devfs, но не уверен, что по скорости работа через него будет отличаться, например, от sysfs (те же copy_{to,from}_user(..)).
|
|
|
|
|
Jul 12 2013, 10:47
|

Участник

Группа: Участник
Сообщений: 52
Регистрация: 30-11-11
Пользователь №: 68 593

|
Цитата(Hoodwin @ Jun 14 2013, 01:50)  Собственно и все. После 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. Так же из файлов читаю текущее положение кольцевых указателей. Заранее спасибо за ответы.
Сообщение отредактировал Муравей - Jul 12 2013, 11:27
|
|
|
|
|
Jul 26 2013, 09:22
|
Частый гость
 
Группа: Свой
Сообщений: 111
Регистрация: 5-05-10
Из: Lebanon, Beirut
Пользователь №: 57 093

|
Цитата(Ya_Mike @ Jul 4 2013, 16:24)  Открытие-закрытие можно делать только один раз - расходы на них сокращаются в ноль по сравнению с временем работы. В приведенном по ссылке драйвере - имеется двойная буферизация с поочередным дма-заполнением каждого, а скорость считывания такова, что системный вызов будет "срабатывать" раз в несколько десятков мс. Накладные расходы - нивелируются. Ко всему - с 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, но мне кажется накладных расходов больше (если судить по статье).
|
|
|
|
|
Aug 6 2013, 02:47
|
Гуру
     
Группа: Свой
Сообщений: 3 644
Регистрация: 28-05-05
Пользователь №: 5 493

|
Цитата(Муравей @ Jul 12 2013, 14:47)  Тоже задумался над написанием драйвера , который будет принимать поток с 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 и выходит
|
|
|
|
|
  |
2 чел. читают эту тему (гостей: 2, скрытых пользователей: 0)
Пользователей: 0
|
|
|