diff --git a/zh_CN.GB2312/books/arch-handbook/pci/chapter.sgml b/zh_CN.GB2312/books/arch-handbook/pci/chapter.sgml index 39cec8ed16..d4924aa666 100644 --- a/zh_CN.GB2312/books/arch-handbook/pci/chapter.sgml +++ b/zh_CN.GB2312/books/arch-handbook/pci/chapter.sgml @@ -1,424 +1,441 @@ &author.cn.spellar; &cnproj.translated.by; PCI设备 PCI总线 本章将讨论FreeBSD为了给PCI总线上的设备编写驱动程序而提供的机制。 探测与连接 这儿的信息是关于PCI总线代码如何迭代通过未连接的设备,并查看新 加载的kld是否会连接其中一个。 示例驱动程序源代码(<filename>mypci.c</filename>) /* * 与PCI函数进行交互的简单KLD * * Murray Stokely */ #include <sys/param.h> /* kernel.h中使用的定义 */ #include <sys/module.h> #include <sys/systm.h> #include <sys/errno.h> #include <sys/kernel.h> /* 模块初始化中使用的类型 */ #include <sys/conf.h> /* cdevsw结构 */ #include <sys/uio.h> /* uio结构 */ #include <sys/malloc.h> #include <sys/bus.h> /* pci总线用到的结构、原型 */ #include <machine/bus.h> #include <sys/rman.h> #include <machine/resource.h> #include <dev/pci/pcivar.h> /* 为了使用get_pci宏! */ #include <dev/pci/pcireg.h> /* softc保存我们每个实例的数据。 */ struct mypci_softc { device_t my_dev; struct cdev *my_cdev; }; /* 函数原型 */ static d_open_t mypci_open; static d_close_t mypci_close; static d_read_t mypci_read; static d_write_t mypci_write; /* 字符设备入口点 */ static struct cdevsw mypci_cdevsw = { .d_version = D_VERSION, .d_open = mypci_open, .d_close = mypci_close, .d_read = mypci_read, .d_write = mypci_write, .d_name = "mypci", }; /* * 在cdevsw例程中,我们通过结构体cdev中的成员si_drv1找出我们的softc。 * 当我们建立/dev项时,在我们的已附着的例程中, * 我们设置这个变量指向我们的softc。 */ int mypci_open(struct cdev *dev, int oflags, int devtype, d_thread_t *td) { struct mypci_softc *sc; /* Look up our softc. */ sc = dev->si_drv1; device_printf(sc->my_dev, "Opened successfully.\n"); return (0); } int mypci_close(struct cdev *dev, int fflag, int devtype, d_thread_t *td) { struct mypci_softc *sc; /* Look up our softc. */ sc = dev->si_drv1; device_printf(sc->my_dev, "Closed.\n"); return (0); } int mypci_read(struct cdev *dev, struct uio *uio, int ioflag) { struct mypci_softc *sc; /* Look up our softc. */ sc = dev->si_drv1; device_printf(sc->my_dev, "Asked to read %d bytes.\n", uio->uio_resid); return (0); } int mypci_write(struct cdev *dev, struct uio *uio, int ioflag) { struct mypci_softc *sc; /* Look up our softc. */ sc = dev->si_drv1; device_printf(sc->my_dev, "Asked to write %d bytes.\n", uio->uio_resid); - return (err); + return (0); } /* PCI支持函数 */ /* * 将某个设置的标识与这个驱动程序支持的标识相比较。 * 如果相符,设置描述字符并返回成功。 */ static int mypci_probe(device_t dev) { device_printf(dev, "MyPCI Probe\nVendor ID : 0x%x\nDevice ID : 0x%x\n", pci_get_vendor(dev), pci_get_device(dev)); if (pci_get_vendor(dev) == 0x11c1) { printf("We've got the Winmodem, probe successful!\n"); device_set_desc(dev, "WinModem"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } /* 只有当探测成功时才调用连接函数 */ static int mypci_attach(device_t dev) { struct mypci_softc *sc; - printf("MyPCI Attach for : deviceID : 0x%x\n",pci_get_vendor(dev)); + printf("MyPCI Attach for : deviceID : 0x%x\n", pci_get_devid(dev)); /* Look up our softc and initialize its fields. */ sc = device_get_softc(dev); sc->my_dev = dev; /* * Create a /dev entry for this device. The kernel will assign us * a major number automatically. We use the unit number of this * device as the minor number and name the character device * "mypci<unit>". */ sc->my_cdev = make_dev(&mypci_cdevsw, device_get_unit(dev), UID_ROOT, GID_WHEEL, 0600, "mypci%u", device_get_unit(dev)); + sc->my_cdev->si_drv1 = sc; printf("Mypci device loaded.\n"); - return (ENXIO); + return (0); } /* 分离设备。 */ static int mypci_detach(device_t dev) { struct mypci_softc *sc; /* Teardown the state in our softc created in our attach routine. */ sc = device_get_softc(dev); destroy_dev(sc->my_cdev); printf("Mypci detach!\n"); return (0); } /* 系统关闭期间在sync之后调用。 */ static int mypci_shutdown(device_t dev) { printf("Mypci shutdown!\n"); return (0); } /* * 设备挂起例程。 */ static int mypci_suspend(device_t dev) { printf("Mypci suspend!\n"); return (0); } /* * 设备恢复(重新开始)例程。 */ static int mypci_resume(device_t dev) { printf("Mypci resume!\n"); return (0); } static device_method_t mypci_methods[] = { /* 设备接口 */ DEVMETHOD(device_probe, mypci_probe), DEVMETHOD(device_attach, mypci_attach), DEVMETHOD(device_detach, mypci_detach), DEVMETHOD(device_shutdown, mypci_shutdown), DEVMETHOD(device_suspend, mypci_suspend), DEVMETHOD(device_resume, mypci_resume), { 0, 0 } }; static devclass_t mypci_devclass; DEFINE_CLASS_0(mypci, mypci_driver, mypci_methods, sizeof(struct mypci_softc)); DRIVER_MODULE(mypci, pci, mypci_driver, mypci_devclass, 0, 0); 示例驱动程序的<filename>Makefile</filename> # 驱动程序mypci的Makefile KMOD= mypci SRCS= mypci.c SRCS+= device_if.h bus_if.h pci_if.h .include <bsd.kmod.mk> 如果你将上面的源文件和 Makefile放入一个目录,你可以运行 make编译示例驱动程序。 还有,你可以运行make load 将驱动程序装载到当前正在运行的内核中,而make unload可在装载后卸载驱动程序。 更多资源 PCI Special Interest Group PCI System Architecture, Fourth Edition by Tom Shanley, et al. 总线资源 PCI总线resources(资源) FreeBSD为从父总线请求资源提供了一种面向对象的机制。几乎所有设备 都是某种类型的总线(PCI, ISA, USB, SCSI等等)的孩子成员,并且这些设备 需要从他们的父总线获取资源(例如内存段, 中断线, 或者DMA通道)。 基地址寄存器 PCI总线Base Address Registers(基地址寄存器) 为了对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), + 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), + 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) { +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) { +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)); +} + + 中断 PCI总线interrupts(中断) 中断按照和分配内存资源相似的方式从面向对象的总线代码分配。首先, 必须从父总线分配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; } - - sc->irq_bt = rman_get_bustag(sc->irqres); - sc->irq_bh = rman_get_bushandle(sc->irqres); 在设备的分离例程中必须注意一些问题。你必须停顿设备的中断流, 并移除中断处理函数。一旦bus_teardown_intr() 返回,你知道你的中断处理函数不会再被调用,并且所有可能已经执行了 这个中断处理函数的线程都已经返回。由于此函数可以睡眠,调用此函数时 你必须不能拥有任何互斥体。 DMA PCI总线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 取消分配资源 取消attach()期间分配的所有资源非常重要。 必须小心谨慎,即使在失败的条件下也要保证取消分配那些正确的东西, 这样当你的驱动程序去掉后系统仍然可以使用。