29. 5.28 Пользовательский I/O (UIO) в Linux
29.1. Введение
UIO (User-defined I/O) — это инфраструктура Linux, предназначенная для упрощения создания драйверов устройств в пространстве пользователя. Она предоставляет минимальную поддержку со стороны ядра, позволяя основную логику взаимодействия с оборудованием реализовать в user space.
UIO особенно полезен, когда: - Устройство не требует сложной логики в прерываниях - Достаточно прямого доступа к регистрам через память (mmap) - Требуется быстрая отладка, без пересборки ядра
—
29.2. Архитектура
UIO состоит из двух частей:
Ядровая часть: - Упрощённый драйвер (uio_pdrv_genirq, uio_platform) - Регистрирует устройство /dev/uioX - Предоставляет mmap-доступ к регистрам и интерфейс прерываний
Пользовательская часть: - Программа на 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 (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.