9.2. Ресурсы шины

FreeBSD предоставляет объектно-ориентированный механизм для запроса ресурсов от родительской шины. Почти все устройства будут находиться в качестве ребёнка не важно какой шины (PCI, ISA, USB, SCSI и т.д) и нуждаться в приобретении ресурсов от родительской шины таких, как сегменты памяти, линии прерываний, DMA каналы.

9.2.1. Базовые адресные регистры

Чтобы сделать что-либо полезное с PCI устройством вам надо получить доступ к базовым адресным регистрам (Base Adress Registres (BARs) ) области конфигурации PCI. PCI специфичные детали получения BAR абстрагированы в функции bus_alloc_resource().

К примеру, типичный драйвер должен иметь что-то похожее в функции attach():

    sc->bar0id = PCI_BAR(0);
    sc->bar0res = bus_alloc_resource(dev, SYS_RES_MEMORY, &(sc->bar0id),
                                 0, ~0, 1, RF_ACTIVE);
    if (sc->bar0res == NULL) {
        printf("Memory allocation of PCI base register 0 failed!\n");
        error = ENXIO;
        goto fail1;
    }

    sc->bar1id = PCI_BAR(1);
    sc->bar1res = bus_alloc_resource(dev, SYS_RES_MEMORY, &(sc->bar1id),
                                 0, ~0, 1, RF_ACTIVE);
    if (sc->bar1res == NULL) {
        printf("Memory allocation of PCI base register 1 failed!\n");
        error =  ENXIO;
        goto fail2;
    }
    sc->bar0_bt = rman_get_bustag(sc->bar0res);
    sc->bar0_bh = rman_get_bushandle(sc->bar0res);
    sc->bar1_bt = rman_get_bustag(sc->bar1res);
    sc->bar1_bh = rman_get_bushandle(sc->bar1res);

Дескрипторы каждого базового регистра хранятся в структуре softc и в дальнейшем могут быть использованы для записи в устройство.

С помощью функций bus_space_* эти дескрипторы могут быть использованы для чтения или записи в регистры устройства. Например, в драйвере может быть реализована функция чтения данных из определенного регистра устройства:

uint16_t
board_read(struct ni_softc *sc, uint16_t address) {
    return bus_space_read_2(sc->bar1_bt, sc->bar1_bh, address);
}

Похожим способом делается запись в регистры:

void
board_write(struct ni_softc *sc, uint16_t address, uint16_t value) {
    bus_space_write_2(sc->bar1_bt, sc->bar1_bh, address, value);
}

Эти функции существуют в 8, 16, 32 битных версиях. Используйте bus_space_{read|write}_{1|2|4} соответственно.

9.2.2. Прерывания

Прерывания (IRQ) распределяются с помощью объектно-ориентированного кода шины способом, похожим на способ распределения ресурсов памяти. Сначала IRQ ресурс выделяется от родительской шины, затем настраивается обработчик прерываний для связи с этим IRQ.

Пример функции attach() скажет больше чем слова.

/* Get the IRQ resource */

    sc->irqid = 0x0;
    sc->irqres = bus_alloc_resource(dev, SYS_RES_IRQ, &(sc->irqid),
                                 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE);
    if (sc->irqres == NULL) {
       printf("IRQ allocation failed!\n");
       error = ENXIO;
       goto fail3;
    }

    /* Now we should set up the interrupt handler */

    error = bus_setup_intr(dev, sc->irqres, INTR_TYPE_MISC,
                          my_handler, sc, &(sc->handler));
    if (error) {
       printf("Couldn't set up irq\n");
       goto fail4;
    }

    sc->irq_bt = rman_get_bustag(sc->irqres);
    sc->irq_bh = rman_get_bushandle(sc->irqres);

Осторожно отсоединяйте программу драйвера. Вы должны успокоить поток прерываний устройства и удалить дескриптор прерывания. После выполнения функции bus_teardown_intr() ваш дескриптор прерываний не будет больше вызываться и все треды, которые возможно могли использовать этот дескриптор завершились. Так как эта функция может "засыпать", то вы не должны удерживать мьютексы при вызове этой функции.

9.2.3. DMA

Это глава является устаревшей и существует только по историческим причинам. Для работы с данной возможностью используйте семейство функций bus_space_dma*(). Этот параграф может будет убран при обновлении этой главы. На данный момент в API вносятся изменения, и когда все вопросы будут решены было бы неплохо обновить эту главу.

На ПК, периферийные устройства желающие использовать возможности DMA должны работать с физическими адресами, что является проблемой, так как FreeBSD использует виртуальную память и почти полностью имеет дело с виртуальными адресами. К счастью, существует функция vtophys() способная помочь.

#include <vm/vm.h>
#include <vm/pmap.h>

#define vtophys(virtual_address) (...)

Немного по другому решается данная проблема на платформе alpha. В этой ситуации хорошим решением будет использование макроопределения vtobus().

#if defined(__alpha__)
#define vtobus(va)      alpha_XXX_dmamap((vm_offset_t)va)
#else
#define vtobus(va)      vtophys(va)
#endif

9.2.4. Освобождение ресурсов

Очень важно освободить все реcурсы, выделенные с помощью attach(). Осторожно освобождайте корректные вещи даже на отказных условиях, чтобы система оставалась в рабочем состоянии, пока ваш драйвер не умрет.

Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

По вопросам, связанным с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам, связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам, связанным с русским переводом документации, пишите в рассылку <frdp@FreeBSD.org.ua>.
Информация по подписке на эту рассылку находится на сайте проекта перевода.