Наши партнеры

UnixForum





Библиотека сайта 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");

Подведем итог

Затем Светлана повторила обычные действия:

  1. Собрала драйвер "видеопамяти" (файл video_ram.ko) с помощью запуска команды make с измененным файлом Makefile.
  2. Загрузила драйвер с помощью команды insmod video_ram.ko.
  3. Выполнила запись в /dev/vram с помощью команды echo -n "0123456789" > /dev/vram
  4. Выполнила чтение из /dev/vram с помощью команды od -t x1 -v /dev/vram | less. (Также можно воспользоваться обычной командой cat /dev/vram, но она выдаст все данные в двоичном формате. С помощью команды od -t x1 данные выдаются в шестнадцатеричном формате. Подробности узнайте, запустив команду man od.)
  5. Выгрузила драйвер с помощью команды rmmod video_ram.

До конца практических занятий осталось еще полчаса и Светлана решила пройтись по классу и, возможно, помочь кому-нибудь с экспериментами.


К предыдущей статье Оглавление К следующей статье