29. 5.28 Пользовательский I/O (UIO) в Linux

29.1. Введение

UIO (User-defined I/O) — это инфраструктура Linux, предназначенная для упрощения создания драйверов устройств в пространстве пользователя. Она предоставляет минимальную поддержку со стороны ядра, позволяя основную логику взаимодействия с оборудованием реализовать в user space.

UIO особенно полезен, когда: - Устройство не требует сложной логики в прерываниях - Достаточно прямого доступа к регистрам через память (mmap) - Требуется быстрая отладка, без пересборки ядра

—

29.2. Архитектура

UIO состоит из двух частей:

  1. Ядровая часть: - Упрощённый драйвер (uio_pdrv_genirq, uio_platform) - Регистрирует устройство /dev/uioX - Предоставляет mmap-доступ к регистрам и интерфейс прерываний

  2. Пользовательская часть: - Программа на C, Python или любом другом языке - Открывает /dev/uioX, мапит память, обрабатывает события

—

29.2.1. Преимущества и ограничения

Преимущества:

  • Простой доступ к I/O-регистрам через mmap

  • Отладка средствами user space (gdb, valgrind, printf)

  • Нет необходимости писать полноценный драйвер ядра

  • Возможность реализации на любом языке (не только C)

  • Позволяет обрабатывать простые прерывания через read()

Ограничения:

  • Нет доступа к подсистемам ядра (I2C, SPI, DRM и др.)

  • Обработка прерываний ограничена (только уведомление)

  • Ответственность за безопасность и синхронизацию — на приложении

—

29.3. Регистрация устройства

Пример через Device Tree:

mydevice@0x10000000 {
    compatible = "generic-uio";
    reg = <0x10000000 0x1000>;
    interrupt-parent = <&gpio>;
    interrupts = <42 0>;
};

Пример в коде ядра:

struct resource res[] = {
    {
        .start = 0x10000000,
        .end   = 0x10000FFF,
        .flags = IORESOURCE_MEM,
    },
};

platform_device_register_simple("uio_pdrv_genirq", -1, res, 1);

—

29.4. Работа с UIO из пространства пользователя

Пример на C:

int fd = open("/dev/uio0", O_RDWR);
void *map = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

// Чтение/запись регистра
uint32_t val = *((volatile uint32_t *)map + 0);
*((volatile uint32_t *)map + 0) = 0x01;

// Ожидание прерывания
uint32_t irqcount;
read(fd, &irqcount, sizeof(irqcount));

—

29.5. Интерфейсы

После загрузки UIO-драйвера создаются:

Устройство:

/dev/uio0

Sysfs:

/sys/class/uio/uio0/
├── maps/map0/addr
├── maps/map0/size
├── name
└── version

—

29.6. Когда использовать UIO

UIO — хороший выбор, если:

  • Устройство предоставляет прямой доступ к регистрам

  • Требуется быстрая реализация и отладка

  • Прерывания можно обрабатывать просто (уведомление, не логика)

  • Нет необходимости в глубокой интеграции с подсистемами ядра

—

29.7. Сравнение UIO и Platform Driver

Сравнение UIO и Platform Driver

Характеристика

UIO (User-space I/O)

Platform Driver (в пространстве ядра)

Простота разработки

Очень высокая (пишется как обычное приложение)

Средняя/низкая (требует знания API ядра)

Прямой доступ к регистрам

Да, через mmap

Да, через ioremap

Обработка прерываний

Только уведомление через read()

Полная логика внутри IRQ handler

Безопасность и синхронизация

На ответственности приложения

Контролируется ядром

Поддержка подсистем

Нет

Полная (I2C, SPI, GPIO, LED, DMA, и др.)

Отладка

Простая (gdb, valgrind, printf)

Сложнее (printk, dmesg, kprobe)

Языки разработки

Любые (C, Python, Rust и др.)

Только C (ядровый стиль)

Применение

Прототипирование, FPGA, простые устройства

Интеграция с системными шинами, сложные драйверы

—

29.8. Заключение

UIO позволяет реализовать драйверы для простых устройств в user space, существенно упрощая разработку, отладку и поддержку. Это особенно полезно при работе с самодельным оборудованием, отладочными платами, FPGA, а также в образовательных и экспериментальных целях.

Если вам не требуется использовать возможности подсистем ядра, UIO — это простой и надёжный путь к созданию пользовательских драйверов в Linux.