nuttx/drivers/pci/pci.c
2024-01-25 09:09:30 -08:00

833 lines
24 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/pci/pci.h>
#include <nuttx/virt/qemu_pci.h>
/****************************************************************************
* 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++;
}
}
}