/**************************************************************************** * drivers/pci/pci.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define ALIGN(x, m) (((x) + ((m) - 1)) & ~((uintptr_t)(m) - 1)) /* Wrappers for all PCI configuration access functions. They just check * alignment, do locking and call the low-level functions pointed to * by ctrl->ops. */ #define PCI_byte_BAD 0 #define PCI_word_BAD ((where) & 1) #define PCI_dword_BAD ((where) & 3) #define PCI_BUS_READ_CONFIG(len, type, size) \ int pci_bus_read_config_##len(FAR struct pci_bus_s *bus, unsigned int devfn, \ int where, FAR type *value) \ { \ int ret = -EINVAL; \ uint32_t data = 0; \ \ if (!PCI_##len##_BAD) \ { \ ret = pci_bus_read_config(bus, devfn, where, size, &data); \ } \ \ *value = (type)data; \ return ret; \ } #define PCI_BUS_WRITE_CONFIG(len, type, size) \ int pci_bus_write_config_##len(FAR struct pci_bus_s *bus, unsigned int devfn, \ int where, type value) \ { \ int ret = -EINVAL; \ \ if (!PCI_##len##_BAD) \ { \ ret = pci_bus_write_config(bus, devfn, where, size, value); \ } \ \ return ret; \ } #define PCI_BUS_READ_IO(len, type, size) \ int pci_bus_read_io_##len(FAR struct pci_bus_s *bus, uintptr_t where, \ FAR type *value) \ { \ int ret = -EINVAL; \ uint32_t data = 0; \ \ if (!PCI_##len##_BAD) \ { \ ret = pci_bus_read_io(bus, where, size, &data); \ } \ \ *value = (type)data; \ return ret; \ } #define PCI_BUS_WRITE_IO(len, type, size) \ int pci_bus_write_io_##len(FAR struct pci_bus_s *bus, uintptr_t where, \ type value) \ { \ int ret = -EINVAL; \ \ if (!PCI_##len##_BAD) \ { \ ret = pci_bus_write_io(bus, where, size, value); \ } \ \ return ret; \ } #define pci_match_one_device(id, dev) \ (((id)->vendor == PCI_ANY_ID || (id)->vendor == (dev)->vendor) && \ ((id)->device == PCI_ANY_ID || (id)->device == (dev)->device) && \ ((id)->subvendor == PCI_ANY_ID || \ (id)->subvendor == (dev)->subsystem_vendor) && \ ((id)->subdevice == PCI_ANY_ID || \ (id)->subdevice == (dev)->subsystem_device) && \ ((((id)->class ^ (dev)->class) & (id)->class_mask) == 0)) /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int pci_ioctl(FAR struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static mutex_t g_pci_lock = NXMUTEX_INITIALIZER; static struct list_node g_pci_device_list = LIST_INITIAL_VALUE(g_pci_device_list); static struct list_node g_pci_driver_list = LIST_INITIAL_VALUE(g_pci_driver_list); static struct list_node g_pci_ctrl_list = LIST_INITIAL_VALUE(g_pci_ctrl_list); static const struct file_operations g_pci_fops = { NULL, /* open */ NULL, /* close */ NULL, /* read */ NULL, /* write */ NULL, /* seek */ pci_ioctl, /* ioctl */ NULL, /* mmap */ }; /**************************************************************************** * Private Functions ****************************************************************************/ static FAR struct pci_device_s * pci_do_find_device_from_bus(FAR struct pci_bus_s *bus, uint8_t busno, unsigned int devfn) { FAR struct pci_bus_s *bus_tmp; FAR struct pci_device_s *dev; list_for_every_entry(&bus->devices, dev, struct pci_device_s, node) { if (dev->bus->number == busno && devfn == dev->devfn) { return dev; } } list_for_every_entry(&bus->children, bus_tmp, struct pci_bus_s, node) { dev = pci_find_device_from_bus(bus_tmp, busno, devfn); if (dev != NULL) { return dev; } } return NULL; } static int pci_vpd_read(FAR struct pci_bus_s *bus, uint32_t devfn, int offset, int count, FAR uint32_t *data) { uint8_t pos; int i; if (offset + count >= PCI_VPD_ADDR_MASK || data != NULL) { return -EINVAL; } pos = pci_bus_find_capability(bus, devfn, PCI_CAP_ID_VPD); if (pos == 0) { return -ENOENT; } for (i = 0; i < count; i++, offset += 4) { int j = 0; pci_bus_write_config_word(bus, devfn, pos + PCI_VPD_ADDR, offset); /** * PCI 2.2 does not specify how long we should poll * for completion nor whether the operation can fail. */ for (; ; ) { uint16_t addr; pci_bus_read_config_word(bus, devfn, pos + PCI_VPD_ADDR, &addr); if (addr & PCI_VPD_ADDR_F) { break; } if (++j == 20) { return -EIO; } up_udelay(4); } pci_bus_read_config_dword(bus, devfn, pos + PCI_VPD_DATA, &data[i]); data[i] = le32toh(data[i]); } return 0; } /**************************************************************************** * Name: pci_ioctl * * Description: * for lspci read/write pci config space * * Input Parameters: * filep - The open file description * cmd - The cmd to read/write cmd * arg - The arg to pass ioctl * Returned Value: * The length of the param * ****************************************************************************/ static int pci_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct pci_controller_s *ctrl; FAR struct pcisel *sel; uint32_t devfn; uint8_t i = 0; int ret; sel = (FAR struct pcisel *)arg; devfn = PCI_DEVFN(sel->pc_dev, sel->pc_func); ret = nxmutex_lock(&g_pci_lock); if (ret < 0) { return ret; } list_for_every_entry(&g_pci_ctrl_list, ctrl, struct pci_controller_s, node) { if (i == sel->pc_domain) { break; } i++; } nxmutex_unlock(&g_pci_lock); if (i != sel->pc_domain) { return -ENODEV; } switch (cmd) { case PCIOCREAD: { FAR struct pci_io *io = (FAR struct pci_io *)arg; ret = pci_bus_read_config(ctrl->bus, devfn, io->pi_reg, io->pi_width, &io->pi_data); break; } case PCIOCWRITE: { FAR struct pci_io *io = (FAR struct pci_io *)arg; ret = pci_bus_write_config(ctrl->bus, devfn, io->pi_reg, io->pi_width, io->pi_data); break; } case PCIOCGETROMLEN: { FAR struct pci_rom *rom = (FAR struct pci_rom *)arg; FAR struct pci_device_s *dev = pci_find_device_from_bus(ctrl->bus, sel->pc_bus, devfn); if (dev == NULL) { return -ENODEV; } rom->pr_romlen = pci_resource_len(dev, PCI_ROM_RESOURCE); ret = 0; break; } case PCIOCGETROM: { FAR void *p; uint32_t addr; uint32_t len; FAR struct pci_rom *rom = (FAR struct pci_rom *)arg; FAR struct pci_device_s *dev = pci_find_device_from_bus(ctrl->bus, sel->pc_bus, devfn); if (dev == NULL) { return -ENODEV; } addr = pci_resource_start(dev, PCI_ROM_RESOURCE); len = pci_resource_len(dev, PCI_ROM_RESOURCE); if (rom->pr_romlen < len) { rom->pr_romlen = len; ret = -E2BIG; break; } p = pci_map_bar(dev, PCI_ROM_RESOURCE); if (p == NULL) { ret = -ENOENT; break; } pci_bus_write_config_dword(ctrl->bus, devfn, PCI_ROM_ADDRESS, addr | PCI_ROM_ADDRESS_ENABLE); memcpy(rom->pr_rom, p, len); pci_bus_write_config_dword(ctrl->bus, devfn, PCI_ROM_ADDRESS, addr); break; } case PCIOCREADMASK: { uint32_t data; FAR struct pci_io *io = (FAR struct pci_io *)arg; if (io->pi_width != 4 || (io->pi_reg & 0x3) || io->pi_reg < PCI_BASE_ADDRESS_0 || io->pi_reg >= PCI_BASE_ADDRESS_SPACE) { ret = -EINVAL; break; } pci_bus_read_config_dword(ctrl->bus, devfn, io->pi_reg, &data); pci_bus_write_config_dword(ctrl->bus, devfn, io->pi_reg, 0xffffffff); pci_bus_read_config_dword(ctrl->bus, devfn, io->pi_reg, &io->pi_data); pci_bus_write_config_dword(ctrl->bus, devfn, io->pi_reg, data); break; } case PCIOCGETVPD: { FAR struct pci_vpd_req *pv = (FAR struct pci_vpd_req *)arg; ret = pci_vpd_read(ctrl->bus, devfn, pv->pv_offset, pv->pv_count, pv->pv_data); break; } default: { ret = -ENOTTY; break; } } return ret; } /**************************************************************************** * Name: pci_change_master * * Description: * Enables/Disbale bus-mastering for device dev * * Input Parameters: * dev - The PCI device to cchange * enable - True to enable, False to disable * ****************************************************************************/ static void pci_change_master(FAR struct pci_device_s *dev, bool enable) { uint16_t old_cmd; uint16_t cmd; pci_read_config_word(dev, PCI_COMMAND, &old_cmd); if (enable) { cmd = old_cmd | PCI_COMMAND_MASTER; } else { cmd = old_cmd & ~PCI_COMMAND_MASTER; } if (cmd != old_cmd) { pci_write_config_word(dev, PCI_COMMAND, cmd); } } /**************************************************************************** * Name: pci_bus_find_start_cap * * Description: * Find the offset of first capability list entry. * * Input Parameters: * bus - The bus this dev exist on * devfn - BDF * hdr_type - Cfg space head type ID * * Returned Value: * Return the ID of first capability list entry * ****************************************************************************/ static uint8_t pci_bus_find_start_cap(FAR struct pci_bus_s *bus, unsigned int devfn, uint8_t hdr_type) { uint16_t status; pci_bus_read_config_word(bus, devfn, PCI_STATUS, &status); if (!(status & PCI_STATUS_CAP_LIST)) { return 0; } /* Ignore MF bit */ switch (hdr_type & 0x7f) { case PCI_HEADER_TYPE_NORMAL: case PCI_HEADER_TYPE_BRIDGE: return PCI_CAPABILITY_LIST; case PCI_HEADER_TYPE_CARDBUS: return PCI_CB_CAPABILITY_LIST; default: return 0; } } /**************************************************************************** * Name: pci_find_next_cap_ttl * * Description: * * Input Parameters: * dev - The PCI device to find capbilities * devfn - BDF * pos - List node * cap - Value of capabilities * ttl - The max depth to find * ****************************************************************************/ static uint8_t pci_find_next_cap_ttl(FAR struct pci_bus_s *bus, unsigned int devfn, uint8_t pos, int cap, FAR int *ttl) { uint16_t ent; uint8_t id; pci_bus_read_config_byte(bus, devfn, pos, &pos); while ((*ttl)--) { if (pos < 0x40) { break; } pos &= ~3; pci_bus_read_config_word(bus, devfn, pos, &ent); id = ent & 0xff; if (id == 0xff) { break; } if (id == cap) { return pos; } pos = ent >> 8; } return 0; } /**************************************************************************** * Name: pci_find_next_cap * * Description: * To find the next capability. * * Input Parameters: * dev - The PCI device to find capbilities * devfn - BDF * pos - List node * cap - Value of capabilities * * Returned Value: * Return the capability data * ****************************************************************************/ static uint8_t pci_find_next_cap(FAR struct pci_bus_s *bus, unsigned int devfn, uint8_t pos, int cap) { int ttl = 48; return pci_find_next_cap_ttl(bus, devfn, pos, cap, &ttl); } /**************************************************************************** * Name: pci_alloc_bus * * Description: * Alloc a memory for a bus and init list node. * * Returned Value: * Return the memory address alloced * ****************************************************************************/ static FAR struct pci_bus_s *pci_alloc_bus(void) { FAR struct pci_bus_s *bus; bus = kmm_zalloc(sizeof(*bus)); list_initialize(&bus->node); list_initialize(&bus->children); list_initialize(&bus->devices); return bus; } /**************************************************************************** * Name: pci_alloc_device * * Description: * Alloc a memory for device be scanned and init the list node. * * Returned Value: * Return the device address alloced * ****************************************************************************/ static FAR struct pci_device_s *pci_alloc_device(void) { FAR struct pci_device_s *dev; dev = kmm_zalloc(sizeof(*dev)); list_initialize(&dev->node); list_initialize(&dev->bus_list); return dev; } /**************************************************************************** * Name: pci_register_bus_devices * * Description: * Register all devices scanned and all buses scanned to responsing list. * * Input Parameters: * bus - The boot bus * ****************************************************************************/ static void pci_register_bus_devices(FAR struct pci_bus_s *bus) { FAR struct pci_device_s *dev; FAR struct pci_bus_s *child_bus; /* Activate all devices on this bus */ list_for_every_entry(&bus->devices, dev, struct pci_device_s, bus_list) { pci_register_device(dev); } /* Walk down the hierarchy */ list_for_every_entry(&bus->children, child_bus, struct pci_bus_s, node) { pci_register_bus_devices(child_bus); } } /**************************************************************************** * Name: pci_size * * Description: * To calculate the pci dev address size. * * Input Parameters: * base - PCI address base address * maxbase - PCI max base address * mask - PCI addres mask * * Returned Value: * Return the size result * ****************************************************************************/ static uint32_t pci_size(uint32_t base, uint32_t maxbase, uint32_t mask) { uint32_t size = maxbase & mask; if (size == 0) { return 0; } size = (size & ~(size - 1)) - 1; if (base == maxbase && ((base | size) & mask) != mask) { return 0; } return size + 1; } /**************************************************************************** * Name: pci_setup_device * * Description: * Search every bar in the device be found, mapping memory if MEM or * prefetchable MEM, and add this dev to the device list. * * Input Parameters: * dev - The PCI device be found * max_bar - Max bar number(6 or 2) * rom_addr - The pci device rom addr * ****************************************************************************/ static void pci_setup_device(FAR struct pci_device_s *dev, int max_bar, uint8_t rom_addr) { int bar; uint32_t orig; uint32_t mask; uint32_t size; uintptr_t start; #ifdef CONFIG_PCI_ASSIGN_ALL_BUSES uint8_t cmd; pci_read_config_byte(dev, PCI_COMMAND, &cmd); pci_write_config_byte(dev, PCI_COMMAND, cmd & ~PCI_COMMAND_IO & ~PCI_COMMAND_MEMORY); #endif for (bar = 0; bar < max_bar; bar++) { int base_address_0 = PCI_BASE_ADDRESS_0 + bar * 4; int base_address_1 = PCI_BASE_ADDRESS_1 + bar * 4; FAR struct pci_resource_s *res; unsigned int flags; pci_read_config_dword(dev, base_address_0, &orig); pci_write_config_dword(dev, base_address_0, 0xfffffffe); pci_read_config_dword(dev, base_address_0, &mask); pci_write_config_dword(dev, base_address_0, orig); if (mask == 0 || mask == 0xffffffff) { pciinfo("pbar%d set bad mask\n", bar); continue; } if (mask & PCI_BASE_ADDRESS_SPACE_IO) { /* IO */ size = pci_size(orig, mask, 0xfffffffe); flags = PCI_RESOURCE_IO; res = &dev->bus->ctrl->io; } else if ((mask & PCI_BASE_ADDRESS_MEM_PREFETCH) && pci_resource_size(&dev->bus->ctrl->mem_pref)) { /* Prefetchable MEM */ size = pci_size(orig, mask, 0xfffffff0); flags = PCI_RESOURCE_MEM | PCI_RESOURCE_PREFETCH; res = &dev->bus->ctrl->mem_pref; } else { /* Non-prefetch MEM */ size = pci_size(orig, mask, 0xfffffff0); flags = PCI_RESOURCE_MEM; res = &dev->bus->ctrl->mem; } if (size == 0) { pcierr("pbar%d bad mask\n", bar); continue; } pciinfo("pbar%d: mask=%08" PRIx32 " %" PRIu32 "bytes\n", bar, mask, size); #ifdef CONFIG_PCI_ASSIGN_ALL_BUSES if (ALIGN(res->start, size) + size > res->end) { pcierr("pbar%d: does not fit within bus res\n", bar); return; } res->start = ALIGN(res->start, size); pci_write_config_dword(dev, base_address_0, res->start); if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64) { pci_write_config_dword(dev, base_address_1, res->start >> 32); } start = res->start; res->start += size; #else UNUSED(res); uint32_t tmp; pci_read_config_dword(dev, base_address_0, &tmp); if (mask & PCI_BASE_ADDRESS_SPACE_IO) { start = tmp & PCI_BASE_ADDRESS_IO_MASK; } else { start = tmp & PCI_BASE_ADDRESS_MEM_MASK; } if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64) { pci_read_config_dword(dev, base_address_1, &tmp); start |= (uint64_t)tmp << 32; } #endif dev->resource[bar].flags = flags; dev->resource[bar].start = start; dev->resource[bar].end = start + size - 1; if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64) { dev->resource[bar++].flags |= PCI_RESOURCE_MEM_64; } } pci_read_config_dword(dev, rom_addr, &orig); pci_write_config_dword(dev, rom_addr, ~PCI_ROM_ADDRESS_ENABLE); pci_read_config_dword(dev, rom_addr, &mask); pci_write_config_dword(dev, rom_addr, orig); start = PCI_ROM_ADDR(orig); size = PCI_ROM_SIZE(mask); if (start != 0 && size != 0) { dev->resource[PCI_ROM_RESOURCE].flags |= PCI_RESOURCE_MEM | PCI_RESOURCE_PREFETCH; dev->resource[PCI_ROM_RESOURCE].start = start; dev->resource[PCI_ROM_RESOURCE].end = start + size - 1; } #ifdef CONFIG_PCI_ASSIGN_ALL_BUSES pci_write_config_byte(dev, PCI_COMMAND, cmd); #endif list_add_tail(&dev->bus->devices, &dev->bus_list); } /**************************************************************************** * Name: pci_presetup_bridge * * Description: * Setup data to the next bridge register. * * Input Parameters: * dev - The next bridge dev * ****************************************************************************/ static void pci_presetup_bridge(FAR struct pci_device_s *dev) { #ifndef CONFIG_PCI_ASSIGN_ALL_BUSES pci_read_config_byte(dev, PCI_PRIMARY_BUS, &dev->bus->number); pci_read_config_byte(dev, PCI_SECONDARY_BUS, &dev->subordinate->number); #else FAR struct pci_controller_s *ctrl = dev->bus->ctrl; uint16_t cmdstat; pci_read_config_word(dev, PCI_COMMAND, &cmdstat); /* Configure bus number registers */ pci_write_config_byte(dev, PCI_PRIMARY_BUS, dev->bus->number); pci_write_config_byte(dev, PCI_SECONDARY_BUS, dev->subordinate->number); pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, 0xff); if (pci_resource_size(&ctrl->mem)) { /* Set up memory and I/O filter limits, assume 32-bit I/O space */ ctrl->mem.start = ALIGN(ctrl->mem.start, 1024 * 1024); pci_write_config_word(dev, PCI_MEMORY_BASE, (ctrl->mem.start & 0xfff00000) >> 16); cmdstat |= PCI_COMMAND_MEMORY; } else { pci_write_config_word(dev, PCI_MEMORY_BASE, 0x1000); pci_write_config_word(dev, PCI_MEMORY_LIMIT, 0x0); } if (pci_resource_size(&ctrl->mem_pref)) { /* Set up memory and I/O filter limits, assume 32-bit I/O space */ ctrl->mem_pref.start = ALIGN(ctrl->mem_pref.start, 1024 * 1024); pci_write_config_word(dev, PCI_PREF_MEMORY_BASE, (ctrl->mem_pref.start & 0xfff00000) >> 16); pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, ctrl->mem_pref.start >> 32); cmdstat |= PCI_COMMAND_MEMORY; } else { /* We don't support prefetchable memory for now, so disable */ pci_write_config_word(dev, PCI_PREF_MEMORY_BASE, 0x1000); pci_write_config_word(dev, PCI_PREF_MEMORY_LIMIT, 0x0); pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, 0x0); pci_write_config_dword(dev, PCI_PREF_LIMIT_UPPER32, 0x0); } if (pci_resource_size(&ctrl->io)) { ctrl->io.start = ALIGN(ctrl->io.start, 1024 * 4); pci_write_config_byte(dev, PCI_IO_BASE, (ctrl->io.start & 0x0000f000) >> 8); pci_write_config_word(dev, PCI_IO_BASE_UPPER16, (ctrl->io.start & 0xffff0000) >> 16); cmdstat |= PCI_COMMAND_IO; } /* Enable memory and I/O accesses, enable bus master */ pci_write_config_word(dev, PCI_COMMAND, cmdstat | PCI_COMMAND_MASTER); #endif } /**************************************************************************** * Name: pci_postsetup_bridge * * Description: * Setup data into Limit subordinate reg. * * Input Parameters: * dev - The next bridge dev * ****************************************************************************/ static void pci_postsetup_bridge(FAR struct pci_device_s *dev) { #ifdef CONFIG_PCI_ASSIGN_ALL_BUSES FAR struct pci_controller_s *ctrl = dev->bus->ctrl; /* Limit subordinate to last used bus number */ pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, ctrl->busno - 1); if (pci_resource_size(&ctrl->mem)) { ctrl->mem.start = ALIGN(ctrl->mem.start, 1024 * 1024); pciinfo("bridge NP limit at %" PRIxPTR "\n", ctrl->mem.start); pci_write_config_word(dev, PCI_MEMORY_LIMIT, ((ctrl->mem.start - 1) & 0xfff00000) >> 16); } if (pci_resource_size(&ctrl->mem_pref)) { ctrl->mem_pref.start = ALIGN(ctrl->mem_pref.start, 1024 * 1024); pciinfo("bridge P limit at %" PRIxPTR "\n", ctrl->mem_pref.start); pci_write_config_word(dev, PCI_PREF_MEMORY_LIMIT, ((ctrl->mem_pref.start - 1) & 0xfff00000) >> 16); pci_write_config_dword(dev, PCI_PREF_LIMIT_UPPER32, (ctrl->mem_pref.start - 1) >> 32); } if (pci_resource_size(&ctrl->io)) { ctrl->io.start = ALIGN(ctrl->io.start, 1024 * 4); pciinfo("bridge IO limit at %" PRIxPTR "\n", ctrl->io.start); pci_write_config_byte(dev, PCI_IO_LIMIT, ((ctrl->io.start - 1) & 0x0000f000) >> 8); pci_write_config_word(dev, PCI_IO_LIMIT_UPPER16, ((ctrl->io.start - 1) & 0xffff0000) >> 16); } #endif } /**************************************************************************** * Name: pci_scan_bus * * Description: * Iterates over all slots on bus looking for devices and buses to * enumerate. * * Input Parameters: * bus - The root bus device that lets us address the whole tree * ****************************************************************************/ static void pci_scan_bus(FAR struct pci_bus_s *bus) { FAR struct pci_device_s *dev; FAR struct pci_bus_s *child_bus; uint32_t devfn; uint32_t l; uint32_t class; uint8_t hdr_type; uint8_t is_multi = 0; pciinfo("pci_scan_bus for bus %d\n", bus->number); for (devfn = 0; devfn < 0xff; ++devfn) { if (PCI_FUNC(devfn) && !is_multi) { /* Not a multi-function device */ continue; } if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type)) { continue; } if (!PCI_FUNC(devfn)) { is_multi = hdr_type & 0x80; } /* Some broken boards return 0 if a slot is empty: */ if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l) || l == 0xffffffff || l == 0x00000000 || l == 0x0000ffff || l == 0xffff0000) { continue; } dev = pci_alloc_device(); dev->bus = bus; dev->devfn = devfn; dev->vendor = l & 0xffff; dev->device = (l >> 16) & 0xffff; dev->hdr_type = hdr_type; pci_read_config_dword(dev, PCI_CLASS_REVISION, &class); dev->revision = class & 0xff; /* Upper 3 bytes */ class >>= 8; dev->class = class; class >>= 8; pciinfo("class = %08" PRIx32 ", hdr_type = %08x\n", class, hdr_type); pciinfo("%02x:%02" PRIx32 " [%04x:%04x]\n", bus->number, dev->devfn, dev->vendor, dev->device); switch (hdr_type & 0x7f) { case PCI_HEADER_TYPE_NORMAL: if (class == PCI_CLASS_BRIDGE_PCI) { goto bad; } pci_setup_device(dev, 6, PCI_ROM_ADDRESS); pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device); pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor); break; case PCI_HEADER_TYPE_BRIDGE: child_bus = pci_alloc_bus(); /* Inherit parent properties */ child_bus->ctrl = bus->ctrl; child_bus->parent_bus = bus; #ifdef CONFIG_PCI_ASSIGN_ALL_BUSES child_bus->number = ctrl->busno++; #endif list_add_tail(&bus->children, &child_bus->node); dev->subordinate = child_bus; /* Scan pci hierarchy behind bridge */ pci_presetup_bridge(dev); pci_scan_bus(child_bus); pci_postsetup_bridge(dev); pci_setup_device(dev, 2, PCI_ROM_ADDRESS1); break; default: bad: pcierr("PCI: %02x:%02" PRIx32 " [%04x/%04x/%06" PRIx32 "] has unknown header type %02x, ignoring.\n", bus->number, dev->devfn, dev->vendor, dev->device, class, hdr_type); continue; } } } /**************************************************************************** * Name: pci_get_msi_base * * Description: * Get MSI and MSI-X base * * Input Parameters: * dev - device * msi - returned MSI base * msix - returned MSI-X base * * Return value: * None * ****************************************************************************/ static void pci_get_msi_base(FAR struct pci_device_s *dev, FAR uint8_t *msi, FAR uint8_t *msix) { if (msi != NULL) { *msi = pci_find_capability(dev, PCI_CAP_ID_MSI); } if (msix != NULL) { *msix = pci_find_capability(dev, PCI_CAP_ID_MSIX); } } /**************************************************************************** * Name: pci_enable_msi * * Description: * Configure and enable MSI. * * Input Parameters: * dev - device * irq - allocated vectors * num - number of vectors * msi - MSI base address * * Return value: * OK on success or a negative error code on failure * ****************************************************************************/ static int pci_enable_msi(FAR struct pci_device_s *dev, FAR int *irq, int num, uint8_t msi) { uint32_t mdr = 0; uint16_t flags = 0; uintptr_t mar = 0; uint16_t mme = 0; uint32_t mmc = 0; int ret = OK; /* Suppoted messages */ for (mme = 0; (1 << mme) < num; mme++); /* Get Message Control Register */ pci_read_config_word(dev, msi + PCI_MSI_FLAGS, &flags); mmc = (flags & PCI_MSI_FLAGS_QMASK) >> PCI_MSI_FLAGS_QMASK_SHIFT; if (mme > mmc) { mme = mmc; num = 1 << mme; pciinfo("Limit MME to %x, num to %d\n", mmc, num); } /* Configure MSI (arch-specific) */ ret = dev->bus->ctrl->ops->connect_irq(dev->bus, irq, num, &mar, &mdr); if (ret < 0) { return ret; } /* Write Message Address Regsiter */ pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_LO, mar); /* Write Message Data Register */ if ((flags & PCI_MSI_FLAGS_64BIT) != 0) { pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_HI, (mar >> 32)); pci_write_config_dword(dev, msi + PCI_MSI_DATA_64, mdr); } else { pci_write_config_word(dev, msi + PCI_MSI_DATA_32, mdr); } flags |= mme << PCI_MSI_FLAGS_QSIZE_SHIFT; /* Enable MSI */ flags |= PCI_MSI_FLAGS_ENABLE; /* Write Message Control Register */ pci_write_config_word(dev, msi + PCI_MSI_FLAGS, flags); return OK; } #ifdef CONFIG_PCI_MSIX /**************************************************************************** * Name: pci_disable_msi * * Description: * Disable MSI. * * Input Parameters: * dev - device * msi - MSI base address * * Return value: * None * ****************************************************************************/ static void pci_disable_msi(FAR struct pci_device_s *dev, uint8_t msi) { uint16_t flags = 0; pci_read_config_word(dev, msi + PCI_MSI_FLAGS, &flags); flags &= ~PCI_MSI_FLAGS_ENABLE; pci_write_config_word(dev, msi + PCI_MSI_FLAGS, flags); } /**************************************************************************** * Name: pci_enable_msix * * Description: * Configure and enable MSI-X. * * Input Parameters: * dev - device * irq - allocated vectors * num - number of vectors * msix - MSI-X base address * * Return value: * OK on success or a negative error code on failure * ****************************************************************************/ static int pci_enable_msix(FAR struct pci_device_s *dev, FAR int *irq, int num, uint8_t msix) { uint32_t mdr = 0; uint16_t flags = 0; uintptr_t mar = 0; uintptr_t tbladdr = 0; uintptr_t tblend = 0; uint32_t tbloffset = 0; uint32_t tblbar = 0; uint32_t tbl = 0; uint16_t tblsize = 0; int i = 0; int ret = OK; /* Get Flags */ pci_read_config_word(dev, msix + PCI_MSIX_FLAGS, &flags); /* Table Size is N - 1 encoded */ tblsize = (flags & PCI_MSIX_FLAGS_QSIZE) + 1; /* Get MSI-X table */ pci_read_config_dword(dev, msix + PCI_MSIX_TABLE, &tbl); /* Extract table address */ tblbar = tbl & PCI_MSIX_TABLE_BIR; tbladdr = pci_resource_start(dev, tblbar); tbloffset = (tbl & PCI_MSIX_TABLE_OFFSET) >> PCI_MSIX_TABLE_OFFSET_SHIFT; tbladdr += tbloffset; /* Map MSI-X table */ tblend = tbladdr + tblsize * PCI_MSIX_ENTRY_SIZE; tbladdr = dev->bus->ctrl->ops->map(dev->bus, tbladdr, tblend); /* Limit tblsize */ if (num > tblsize) { pciinfo("Limit tblszie to %xu\n", tblsize); num = tblsize; } for (i = 0; i < num; i++) { /* Connect MSI-X (arch-specific) */ ret = dev->bus->ctrl->ops->connect_irq(dev->bus, &irq[i], 1, &mar, &mdr); if (ret < 0) { return ret; } /* Write Message Address Register */ pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_LOWER_ADDR, mar); pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_UPPER_ADDR, (mar >> 32)); /* Write Message Data Register */ pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_DATA, mdr); /* Write Vector Control register */ pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_VECTOR_CTRL, 0); /* Next vector */ tbladdr += PCI_MSIX_ENTRY_SIZE; } /* Enable MSI-X */ flags |= PCI_MSIX_FLAGS_ENABLE; pci_write_config_word(dev, msix + PCI_MSIX_FLAGS, flags); return OK; } #endif /* CONFIG_PCI_MSIX */ /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: pci_find_device_from_bus * * Description: * To find a PCI device from the bus * * Input Parameters: * bus - pci bus * busno - bus number * devfn - device number and function number * * Returned Value: * Failed if return NULL, otherwise return pci devices * ****************************************************************************/ FAR struct pci_device_s * pci_find_device_from_bus(FAR struct pci_bus_s *bus, uint8_t busno, unsigned int devfn) { FAR struct pci_device_s *dev; int ret; ret = nxmutex_lock(&g_pci_lock); if (ret < 0) { return NULL; } dev = pci_do_find_device_from_bus(bus, busno, devfn); nxmutex_unlock(&g_pci_lock); return dev; } /**************************************************************************** * Name: pci_bus_read_config * * Description: * Read pci device config space * * Input Parameters: * bus - The PCI device belong to * devfn - The PCI device dev number and function number * where - The register address * size - The data length * val - The data buffer * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ int pci_bus_read_config(FAR struct pci_bus_s *bus, unsigned int devfn, int where, int size, FAR uint32_t *val) { if (size != 1 && size != 2 && size != 4) { return -EINVAL; } return bus->ctrl->ops->read(bus, devfn, where, size, val); } /**************************************************************************** * Name: pci_bus_write_config * * Description: * Read pci device config space * * Input Parameters: * bus - The PCI device belong to * devfn - The PCI device dev number and function number * where - The register address * size - The data length * val - The data * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ int pci_bus_write_config(FAR struct pci_bus_s *bus, unsigned int devfn, int where, int size, uint32_t val) { if (size != 1 && size != 2 && size != 4) { return -EINVAL; } return bus->ctrl->ops->write(bus, devfn, where, size, val); } /**************************************************************************** * Name: pci_bus_read_io * * Description: * Read pci device io space * * Input Parameters: * bus - The PCI device belong to * addr - The address to read * size - The data length * val - The data buffer * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ int pci_bus_read_io(FAR struct pci_bus_s *bus, uintptr_t addr, int size, FAR uint32_t *val) { if (size != 1 && size != 2 && size != 4) { return -EINVAL; } return bus->ctrl->ops->read_io(bus, addr, size, val); } /**************************************************************************** * Name: pci_bus_write_io * * Description: * Read pci device io space * * Input Parameters: * bus - The PCI device belong to * addr - The address to write * size - The data length * val - The data * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ int pci_bus_write_io(FAR struct pci_bus_s *bus, uintptr_t addr, int size, uint32_t val) { if (size != 1 && size != 2 && size != 4) { return -EINVAL; } return bus->ctrl->ops->write_io(bus, addr, size, val); } /**************************************************************************** * Name: pci_set_master * * Description: * Enables bus-mastering for device * * Input Parameters: * dev - The PCI device to enable * ****************************************************************************/ void pci_set_master(FAR struct pci_device_s *dev) { pci_change_master(dev, true); } /**************************************************************************** * Name: pci_clear_master * * Description: * Disables bus-mastering for device * * Input Parameters: * dev - The PCI device to disable * ****************************************************************************/ void pci_clear_master(FAR struct pci_device_s *dev) { pci_change_master(dev, false); } /**************************************************************************** * Name: pci_enable_device * * Description: * Initialize device before it's used by a driver by setting command * register. * * Input Parameters: * dev - PCI device to be enabled * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ int pci_enable_device(FAR struct pci_device_s *dev) { uint32_t cmd; pci_read_config_dword(dev, PCI_COMMAND, &cmd); return pci_write_config_dword(dev, PCI_COMMAND, cmd | PCI_COMMAND_IO | PCI_COMMAND_MEMORY); } /**************************************************************************** * Name: pci_disable_device * * Description: * Disable pci device before it's unused by a driver by setting command * register. * * Input Parameters: * dev - PCI device to be disable * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ int pci_disable_device(FAR struct pci_device_s *dev) { uint32_t cmd; pci_read_config_dword(dev, PCI_COMMAND, &cmd); return pci_write_config_dword(dev, PCI_COMMAND, cmd & ~PCI_COMMAND_IO & ~PCI_COMMAND_MEMORY); } /**************************************************************************** * Name: pci_select_bars * * Description: * Make BAR mask from the type of resource * This helper routine makes bar mask from the type of resource. * * Input Parameters: * dev - The PCI device for which BAR mask is made * flags - Resource type mask to be selected * ****************************************************************************/ int pci_select_bars(FAR struct pci_device_s *dev, unsigned int flags) { int bars = 0; int i; for (i = 0; i < PCI_NUM_RESOURCES; i++) { if (pci_resource_flags(dev, i) & flags) { bars |= 1 << i; } } return bars; } /**************************************************************************** * Name: pci_map_bar * * Description: * Create a virtual mapping cookie for a PCI BAR. * * Using this function you will get an address to your device BAR. * These functions hide the details if this is a MMIO or PIO address * space and will just do what you expect from them in the correct way. * * Input Parameters: * dev - PCI device that owns the BAR * bar - BAR number * * Returned Value: * IO address or zero if failed * ****************************************************************************/ FAR void *pci_map_bar(FAR struct pci_device_s *dev, int bar) { FAR struct pci_bus_s *bus = dev->bus; uintptr_t start = pci_resource_start(dev, bar); uintptr_t end = pci_resource_end(dev, bar); if (bus->ctrl->ops->map) { start = bus->ctrl->ops->map(bus, start, end); } return (FAR void *)start; } /**************************************************************************** * Name: pci_find_capability * * Description: * Query for devices' capabilities * * Tell if a device supports a given PCI capability. * * Input Parameters: * dev - PCI device to query * cap - Capability code * * Returned Value: * Returns the address of the requested capability structure within the * device's PCI configuration space or 0 in case the device does not * support it. * ****************************************************************************/ uint8_t pci_find_capability(FAR struct pci_device_s *dev, int cap) { uint8_t pos; pos = pci_bus_find_start_cap(dev->bus, dev->devfn, dev->hdr_type); if (pos) { pos = pci_find_next_cap(dev->bus, dev->devfn, pos, cap); } return pos; } /**************************************************************************** * Name: pci_find_next_capability * * Description: * To find the next capability. * * Input Parameters: * dev - The PCI device to find capbilities * pos - List node * cap - Value of capabilities * * Returned Value: * Return the capability data * ****************************************************************************/ uint8_t pci_find_next_capability(FAR struct pci_device_s *dev, uint8_t pos, int cap) { return pci_find_next_cap(dev->bus, dev->devfn, pos + PCI_CAP_LIST_NEXT, cap); } /**************************************************************************** * Name: pci_stat_line * * Description: * Determine if the interrupt line is active for a given device * * Input Parameters: * dev - device * * Return value: * True if interrupt is active * ****************************************************************************/ bool pci_stat_line(FAR struct pci_device_s *dev) { uint16_t tmp1; uint16_t tmp2; /* Interrupts enabled if Interrupt Disable is not set and Interrupt Status * is set. */ pci_read_config_word(dev, PCI_COMMAND, &tmp1); pci_read_config_word(dev, PCI_STATUS, &tmp2); return (!(tmp1 & PCI_COMMAND_INTX_DISABLE) && (tmp2 & PCI_STATUS_INTERRUPT)); } /**************************************************************************** * Name: pci_get_irq * * Description: * Get interrupt number associated with a device PCI interrupt line * * Input Parameters: * dev - PCI device * * Return value: * Return interrupt number associated with a given INTx. * ****************************************************************************/ int pci_get_irq(FAR struct pci_device_s *dev) { uint8_t line = 0; pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &line); return dev->bus->ctrl->ops->get_irq(dev->bus, line); } /**************************************************************************** * Name: pci_alloc_irq * * Description: * Allocate MSI or MSI-X vectors * * Input Parameters: * dev - PCI device * irq - allocated vectors * num - number of vectors * * Return value: * Return the number of allocated vectors on succes or negative errno * on failure. * ****************************************************************************/ int pci_alloc_irq(FAR struct pci_device_s *dev, FAR int *irq, int num) { return dev->bus->ctrl->ops->alloc_irq(dev->bus, irq, num); } /**************************************************************************** * Name: pci_release_irq * * Description: * Release MSI or MSI-X vectors * * Input Parameters: * dev - PCI device * irq - allocated vectors * num - number of vectors * * Return value: * Failed if return a negative value, otherwise success * ****************************************************************************/ void pci_release_irq(FAR struct pci_device_s *dev, FAR int *irq, int num) { dev->bus->ctrl->ops->release_irq(dev->bus, irq, num); } /**************************************************************************** * Name: pci_connect_irq * * Description: * Connect MSI or MSI-X if available. * * Input Parameters: * dev - PCI device * irq - allocated vectors * num - number of vectors * * Return value: * Return -ENOSETUP if MSI/MSI-X not available. Return OK on success. * ****************************************************************************/ int pci_connect_irq(FAR struct pci_device_s *dev, FAR int *irq, int num) { uint8_t msi = 0; uint8_t msix = 0; /* Get MSI base */ pci_get_msi_base(dev, &msi, &msix); if (msi == 0 && msix == 0) { /* MSI and MSI-X not supported */ return -ENOTSUP; } /* Configure MSI or MSI-X */ #ifdef CONFIG_PCI_MSIX if (msix != 0) { /* Disalbe MSI */ pci_disable_msi(dev, msi); /* Enable MSI-X */ return pci_enable_msix(dev, irq, num, msix); } else #endif { /* Enable MSI */ return pci_enable_msi(dev, irq, num, msi); } } /**************************************************************************** * Name: pci_register_driver * * Description: * To register a PCI driver * * Input Parameters: * drv - PCI driver * * Returned Value: * Failed if return a negative value, otherwise success * ****************************************************************************/ int pci_register_driver(FAR struct pci_driver_s *drv) { FAR const struct pci_device_id_s *id; FAR struct pci_device_s *dev; int ret; DEBUGASSERT(drv != NULL && drv->probe != NULL && drv->id_table != NULL); ret = nxmutex_lock(&g_pci_lock); if (ret < 0) { return ret; } /* Add the driver to the pci driver list */ list_add_tail(&g_pci_driver_list, &drv->node); list_for_every_entry(&g_pci_device_list, dev, struct pci_device_s, node) { if (dev->drv != NULL) { continue; } for (id = drv->id_table; id->vendor; id++) { if (pci_match_one_device(id, dev)) { dev->id = id; if (drv->probe(dev) >= 0) { dev->drv = drv; } } } } nxmutex_unlock(&g_pci_lock); return ret; } /**************************************************************************** * Name: pci_unregister_driver * * Description: * To unregister a PCI driver * * Input Parameters: * drv - PCI driver * * Returned Value: * Failed if return a negative value, otherwise success * ****************************************************************************/ int pci_unregister_driver(FAR struct pci_driver_s *drv) { FAR struct pci_device_s *dev; int ret; DEBUGASSERT(drv != NULL && drv->remove != NULL); ret = nxmutex_lock(&g_pci_lock); if (ret < 0) { return ret; } list_for_every_entry(&g_pci_device_list, dev, struct pci_device_s, node) { if (dev->drv == drv) { drv->remove(dev); dev->drv = NULL; } } list_delete(&drv->node); nxmutex_unlock(&g_pci_lock); return ret; } /**************************************************************************** * Name: pci_register_device * * Description: * To register a PCI device * * Input Parameters: * dev - PCI device * * Returned Value: * Failed if return a negative value, otherwise success * ****************************************************************************/ int pci_register_device(FAR struct pci_device_s *dev) { FAR const struct pci_device_id_s *id; FAR struct pci_driver_s *drv; int ret; ret = nxmutex_lock(&g_pci_lock); if (ret < 0) { return ret; } list_add_tail(&g_pci_device_list, &dev->node); list_for_every_entry(&g_pci_driver_list, drv, struct pci_driver_s, node) { for (id = drv->id_table; id->vendor; id++) { if (pci_match_one_device(id, dev)) { dev->id = id; if (drv->probe(dev) >= 0) { dev->drv = drv; break; } } } } nxmutex_unlock(&g_pci_lock); return ret; } /**************************************************************************** * Name: pci_unregister_device * * Description: * To unregister a PCI device * * Input Parameters: * dev - PCI device * * Returned Value: * Failed if return a negative value, otherwise success * ****************************************************************************/ int pci_unregister_device(FAR struct pci_device_s *dev) { int ret; ret = nxmutex_lock(&g_pci_lock); if (ret < 0) { return ret; } if (dev->drv && dev->drv->remove) { dev->drv->remove(dev); } dev->drv = NULL; list_delete(&dev->node); nxmutex_unlock(&g_pci_lock); return ret; } /**************************************************************************** * Name: pci_register_controller * * Description: * Start pci bridge enumeration process, and register pci device. * * Input Parameters: * ctrl - PCI controller to register * ****************************************************************************/ int pci_register_controller(FAR struct pci_controller_s *ctrl) { FAR struct pci_bus_s *bus; if (ctrl == NULL) { return -EINVAL; } bus = pci_alloc_bus(); bus->ctrl = ctrl; ctrl->bus = bus; ctrl->busno = 1; pci_scan_bus(bus); pci_register_bus_devices(bus); nxmutex_lock(&g_pci_lock); list_add_tail(&g_pci_ctrl_list, &ctrl->node); nxmutex_unlock(&g_pci_lock); return 0; } /**************************************************************************** * Name: pci_bus_find_capability * * Description: * Query for devices' capabilities * * Tell if a device supports a given PCI capability. * * Input Parameters: * bus - PCI device bus belong to * devfn - PCI device number and function number * cap - Capability code * * Returned Value: * Returns the address of the requested capability structure within the * device's PCI configuration space or 0 in case the device does not * support it. * ****************************************************************************/ uint8_t pci_bus_find_capability(FAR struct pci_bus_s *bus, unsigned int devfn, int cap) { uint8_t type = 0; uint8_t pos; pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &type); pos = pci_bus_find_start_cap(bus, devfn, type); if (pos) { pos = pci_find_next_cap(bus, devfn, pos, cap); } return pos; } /**************************************************************************** * Name: pci_dev_register * * Description: * Create an pci dev driver. * * Returned Value: * Zero (OK) on success; A negated errno value on failure. * ****************************************************************************/ int pci_dev_register(void) { return register_driver("/dev/pci", &g_pci_fops, 0666, NULL); } /**************************************************************************** * Name: pci_bus_read_config_xxx * * Description: * Read pci device config space * * Input Parameters: * bus - The PCI device to belong to * devfn - The PCI device number and function number * where - The register address * val - The data buf * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ PCI_BUS_READ_CONFIG(byte, uint8_t, 1) PCI_BUS_READ_CONFIG(word, uint16_t, 2) PCI_BUS_READ_CONFIG(dword, uint32_t, 4) /**************************************************************************** * Name: pci_bus_write_config_xxx * * Description: * Write pci device config space * * Input Parameters: * bus - The PCI device to belong to * devfn - The PCI device number and function number * where - The register address * val - The data * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ PCI_BUS_WRITE_CONFIG(byte, uint8_t, 1) PCI_BUS_WRITE_CONFIG(word, uint16_t, 2) PCI_BUS_WRITE_CONFIG(dword, uint32_t, 4) /**************************************************************************** * Name: pci_bus_read_io_xxx * * Description: * Read pci device io space * * Input Parameters: * bus - The PCI device belong to * where - The address to read * val - The data buffer * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ PCI_BUS_READ_IO(byte, uint8_t, 1) PCI_BUS_READ_IO(word, uint16_t, 2) PCI_BUS_READ_IO(dword, uint32_t, 4) /**************************************************************************** * Name: pci_bus_write_io_xxx * * Description: * Write pci device io space * * Input Parameters: * bus - The PCI device belong to * where - The address to write * val - The data * * Returned Value: * Zero if success, otherwise nagative * ****************************************************************************/ PCI_BUS_WRITE_IO(byte, uint8_t, 1) PCI_BUS_WRITE_IO(word, uint16_t, 2) PCI_BUS_WRITE_IO(dword, uint32_t, 4)