FreeBSD 系统结构手册
上一页 | 第11章 　PCI设备 | 下一页

# 11.2 总线资源

FreeBSD为从父总线请求资源提供了一种面向对象的机制。几乎所有设备 都是某种类型的总线（PCI, ISA, USB, SCSI等等）的孩子成员，并且这些设备 需要从他们的父总线获取资源（例如内存段, 中断线, 或者DMA通道）。

## 11.2.1 基地址寄存器

为了对PCI设备做些有用的事情，你需要从PCI配置空间获取 Base Address Registers (BARs)。获取BAR时的 PCI特定的细节被抽象在函数`bus_alloc_resource()`中。

例如，一个典型的驱动程序可能在`attach()` 函数中有些类似下面的东西：

        sc->bar0id = PCIR_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 = PCIR_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}`。

> 注意: 在 FreeBSD 7.0 和更高版本中， 可以用 `bus_*` 函数来代替 `bus_space_*`。 `bus_*` 函数使用的参数是 struct resource * 指针， 而不是 bus tag 和句柄。 这样， 您就可以将 `softc` 中的 bus tag 和 bus 句柄这两个成员变量去掉， 并将 `board_read()` 函数改写为：

>     uint16_t
>     board_read(struct ni_softc *sc, uint16_t address)
>     {
>     	return (bus_read(sc->bar1res, address));
>     }

## 11.2.2 中断

中断按照和分配内存资源相似的方式从面向对象的总线代码分配。首先， 必须从父总线分配IRQ资源，然后必须设置中断处理函数来处理这个IRQ。

再一次，来自设备`attach()`函数的例子比文字 更具说明性。

    /* 取得IRQ资源 */

        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;
        }

        /* 现在我们应当设置中断处理函数 */

        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;
        }

在设备的分离例程中必须注意一些问题。你必须停顿设备的中断流， 并移除中断处理函数。一旦`bus_teardown_intr()` 返回，你知道你的中断处理函数不会再被调用，并且所有可能已经执行了 这个中断处理函数的线程都已经返回。由于此函数可以睡眠，调用此函数时 你必须不能拥有任何互斥体。

## 11.2.3 DMA

本节已废弃，只是由于历史原因而给出。处理这些问题的适当方法是 使用`bus_space_dma*()`函数。当更新这一节以反映 那样用法时，这段就可能被去掉。然而，目前API还不断有些变动，因此一旦 它们固定下来后，更新这一节来反映那些改动就很好了。

在PC上，想进行总线主控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

## 11.2.4 取消分配资源

取消`attach()`期间分配的所有资源非常重要。 必须小心谨慎，即使在失败的条件下也要保证取消分配那些正确的东西， 这样当你的驱动程序去掉后系统仍然可以使用。

上一页 | 起始页 | 下一页
PCI设备 | 上一级 | 通用访问方法SCSI控制器

本文档和其它文档可从这里下载：ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题，请先阅读文档，如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.