/**************************************************************************** * 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 /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* For now hard code jailhouse as a flag. In the future we can determine this * by looking at the CPUID base for "Jailhouse\0\0\0" */ #define JAILHOUSE_ENABLED 1 #define PCI_BDF(bus, slot, func) (((uint32_t)bus << 8) | \ ((uint32_t)slot << 3) | \ func) /**************************************************************************** * Private Functions Definitions ****************************************************************************/ static void pci_probe_device(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, uint8_t slot_idx, uint8_t func, FAR const struct pci_dev_type_s **types); static uint8_t pci_check_pci_bridge(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, uint8_t slot_idx, uint8_t dev_func); static void pci_scan_device(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, uint8_t slot_idx, FAR const struct pci_dev_type_s **types); static void pci_scan_bus(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, FAR const struct pci_dev_type_s **types); static void pci_set_cmd_bit(FAR struct pci_dev_s *dev, uint16_t bitmask); static void pci_clear_cmd_bit(FAR struct pci_dev_s *dev, uint16_t bitmask); /**************************************************************************** * Public Data ****************************************************************************/ const struct pci_dev_type_s *g_pci_device_types[] = { #ifdef CONFIG_VIRT_QEMU_PCI_TEST &g_pci_type_qemu_pci_test, #endif #ifdef CONFIG_VIRT_QEMU_EDU &g_pci_type_qemu_edu, #endif NULL, }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: pci_probe * * Description: * Checks if the specified device is supported and if so calls probe on it * * Input Parameters: * root_bus - The root bus device that lets us address the whole tree * bus - Bus ID * slot - Device Slot * func - Device Function * types - List of pointers to devices types recognized, NULL terminated * ****************************************************************************/ static void pci_probe_device(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, uint8_t slot_idx, uint8_t func, FAR const struct pci_dev_type_s **types) { struct pci_dev_s tmp_dev; uint32_t class_rev; uint16_t vid; uint16_t id; int i; tmp_dev.bus = root_bus; tmp_dev.bdf = PCI_BDF(bus_idx, slot_idx, func); vid = root_bus->ops->pci_cfg_read(&tmp_dev, PCI_CONFIG_VENDOR, 2); id = root_bus->ops->pci_cfg_read(&tmp_dev, PCI_CONFIG_DEVICE, 2); /* This is reading rev prog_if subclass and class */ class_rev = root_bus->ops->pci_cfg_read(&tmp_dev, PCI_CONFIG_REV_ID, 4); pci_dev_dump(&tmp_dev); for (i = 0; types[i] != NULL; i++) { if (types[i]->vendor == PCI_ID_ANY || types[i]->vendor == vid) { if (types[i]->device == PCI_ID_ANY || types[i]->device == id) { if (types[i]->class_rev == PCI_ID_ANY || types[i]->class_rev == class_rev) { pciinfo("Found: %s\n", types[i]->name); if (types[i]->probe) { pciinfo("[%02x:%02x.%x] Probing\n", bus_idx, slot_idx, func); types[i]->probe(root_bus, types[i], tmp_dev.bdf); } else { pcierr("[%02x:%02x.%x] Error: Invalid" "device probe function\n", bus_idx, slot_idx, func); } break; } } } } } /**************************************************************************** * Name: pci_check_pci_bridge * * Description: * Checks if the specified device is PCI bridge and return the sub-bridge * idx if found. Otherwise return 0. * * Input Parameters: * root_bus - The root bus device that lets us address the whole tree * bus - Bus ID * slot - Device Slot * func - Device Function * ****************************************************************************/ static uint8_t pci_check_pci_bridge(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, uint8_t slot_idx, uint8_t dev_func) { struct pci_dev_s tmp_dev; uint8_t base_class; uint8_t sub_class; uint8_t secondary_bus; tmp_dev.bus = root_bus; tmp_dev.bdf = PCI_BDF(bus_idx, slot_idx, dev_func); /* Check if this is a PCI-PCI bridge device */ base_class = root_bus->ops->pci_cfg_read(&tmp_dev, PCI_CONFIG_CLASS, 1); sub_class = root_bus->ops->pci_cfg_read(&tmp_dev, PCI_CONFIG_SUBCLASS, 1); if ((base_class == PCI_CLASS_BASE_BRG_DEV) && (sub_class == PCI_CLASS_SUB_PCI_BRG)) { /* This is a bridge device we need to determine the bus idx and * enumerate it just like we do the root. */ pciinfo("[%02x:%02x.%x] Found Bridge\n", bus_idx, slot_idx, dev_func); secondary_bus = root_bus->ops->pci_cfg_read( &tmp_dev, PCI_CONFIG_SEC_BUS, 1); return secondary_bus; } return 0; } /**************************************************************************** * Name: pci_scan_device * * Description: * Checks if the specified device is a bus and iterates over it or * if it is a real device initializes it if recognized. * * Input Parameters: * root_bus - The root bus device that lets us address the whole tree * bus - Bus ID * slot - Device Slot * types - List of pointers to devices types recognized, NULL terminated * ****************************************************************************/ static void pci_scan_device(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, uint8_t slot_idx, FAR const struct pci_dev_type_s **types) { struct pci_dev_s tmp_dev; uint8_t multi_function; uint8_t dev_func = 0; uint16_t vid; uint8_t sec_bus; tmp_dev.bus = root_bus; tmp_dev.bdf = PCI_BDF(bus_idx, slot_idx, dev_func); vid = root_bus->ops->pci_cfg_read(&tmp_dev, PCI_CONFIG_VENDOR, 2); if (vid == 0xffff) { return; } multi_function = root_bus->ops->pci_cfg_read( &tmp_dev, PCI_CONFIG_HEADER_TYPE, 1) & PCI_HEADER_MASK_MULTI; /* Jailhouse breaks the PCI spec by allowing you to pass individual * functions of a multi-function device. In this case we need to * scan each of the functions not just function 0. */ if (multi_function || JAILHOUSE_ENABLED) { /* This is a multi-function device that we need to iterate over */ for (dev_func = 0; dev_func < 8; dev_func++) { tmp_dev.bdf = PCI_BDF(bus_idx, slot_idx, dev_func); vid = root_bus->ops->pci_cfg_read(&tmp_dev, PCI_CONFIG_VENDOR, 2); if (vid != 0xffff) { sec_bus = pci_check_pci_bridge( root_bus, bus_idx, slot_idx, dev_func); if (sec_bus) { pci_scan_bus(root_bus, sec_bus, types); continue; } pci_probe_device(root_bus, bus_idx, slot_idx, dev_func, types); } } } else { /* Check if this is a PCI-PCI bridge device with MF=0 */ sec_bus = pci_check_pci_bridge(root_bus, bus_idx, slot_idx, dev_func); if (sec_bus) { pci_scan_bus(root_bus, sec_bus, types); } else { pci_probe_device(root_bus, bus_idx, slot_idx, dev_func, types); } } } /**************************************************************************** * Name: pci_scan_bus * * Description: * Iterates over all slots on bus looking for devices and buses to * enumerate. * * Input Parameters: * root_bus - The root bus device that lets us address the whole tree * bus - Bus ID * types - List of pointers to devices types recognized, NULL terminated * ****************************************************************************/ static void pci_scan_bus(FAR struct pci_bus_s *root_bus, uint8_t bus_idx, FAR const struct pci_dev_type_s **types) { uint8_t slot_idx; for (slot_idx = 0; slot_idx < 32; slot_idx++) { pci_scan_device(root_bus, bus_idx, slot_idx, types); } } /**************************************************************************** * Name: pci_set_cmd_bit * * Description: * This sets an individual bit in the command register for a device. * * Input Parameters: * dev - device * bit - Bit to set * ****************************************************************************/ static void pci_set_cmd_bit(FAR struct pci_dev_s *dev, uint16_t bitmask) { uint16_t cmd; cmd = dev->bus->ops->pci_cfg_read(dev, PCI_CONFIG_COMMAND, 2); dev->bus->ops->pci_cfg_write(dev, PCI_CONFIG_COMMAND, (cmd | bitmask), 2); } /**************************************************************************** * Name: pci_clear_cmd_bit * * Description: * This clears an individual bit in the command register for a device. * * Input Parameters: * dev - device * bit - Bit to set * ****************************************************************************/ static void pci_clear_cmd_bit(FAR struct pci_dev_s *dev, uint16_t bitmask) { uint16_t cmd; cmd = dev->bus->ops->pci_cfg_read(dev, PCI_CONFIG_COMMAND, 2); dev->bus->ops->pci_cfg_write(dev, PCI_CONFIG_COMMAND, (cmd & ~bitmask), 2); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: pci_enumerate * * Description: * Scan the PCI bus and enumerate the devices. * Initialize any recognized devices, given in types. * * Input Parameters: * bus - PCI-E bus structure * types - List of pointers to devices types recognized, NULL terminated * * Returned Value: * 0: success, <0: A negated errno * ****************************************************************************/ int pci_enumerate(FAR struct pci_bus_s *bus, FAR const struct pci_dev_type_s **types) { if (!bus) { return -EINVAL; } if (!types) { return -EINVAL; } pci_scan_bus(bus, 0, types); return OK; } /**************************************************************************** * Name: pci_initialize * * Description: * Initialize the PCI-E bus and enumerate the devices with give devices * type array * * Input Parameters: * bus - An PCIE bus * types - A array of PCIE device types * num - Number of device types * * Returned Value: * OK if the driver was successfully register; A negated errno value is * returned on any failure. * ****************************************************************************/ int pci_initialize(FAR struct pci_bus_s *bus) { return pci_enumerate(bus, g_pci_device_types); } /**************************************************************************** * Name: pci_enable_io * * Description: * Enable MMIO or IOPORT * * Input Parameters: * dev - device * space - which resource is being enabled * PCI_SYS_RES_IOPORT for io port address decoding or * PCI_SYS_RES_MEM for memory * * Return value: * -EINVAL: error * OK: OK * ****************************************************************************/ int pci_enable_io(FAR struct pci_dev_s *dev, int res) { switch (res) { case PCI_SYS_RES_IOPORT: { pci_set_cmd_bit(dev, PCI_CMD_IO_SPACE); return OK; } case PCI_SYS_RES_MEM: { pci_set_cmd_bit(dev, PCI_CMD_MEM_SPACE); return OK; } } return -EINVAL; } /**************************************************************************** * Name: pci_disable_io * * Description: * Enable MMIO or IOPORT * * Input Parameters: * dev - device * space - which resource is being disabled * PCI_SYS_RES_IOPORT for io port address decoding or * PCI_SYS_RES_MEM for memory * * Return value: * -EINVAL: error * OK: OK * ****************************************************************************/ int pci_disable_io(FAR struct pci_dev_s *dev, int res) { switch (res) { case PCI_SYS_RES_IOPORT: { pci_clear_cmd_bit(dev, PCI_CMD_IO_SPACE); return OK; } case PCI_SYS_RES_MEM: { pci_clear_cmd_bit(dev, PCI_CMD_MEM_SPACE); return OK; } } return -EINVAL; } /**************************************************************************** * Name: pci_enable_bus_master * * Description: * Enable bus mastering for device so it can perform PCI accesses * * Input Parameters: * dev - device * * Return value: * -EINVAL: error * OK: OK * ****************************************************************************/ int pci_enable_bus_master(FAR struct pci_dev_s *dev) { pci_set_cmd_bit(dev, PCI_CMD_BUS_MSTR); return OK; } /**************************************************************************** * Name: pci_disable_bus_master * * Description: * Disable bus mastering for device * * Input Parameters: * dev - device * * Return value: * -EINVAL: error * OK: OK * ****************************************************************************/ int pci_disable_bus_master(FAR struct pci_dev_s *dev) { pci_clear_cmd_bit(dev, PCI_CMD_BUS_MSTR); return OK; } /**************************************************************************** * Name: pci_bar_valid * * Description: * Determine in if the address in the BAR is valid * * Input Parameters: * dev - device * bar_id - bar number * * Return value: * -EINVAL: error * OK: OK * ****************************************************************************/ int pci_bar_valid(FAR struct pci_dev_s *dev, uint8_t bar_id) { uint32_t bar = dev->bus->ops->pci_cfg_read(dev, PCI_HEADER_NORM_BAR0 + (bar_id * 4), 4); if (bar == PCI_BAR_INVALID) { return -EINVAL; } return OK; } /**************************************************************************** * Name: pci_bar_is_64 * * Description: * Determine in if the bar address is 64 bit. If it is the address includes * the address in the next bar location. * * Input Parameters: * dev - device * bar_id - bar number * * Return value: * true: 64bit address * ****************************************************************************/ bool pci_bar_is_64(FAR struct pci_dev_s *dev, uint8_t bar_id) { uint32_t bar = dev->bus->ops->pci_cfg_read(dev, PCI_HEADER_NORM_BAR0 + (bar_id * 4), 4); /* Check that it is memory and not io port */ if ((bar & PCI_BAR_LAYOUT_MASK) != PCI_BAR_LAYOUT_MEM) { return false; } if (((bar & PCI_BAR_TYPE_MASK) >> PCI_BAR_TYPE_OFFSET) == PCI_BAR_TYPE_64) { return true; } return false; } /**************************************************************************** * Name: pci_bar_size * * Description: * Determine the size of the address space required by the BAR * * Input Parameters: * dev - device * bar_id - bar number * * Return value: * Size of address space * ****************************************************************************/ uint64_t pci_bar_size(FAR struct pci_dev_s *dev, uint8_t bar_id) { FAR const struct pci_bus_ops_s *dev_ops = dev->bus->ops; uint32_t bar; uint32_t size; uint64_t full_size; uint8_t bar_offset; bar_offset = PCI_HEADER_NORM_BAR0 + (bar_id * 4); bar = dev_ops->pci_cfg_read(dev, bar_offset, 4); /* Write all 1 to the BAR. We are looking for which bits will change */ dev_ops->pci_cfg_write(dev, bar_offset, 0xffffffff, 4); full_size = dev_ops->pci_cfg_read(dev, bar_offset, 4); /* Resore BAR to original values */ dev_ops->pci_cfg_write(dev, bar_offset, bar, 4); if (full_size == 0) { /* This is not a valid bar */ return 0; } if ((bar & PCI_BAR_LAYOUT_MASK) == PCI_BAR_LAYOUT_MEM) { full_size &= PCI_BAR_MEM_BASE_MASK; } else { full_size &= PCI_BAR_IO_BASE_MASK; } /* If it is 64 bit address check the next bar as well */ if (pci_bar_is_64(dev, bar_id)) { bar_offset += 4; bar = dev_ops->pci_cfg_read(dev, bar_offset, 4); dev_ops->pci_cfg_write(dev, bar_offset, 0xffffffff, 4); size = dev_ops->pci_cfg_read(dev, bar_offset, 4); dev_ops->pci_cfg_write(dev, bar_offset, bar, 4); full_size |= ((uint64_t)size << 32); } else { full_size |= (uint64_t)(0xffffffff) << 32; } return ~full_size + 1; } /**************************************************************************** * Name: pci_bar_addr * * Description: * Determine the size of the address space required by the BAR * * Input Parameters: * dev - device * bar_id - bar number * * Return value: * full bar address * ****************************************************************************/ uint64_t pci_bar_addr(FAR struct pci_dev_s *dev, uint8_t bar_id) { FAR const struct pci_bus_ops_s *dev_ops = dev->bus->ops; uint64_t addr; uint8_t bar_offset; bar_offset = PCI_HEADER_NORM_BAR0 + (bar_id * 4); addr = dev_ops->pci_cfg_read(dev, bar_offset, 4); if ((addr & PCI_BAR_LAYOUT_MASK) == PCI_BAR_LAYOUT_MEM) { addr &= PCI_BAR_MEM_BASE_MASK; } else { addr &= PCI_BAR_IO_BASE_MASK; } /* If it is 64 bit address check the next bar as well */ if (pci_bar_is_64(dev, bar_id)) { bar_offset += 4; addr |= (uint64_t)(dev_ops->pci_cfg_read(dev, bar_offset, 4)) << 32; } return addr; } /**************************************************************************** * Name: pci_dev_dump * * Description: * Dump the configuration information for the device * * Input Parameters: * dev - device * ****************************************************************************/ void pci_dev_dump(FAR struct pci_dev_s *dev) { FAR const struct pci_bus_ops_s *dev_ops = dev->bus->ops; uint8_t bar_mem_type = 0; uint8_t bar_id; uint32_t bar; uint64_t bar_size; uint64_t bar_addr; uint8_t cap_id; uint8_t cap_offset; uint32_t bdf; uint16_t vid; uint16_t pid; uint8_t header; uint8_t progif; uint8_t subclass; uint8_t class; uint8_t int_pin; uint8_t int_line; bdf = dev->bdf; vid = dev_ops->pci_cfg_read(dev, PCI_CONFIG_VENDOR, 2); pid = dev_ops->pci_cfg_read(dev, PCI_CONFIG_DEVICE, 2); header = dev_ops->pci_cfg_read(dev, PCI_CONFIG_HEADER_TYPE, 1); progif = dev_ops->pci_cfg_read(dev, PCI_CONFIG_PROG_IF, 1); subclass = dev_ops->pci_cfg_read(dev, PCI_CONFIG_SUBCLASS, 1); class = dev_ops->pci_cfg_read(dev, PCI_CONFIG_CLASS, 1); pciinfo("[%02x:%02x.%x] %04x:%04x\n", bdf >> 8, (bdf & 0xff) >> 3, bdf & 0x7, vid, pid); pciinfo("\ttype %02x Prog IF %02x Class %02x Subclass %02x\n", header, progif, class, subclass); cap_offset = dev_ops->pci_cfg_read(dev, PCI_HEADER_NORM_CAP, 1); while (cap_offset) { cap_id = dev_ops->pci_cfg_read(dev, cap_offset, 1); if (cap_id > PCI_CAP_ID_END) { pcierr("Invalid PCI Capability Found, Skipping. %d\n", cap_id); DEBUGPANIC(); break; } pciinfo("\tCAP %02x\n", cap_id); cap_offset = dev_ops->pci_cfg_read(dev, cap_offset + 1, 1); } if ((header & PCI_HEADER_TYPE_MASK) != PCI_HEADER_NORMAL) { return; } int_pin = dev_ops->pci_cfg_read(dev, PCI_HEADER_NORM_INT_PIN, 1); int_line = dev_ops->pci_cfg_read(dev, PCI_HEADER_NORM_INT_LINE, 1); pciinfo("\tINT Pin %02x Line %02x\n", int_pin, int_line); for (bar_id = 0; bar_id < PCI_BAR_CNT; bar_id++) { if (pci_bar_valid(dev, bar_id) != OK) { continue; } bar = dev_ops->pci_cfg_read(dev, PCI_HEADER_NORM_BAR0 + (bar_id * 4), 4); bar_size = pci_bar_size(dev, bar_id); bar_addr = pci_bar_addr(dev, bar_id); if ((bar & PCI_BAR_LAYOUT_MASK) == PCI_BAR_LAYOUT_MEM) { switch ((bar & PCI_BAR_TYPE_MASK) >> PCI_BAR_TYPE_OFFSET) { case PCI_BAR_TYPE_64: bar_mem_type = 64; break; case PCI_BAR_TYPE_32: bar_mem_type = 32; break; case PCI_BAR_TYPE_16: bar_mem_type = 16; break; default: bar_mem_type = 0; } pciinfo("\tBAR [%d] MEM %db range %p-%p (%p)\n", bar_id, bar_mem_type, bar_addr, bar_addr + bar_size - 1, bar_size); } else { pciinfo("\tBAR [%d] PIO range %p-%p (%p)\n", bar_id, bar_addr, bar_addr + bar_size - 1, bar_size); } /* Skip next bar if this one was 64bit */ if (bar_mem_type == 64) { bar_id++; } } }