3 августа 2019 г.

Zynq UIO Linux драйвер, как его использовать с AXI интерфейсом

В этой статье речь пойдёт про UIO драйвер Linux, Userspace Input Output.

Ранее было рассмотрено несколько простых способов как работать с GPIO на плате Zybo с помощью Linux и было сказано несколько слов про работу с памятью ОС в целом.

Для работы с периферией нужно использовать специальный драйвер предназначенный конкретно для неё, либо обладать правами администратора, знать где в памяти ОС находится необходимые регистры и работать с ними напрямую через /dev/mem.

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

Создание своего драйвера требует довольно больших затрат и возможно оно нее оправдано, к примеру для простого доступа к светодиоду. Для его написания нужно взаимодействовать с исходниками ядра, что в конечном итоге может повредить всю ОС.
Хорошая книга по Linux драйверам: Linux Device Drivers,



Для упрощения работы, была создана UIO система. Для работы требуется минимальное взаимодействие с ядром и периферия настраивается через device tree.

Сперва нужно добавить новое устройство в device tree. Как его изменить было описано здесь.
Мы будем работать со светодиодом через AXI, по адресу 0x41200000. Он был подключен здесь.

В самый конец файла я добавил следующие строчки
&axi_led {
 compatible = "generic-uio";
 reg = < 0x41200000 0x1000 >;
};
axi_led - название устройства
compatible - строчка по которой драйвер будет искать данный модуль
reg - адрес в памяти откуда начинается необходимый нам блок, 0x1000 - размер блока который нам необходим.

Подробнее про device tree: xillybus.com.

В ядре Linux, произошли некоторые изменения и теперь драйвер по умолчанию не ищет устройства в device tree, подробнее об этом в коммите на гитхаб и на форуме. Рекомендуется добавить в device tree следующую строчку (или изменить существующую):
bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
Она должна быть в первом объявленном устройстве, там происходит описание системы.
Либо в ручную изменить исходники ядра. Убрав изменения. Для этого в файле drivers/uio/uio_pdrv_genirq.c. Нужно чтобы часть с поиском устройства была следующей:
#ifdef CONFIG_OF
static struct of_device_id uio_of_genirq_match[] = {
 { .compatible = "generic-uio", },
 { /* This is filled with module_parm */ },
 { /* Sentinel */ },
};
Я выбрал последний способ.

Перед компиляцией ядра нужно подключить UIO драйвер.
При настройке ядра и выполнении команды
make ARCH=arm menuconfig
следует перейти в
Device Drivers  --->
  <*> Userspace I/O drivers  --->
    <*>   Userspace I/O platform driver with generic IRQ handling
    <*>   Userspace platform driver with generic irq and dynamic memory
И подключить драйвер, поставив "*".

Рис.1 Настройка ядра Linux
Затем всё нужно скомпилировать и проверить. Должен появиться файл uio0 в папке /dev/ и папка uio0 в sys/class/uio/. Если добавлять больше устройств, то их индекс будет увеличиваться uio1, uio2 и т.д.

Рис.2 Проверка подключения нового устройства
Как видно появилась область памяти /dev/uio0 к которой могут обращаться обычные пользователи (но они должны быть в специальной группе).
Для проверки можно воспользоваться немного изменённым кодом из предыдущей статьи.
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <errno.h> 
#include <signal.h> 
#include <fcntl.h> 
#include <ctype.h> 
#include <termios.h> 
#include <sys/types.h> 
#include <sys/mman.h> 

#define MAP_SIZE 0x1000 
#define LED_BIT 1

int main(int argc, char **argv) { 
  int fd; 
  void *map_base;
  unsigned char *led_val;
  off_t offset; 
 
   if ((fd = open ("/dev/uio0", O_RDWR | O_SYNC)) == -1) {
     
    printf ("/dev/uio0 not opened.\n");
    return -1;
  }

   map_base = mmap (0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
   if (map_base == (void *) -1) {
    close(fd);
    printf ("/dev/uio0 mapping failed.\n");
  }
  led_val = map_base;
  while(1)
  {
    printf ("LED is %s \n", (*led_val & 1) ? "On": "Off");
    fflush (stdout); 
    sleep(1);
    printf ("Changing LED Status\n");
    fflush (stdout); 
    *led_val ^= LED_BIT;
    sleep(1);
    
  }

  if (munmap(map_base, MAP_SIZE) == -1) {
    printf("unmap failed\n");
  }
  close (fd); 
  return 0; 
}


Основные источники:
www.kernel.org
fpga.org
svenand.blogdrives.com
r4nd0m6uy.ch
yurovsky.github.io
xillybus.com
forums.xilinx.com
github.com
stackoverflow.com
www.equestionanswers.com

Комментариев нет :

Отправить комментарий