Библиотека сайта rus-linux.net
Драйверы устройств в Linux
Часть 7: Общие принципы доступа к аппаратным устройствам в Linux
Оригинал: "Device Drivers, Part 7: Generic Hardware Access in Linux"Автор: Anil Kumar Pugalia
Дата публикации: June 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.
В этой статье, которая является частью серии статей о драйверах устройств в Linux, речь пойдет о доступе к аппаратным устройствам в Linux.
Светлана, когда она вошла в лабораторию драйверов устройств для Linux, которая находилась на втором этаже ее колледжа, все еще была горда своими достижениями, связанными с символьными драйверам. Многие из ее одноклассников уже читали ее блог, и прокомментировали ее результаты. И сегодня был шанс показать себя на высоком уровне. До сих пор, все изучение касалось программного обеспечения, но сегодня лабораторная работа была связана с доступом в Linux к аппаратным устройствам.
Предполагается, что в лаборатории студенты должны в течение нескольких лабораторных занятий научиться "на практике" тому, как в Linux на различных архитектурах получать доступ к различным видам оборудования. Сотрудники лаборатории, как правило, хотят, чтобы студенты могли сразу без какого-либо практического навыка начинать работать с оборудованием, поэтому они для студентов подготовили несколько презентаций (доступны здесь).
Общий способ взаимодействия с аппаратным обеспечением
Как только все в лаборатории расселись, лаборант Прити начал с введения в аппаратные интерфейсы системы Linux. Если не вдаваться в теоретические детали, то на первом интересном слайде была представлена общая схема архитектурно-прозрачных аппаратных интерфейсов (смотрите рис.1).
Рис.1: Отображение аппаратных средств
Основным допущением является то, что рассматривается 32-битная архитектура. Для других вариантов
архитектуры отображение в память будет соответствующим образом изменено. Для 32-разрядной адресной
шины диапазоны отображения адресов / памяти будут от 0 (0x00000000
) и до "232 – 1" (0xFFFFFFFF
). Архитектурно-независимая схема использования отображения будет такой, как она показана на рис.1 — области, используемые под память (RAM — оперативная память) и используемые для устройств (регистры и память устройств), чередуются. В действительности, эти адреса зависят от используемой архитектуры. Например, в архитектуре x86, первые 3 Гб (с 0x00000000
до 0xBFFFFFFF
) , как правило, используются под память, а последние 1 Гб (с 0xC0000000
до 0xFFFFFFFF
) - для отображения устройств. Однако, если оперативной памяти меньше, скажем, 2 Гб, отображение устройств может начаться с 2 Гб (0x80000000
).
Запустите команду cat /proc/iomem
для того, чтобы получить
схему отображения памяти (карту памяти), используемую в вашей системе. Запустите команду cat /proc/meminfo
для того, чтобы получить приблизительный размер оперативной памяти, имеющийся на вашем компьютере. На рис.2 приведены скриншоты результатов работы этих команд.
Рис.2: Физические адреса памяти и адреса шин в системе x86
Независимо от фактических значений, адреса, которые отображаются в оперативную память, называются физическими адресами, а те адреса, которые используются для карт устройств, называются адресами шин, т. к. как эти устройства всегда отображаются через некоторую шину, зависящую от архитектуры, например, шину PCI - в архитектуре x86 , шину AMBA - в архитектуре ARM, шину SuperHyway - в архитектуре SuperH и т.д.
Все зависящие от архитектуры значения этих физических и шинных адресов либо конфигурируются динамически, либо берутся из спецификационных данных (например, руководств по аппаратному обеспечению) процессоров/ контроллеров соответствующей архитектуры. Самое интересное, что в Linux ни к какому из этих устройств нет непосредственного доступа, для этого используется отображение в виртуальные адреса, а затем через эти адреса происходит доступ к устройствам; в результате этого доступ к оперативной памяти и к устройствам становится почти одинаковым. Для подключения или отключения отображения адресов шины устройства в виртуальные адреса есть соответствующие API (прототип в <asm/io.h>
):
void *ioremap(unsigned long device_bus_address, unsigned long device_region_size); void iounmap(void *virt_addr);
Поскольку отображение осуществляется в виртуальные адреса, оно
зависит от спецификаций устройства, таких как набор регистров и/или памяти устройства, используемой
в операциях чтения и записи; поэтому к
виртуальному адресу, возвращаемому функцией ioremap()
, добавляется
соответствующее смещение. Для этого есть следующие API (прототип также
в <asm/io.h>
):
unsigned int ioread8(void *virt_addr); unsigned int ioread16(void *virt_addr); unsigned int ioread32(void *virt_addr); unsigned int iowrite8(u8 value, void *virt_addr); unsigned int iowrite16(u16 value, void *virt_addr); unsigned int iowrite32(u32 value, void *virt_addr);
Доступ к видеопамяти в стиле "DOS"
После получения этих первоначальных сведений студенты перешли к реальным экспериментам. Предлагалось, что для того, чтобы понять, как использовать приведенные выше API, первым экспериментом должен быть доступом к видеопамяти в стиле "DOS".
Светлана зашла в систему, перешла в директорий /proc/iomem
(как показано на рис.2)
и получила адреса видеопамяти с 0x000A0000
и до 0x000BFFFF
. Для того, чтобы
превратить свой уже существующий null-драйвер в драйвер "видеопамяти", она добавила в его конструктор и деструктор приведенные выше API с соответствующими параметрами. Затем она с помощью обращения к операциям чтения и записи добавила пользовательский доступ к "видеопамяти"; ниже приводится ее новый файл video_ram.c
:
#include <linux/module.h> #include <linux/version.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <asm/io.h> #define VRAM_BASE 0x000A0000 #define VRAM_SIZE 0x00020000 static void __iomem *vram; static dev_t first; static struct cdev c_dev; static struct class *cl; static int my_open(struct inode *i, struct file *f) { return 0; } static int my_close(struct inode *i, struct file *f) { return 0; } static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off) { int i; u8 byte; if (*off >= VRAM_SIZE) { return 0; } if (*off + len > VRAM_SIZE) { len = VRAM_SIZE - *off; } for (i = 0; i < len; i++) { byte = ioread8((u8 *)vram + *off + i); if (copy_to_user(buf + i, &byte, 1)) { return -EFAULT; } } *off += len; return len; } static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off) { int i; u8 byte; if (*off >= VRAM_SIZE) { return 0; } if (*off + len > VRAM_SIZE) { len = VRAM_SIZE - *off; } for (i = 0; i < len; i++) { if (copy_from_user(&byte, buf + i, 1)) { return -EFAULT; } iowrite8(byte, (u8 *)vram + *off + i); } *off += len; return len; } static struct file_operations vram_fops = { .owner = THIS_MODULE, .open = my_open, .release = my_close, .read = my_read, .write = my_write }; static int __init vram_init(void) /* Constructor */ { if ((vram = ioremap(VRAM_BASE, VRAM_SIZE)) == NULL) { printk(KERN_ERR "Mapping video RAM failed\n"); return -1; } if (alloc_chrdev_region(&first, 0, 1, "vram") < 0) { return -1; } if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL) { unregister_chrdev_region(first, 1); return -1; } if (device_create(cl, NULL, first, NULL, "vram") == NULL) { class_destroy(cl); unregister_chrdev_region(first, 1); return -1; } cdev_init(&c_dev, &vram_fops); if (cdev_add(&c_dev, first, 1) == -1) { device_destroy(cl, first); class_destroy(cl); unregister_chrdev_region(first, 1); return -1; } return 0; } static void __exit vram_exit(void) /* Destructor */ { cdev_del(&c_dev); device_destroy(cl, first); class_destroy(cl); unregister_chrdev_region(first, 1); iounmap(vram); } module_init(vram_init); module_exit(vram_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>"); MODULE_DESCRIPTION("Video RAM Driver");
Подведем итог
Затем Светлана повторила обычные действия:
- Собрала драйвер "видеопамяти" (файл
video_ram.ko
) с помощью запуска командыmake
с измененным файломMakefile
. - Загрузила драйвер с помощью команды
insmod video_ram.ko
. - Выполнила запись в
/dev/vram
с помощью командыecho -n "0123456789" > /dev/vram
- Выполнила чтение из
/dev/vram
с помощью командыod -t x1 -v /dev/vram | less
. (Также можно воспользоваться обычной командойcat /dev/vram
, но она выдаст все данные в двоичном формате. С помощью командыod -t x1
данные выдаются в шестнадцатеричном формате. Подробности узнайте, запустив командуman od
.) - Выгрузила драйвер с помощью команды
rmmod video_ram
.
До конца практических занятий осталось еще полчаса и Светлана решила пройтись по классу и, возможно, помочь кому-нибудь с экспериментами.
К предыдущей статье | Оглавление | К следующей статье |