--- description: 'Устройства PCI' next: books/arch-handbook/scsi params: path: /books/arch-handbook/pci/ prev: books/arch-handbook/isa showBookMenu: 'true' tags: ["PCI", "Devices", "example", "guide"] title: 'Глава 11. Устройства PCI' weight: 13 --- [[pci]] = Устройства PCI :doctype: book :toc: macro :toclevels: 1 :icons: font :sectnums: :sectnumlevels: 6 :sectnumoffset: 11 :partnums: :source-highlighter: rouge :experimental: :images-path: books/arch-handbook/ ifdef::env-beastie[] ifdef::backend-html5[] :imagesdir: ../../../../images/{images-path} endif::[] ifndef::book[] include::shared/authors.adoc[] include::shared/mirrors.adoc[] include::shared/releases.adoc[] include::shared/attributes/attributes-{{% lang %}}.adoc[] include::shared/{{% lang %}}/teams.adoc[] include::shared/{{% lang %}}/mailing-lists.adoc[] include::shared/{{% lang %}}/urls.adoc[] toc::[] endif::[] ifdef::backend-pdf,backend-epub3[] include::../../../../../shared/asciidoctor.adoc[] endif::[] endif::[] ifndef::env-beastie[] toc::[] include::../../../../../shared/asciidoctor.adoc[] endif::[] Эта глава расскажет о механизмах FreeBSD для написания драйвера устройства на шине PCI. [[pci-probe]] == Обнаружение и подключение Информация о том, как код шины PCI перебирает неприсоединённые устройства и проверяет, сможет ли только что загруженный kld присоединиться к любому из них. === Пример исходного кода драйвера ([.filename]#mypci.c#) [.programlisting] .... /* * Simple KLD to play with the PCI functions. * * Murray Stokely */ #include /* defines used in kernel.h */ #include #include #include #include /* types used in module initialization */ #include /* cdevsw struct */ #include /* uio struct */ #include #include /* structs, prototypes for pci bus stuff and DEVMETHOD macros! */ #include #include #include #include /* For pci_get macros! */ #include /* The softc holds our per-instance data. */ struct mypci_softc { device_t my_dev; struct cdev *my_cdev; }; /* Function prototypes */ static d_open_t mypci_open; static d_close_t mypci_close; static d_read_t mypci_read; static d_write_t mypci_write; /* Character device entry points */ 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", }; /* * In the cdevsw routines, we find our softc by using the si_drv1 member * of struct cdev. We set this variable to point to our softc in our * attach routine when we create the /dev entry. */ int mypci_open(struct cdev *dev, int oflags, int devtype, struct thread *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, struct thread *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 %zd 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 %zd bytes.\n", uio->uio_resid); return (0); } /* PCI Support Functions */ /* * Compare the device ID of this device against the IDs that this driver * supports. If there is a match, set the description and return success. */ 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); } /* Attach function is only called if the probe is successful. */ static int mypci_attach(device_t dev) { struct mypci_softc *sc; 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". */ 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 (0); } /* Detach device. */ 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); } /* Called during system shutdown after sync. */ static int mypci_shutdown(device_t dev) { printf("Mypci shutdown!\n"); return (0); } /* * Device suspend routine. */ static int mypci_suspend(device_t dev) { printf("Mypci suspend!\n"); return (0); } /* * Device resume routine. */ static int mypci_resume(device_t dev) { printf("Mypci resume!\n"); return (0); } static device_method_t mypci_methods[] = { /* Device interface */ 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), DEVMETHOD_END }; 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# для примера драйвера [.programlisting] .... # Makefile for mypci driver KMOD= mypci SRCS= mypci.c SRCS+= device_if.h bus_if.h pci_if.h .include .... Если вы поместите исходный файл выше и [.filename]#Makefile# в каталог, вы можете запустить `make` для компиляции примера драйвера. Дополнительно можно выполнить `make load` для загрузки драйвера в текущее ядро и `make unload` для выгрузки драйвера после его загрузки. === Дополнительные ресурсы * http://www.pcisig.org/[Группа по стандартам PCI] * PCI System Architecture, Fourth Edition by Tom Shanley, et al. [[pci-bus]] == Ресурсы шины FreeBSD предоставляет объектно-ориентированный механизм для запроса ресурсов от родительской шины. Почти все устройства будут дочерними элементами какого-либо типа шины (PCI, ISA, USB, SCSI и т.д.), и этим устройствам необходимо получать ресурсы от своей родительской шины (такие как сегменты памяти, линии прерываний или каналы DMA). === Регистры базовых адресов Для выполнения каких-либо полезных действий с устройством PCI необходимо получить _регистры базовых адресов_ (BAR) из конфигурационного пространства PCI. Специфичные для PCI детали получения BAR абстрагированы в функции `bus_alloc_resource()`. Например, типичный драйвер может содержать что-то подобное в функции `attach()`: [.programlisting] .... 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_*`. Например, драйвер может содержать сокращённую функцию для чтения из специфичного для платы регистра, как показано ниже: [.programlisting] .... uint16_t board_read(struct ni_softc *sc, uint16_t address) { return bus_space_read_2(sc->bar1_bt, sc->bar1_bh, address); } .... Аналогично, можно записать в регистры с помощью: [.programlisting] .... 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}` соответственно. [NOTE] ==== В FreeBSD 7.0 и более поздних версиях вы можете использовать функции `bus_*` вместо `bus_space_*`. Функции `bus_*` принимают указатель на структуру resource * вместо тега шины и дескриптора. Таким образом, вы можете удалить тег шины и дескриптор шины из `softc` и переписать функцию `board_read()` как: [.programlisting] .... uint16_t board_read(struct ni_softc *sc, uint16_t address) { return (bus_read(sc->bar1res, address)); } .... ==== === Прерывания Прерывания выделяются объектно-ориентированным кодом шины аналогично ресурсам памяти. Сначала ресурс IRQ должен быть выделен из родительской шины, а затем должен быть настроен обработчик прерывания для работы с этим IRQ. Вот пример из функции `attach()` устройства, который скажет больше, чем слова. [.programlisting] .... /* 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; } .... Некоторые меры предосторожности должны быть приняты в процедуре отключения драйвера. Необходимо остановить поток прерываний устройства и удалить обработчик прерываний. Как только `bus_teardown_intr()` завершится, можно быть уверенным, что обработчик прерываний больше не будет вызываться и все потоки, которые могли выполнять этот обработчик, завершили работу. Поскольку эта функция может засыпать, нельзя удерживать какие-либо мьютексы при её вызове. === DMA Этот раздел устарел и приведён только в исторических целях. Правильный способ решения этих проблем — использование функций `bus_space_dma*()`. Этот абзац можно удалить, когда раздел будет обновлён с учётом данного подхода. Однако на данный момент API находится в состоянии изменения, поэтому, когда он стабилизируется, будет полезно обновить этот раздел соответствующим образом. На ПК периферийные устройства, которые хотят использовать DMA с управлением шиной, должны работать с физическими адресами. Это проблема, поскольку FreeBSD использует виртуальную память и работает почти исключительно с виртуальными адресами. К счастью, существует функция `vtophys()`, которая поможет. [.programlisting] .... #include #include #define vtophys(virtual_address) (...) .... Однако решение немного отличается на alpha, и на самом деле нам нужна функция под названием `vtobus()`. [.programlisting] .... #if defined(__alpha__) #define vtobus(va) alpha_XXX_dmamap((vm_offset_t)va) #else #define vtobus(va) vtophys(va) #endif .... === Освобождение ресурсов Очень важно освободить все ресурсы, которые были выделены во время `attach()`. Необходимо внимательно следить за освобождением правильных ресурсов даже в случае ошибки, чтобы система оставалась работоспособной при завершении работы вашего драйвера.