1380 lines
42 KiB
C
1380 lines
42 KiB
C
/*
|
|
* Copyright (C) 2012-2015 Apple Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Inc.
|
|
*/
|
|
|
|
#include <debug.h>
|
|
#include <drivers/pci.h>
|
|
#include <platform.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys.h>
|
|
#include <sys/menu.h>
|
|
#include "pci_private.h"
|
|
|
|
#define PDBG_REG DEBUG_SPEW
|
|
#define PDBG_SPEW DEBUG_SPEW
|
|
#define PDBG_PROBE DEBUG_SPEW
|
|
#define PDBG_MAP DEBUG_SPEW
|
|
|
|
pci_device_t pci_bridges[256];
|
|
|
|
void pci_init(void)
|
|
{
|
|
platform_register_pci_busses();
|
|
}
|
|
|
|
static void pci_register_bridge(pci_device_t bridge)
|
|
{
|
|
ASSERT(pci_bridges[bridge->secondary_bus] == NULL);
|
|
|
|
pci_bridges[bridge->secondary_bus] = bridge;
|
|
}
|
|
|
|
static void pci_unregister_bridge(pci_device_t bridge)
|
|
{
|
|
ASSERT(pci_bridges[bridge->secondary_bus] != NULL);
|
|
|
|
pci_bridges[bridge->secondary_bus] = NULL;
|
|
}
|
|
|
|
pci_device_t pci_create_host_bridge(const char *name, uint8_t bus_num, uint32_t memory_base, uint32_t memory_size, void *priv, pci_config_read_cb_t read_cb, pci_config_write_cb_t write_cb)
|
|
{
|
|
pci_device_t new_bridge;
|
|
|
|
new_bridge = calloc(sizeof(*new_bridge), 1);
|
|
|
|
strlcpy(new_bridge->name, name, sizeof(new_bridge->name));
|
|
new_bridge->secondary_bus = bus_num;
|
|
new_bridge->subordinate_bus = bus_num;
|
|
new_bridge->memory_base = memory_base;
|
|
new_bridge->memory_size = memory_size;
|
|
new_bridge->memory_allocated = 0;
|
|
new_bridge->config_read = read_cb;
|
|
new_bridge->config_write = write_cb;
|
|
new_bridge->bridge_priv = priv;
|
|
|
|
pci_register_bridge(new_bridge);
|
|
|
|
return new_bridge;
|
|
}
|
|
|
|
// When the host bridge's last child goes away, reset all of the
|
|
// allocations to 0, allowing us to reprobe under the host bridge
|
|
// again if needed
|
|
static void pci_reinitialize_host_bridge(pci_device_t host_bridge)
|
|
{
|
|
ASSERT(host_bridge->num_children == 0);
|
|
ASSERT(host_bridge->bridge == NULL);
|
|
|
|
host_bridge->subordinate_bus = host_bridge->secondary_bus;
|
|
host_bridge->memory_allocated = 0;
|
|
}
|
|
|
|
// 8 bit wide read from the given device/offset under the given bridge
|
|
// Function is assumed to be 0 (no multi-function devices)
|
|
static uint8_t pci_bridge_config_read8(pci_device_t bridge, uint8_t device_num, uint16_t offset)
|
|
{
|
|
uint8_t value;
|
|
uint32_t bdfo = PCI_BDFO(bridge->secondary_bus, device_num, 0, offset);
|
|
bridge->config_read(bridge->bridge_priv, &value, bdfo, 1);
|
|
|
|
dprintf(PDBG_REG, "pci(%02x:%02x:00): 8-bit config read 0x%04x -> 0x%02x\n",
|
|
bridge->secondary_bus, device_num, offset, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
// 16 bit wide read from the given device/offset under the given bridge
|
|
// Function is assumed to be 0 (no multi-function devices)
|
|
static uint16_t pci_bridge_config_read16(pci_device_t bridge, uint8_t device_num, uint16_t offset)
|
|
{
|
|
uint16_t value;
|
|
uint32_t bdfo = PCI_BDFO(bridge->secondary_bus, device_num, 0, offset);
|
|
bridge->config_read(bridge->bridge_priv, &value, bdfo, 2);
|
|
|
|
dprintf(PDBG_REG, "pci(%02x:%02x:00): 16-bit config read 0x%04x -> 0x%04x\n",
|
|
bridge->secondary_bus, device_num, offset, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
// 32 bit wide read from the given device/offset under the given bridge
|
|
// Function is assumed to be 0 (no multi-function devices)
|
|
static uint32_t pci_bridge_config_read32(pci_device_t bridge, uint8_t device_num, uint16_t offset)
|
|
{
|
|
uint32_t value;
|
|
uint32_t bdfo = PCI_BDFO(bridge->secondary_bus, device_num, 0, offset);
|
|
bridge->config_read(bridge->bridge_priv, &value, bdfo, 4);
|
|
|
|
dprintf(PDBG_REG, "pci(%02x:%02x:00): 32-bit config read 0x%04x -> 0x%08x\n",
|
|
bridge->secondary_bus, device_num, offset, value);
|
|
|
|
return value;
|
|
}
|
|
|
|
// 8 bit wide write to the given device/offset under the given bridge
|
|
// Function is assumed to be 0 (no multi-function devices)
|
|
static void pci_bridge_config_write8(pci_device_t bridge, uint8_t device_num, uint16_t offset, uint8_t value)
|
|
{
|
|
uint32_t bdfo = PCI_BDFO(bridge->secondary_bus, device_num, 0, offset);
|
|
|
|
dprintf(PDBG_REG, "pci(%02x:%02x:00): 8-bit config write 0x%04x <- 0x%02x\n",
|
|
bridge->secondary_bus, device_num, offset, value);
|
|
|
|
bridge->config_write(bridge->bridge_priv, &value, bdfo, 1);
|
|
}
|
|
|
|
// 16 bit wide write to the given device/offset under the given bridge
|
|
// Function is assumed to be 0 (no multi-function devices)
|
|
static void pci_bridge_config_write16(pci_device_t bridge, uint8_t device_num, uint16_t offset, uint16_t value)
|
|
{
|
|
uint32_t bdfo = PCI_BDFO(bridge->secondary_bus, device_num, 0, offset);
|
|
|
|
dprintf(PDBG_REG, "pci(%02x:%02x:00): 16-bit config write 0x%04x <- 0x%04x\n",
|
|
bridge->secondary_bus, device_num, offset, value);
|
|
|
|
bridge->config_write(bridge->bridge_priv, &value, bdfo, 2);
|
|
}
|
|
|
|
// 32 bit wide write to the given device/offset under the given bridge
|
|
// Function is assumed to be 0 (no multi-function devices)
|
|
static void pci_bridge_config_write32(pci_device_t bridge, uint8_t device_num, uint16_t offset, uint32_t value)
|
|
{
|
|
uint32_t bdfo = PCI_BDFO(bridge->secondary_bus, device_num, 0, offset);
|
|
|
|
dprintf(PDBG_REG, "pci(%02x:%02x:00): 32-bit config write 0x%04x <- 0x%08x\n",
|
|
bridge->secondary_bus, device_num, offset, value);
|
|
|
|
bridge->config_write(bridge->bridge_priv, &value, bdfo, 4);
|
|
}
|
|
|
|
pci_device_t pci_bridge_probe(pci_device_t bridge, uint8_t device_num, utime_t timeout)
|
|
{
|
|
pci_device_t new_dev;
|
|
utime_t start_time;
|
|
uint8_t bus_num;
|
|
uint32_t idreg;
|
|
uint16_t pcie_cap;
|
|
|
|
if (bridge->devices[device_num] != NULL)
|
|
return bridge->devices[device_num];
|
|
|
|
bus_num = bridge->secondary_bus;
|
|
|
|
dprintf(PDBG_PROBE, "pci(%02x:%02x:00): probe start\n", bus_num, device_num);
|
|
|
|
start_time = system_time();
|
|
do {
|
|
idreg = pci_bridge_config_read32(bridge, device_num, PCI_CONFIG_VENDOR_ID);
|
|
if ((idreg != 0xffffffff) && (idreg != 0xffff0001))
|
|
break;
|
|
} while (!time_has_elapsed(start_time, timeout));
|
|
|
|
if ((idreg == 0xffffffff) || (idreg == 0xffff0001)) {
|
|
dprintf(PDBG_PROBE, "pci(%02x:%02x:00): probe timeout\n", bus_num, device_num);
|
|
return NULL;
|
|
}
|
|
|
|
new_dev = calloc(sizeof(*new_dev), 1);
|
|
|
|
new_dev->bridge = bridge;
|
|
new_dev->device_num = device_num;
|
|
snprintf(new_dev->name, sizeof(new_dev->name), "%02x:%02x:%02x", bus_num, device_num, 0);
|
|
|
|
// it's safe to use the pci_config_read_XXX functions from here forwards on new_dev
|
|
|
|
new_dev->vendor_id = idreg & 0xffff;
|
|
new_dev->device_id = (idreg >> 16) & 0xffff;
|
|
|
|
idreg = pci_config_read32(new_dev, PCI_CONFIG_REVISION_ID);
|
|
new_dev->revision_id = (idreg & 0xff);
|
|
new_dev->class_code = ((idreg >> 8) & 0xffffff);
|
|
|
|
idreg = pci_config_read8(new_dev, PCI_CONFIG_HEADER_TYPE);
|
|
new_dev->header_type = idreg & 0x7f;
|
|
new_dev->multifunction = (idreg & 0x80) != 0;
|
|
if (new_dev->header_type != 0 && new_dev->header_type != 1) {
|
|
dprintf(DEBUG_CRITICAL, "PCI device has unknown header type %d\n", idreg);
|
|
dprintf(DEBUG_CRITICAL, "pci(%s): unknown header type %d\n", new_dev->name, idreg);
|
|
goto error;
|
|
}
|
|
|
|
dprintf(PDBG_PROBE, "pci(%s): probed id %04x:%04x rev %02x type %02x class %06x\n",
|
|
new_dev->name, new_dev->vendor_id, new_dev->device_id, new_dev->revision_id,
|
|
new_dev->header_type, new_dev->class_code);
|
|
|
|
if (new_dev->header_type == 0) {
|
|
new_dev->num_bars = 6;
|
|
} else {
|
|
new_dev->num_bars = 2;
|
|
// Bridges inherit the config read callbacks from their parents
|
|
new_dev->config_read = bridge->config_read;
|
|
new_dev->config_write = bridge->config_write;
|
|
new_dev->bridge_priv = bridge->bridge_priv;
|
|
// And a bridge's primary bus is its parent's secondary bus
|
|
new_dev->primary_bus = bus_num;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < new_dev->num_bars; i++) {
|
|
uint32_t bar;
|
|
uint32_t original;
|
|
uint32_t masked;
|
|
uint32_t type;
|
|
uint64_t bar_size;
|
|
bool is_64;
|
|
|
|
bar = PCI_CONFIG_BASE_ADDRESS0 + 4 * i;
|
|
|
|
original = pci_config_read32(new_dev, bar);
|
|
pci_config_write32(new_dev, bar, 0xffffffff);
|
|
masked = pci_config_read32(new_dev, bar);
|
|
pci_config_write32(new_dev, bar, original);
|
|
|
|
// 0 is unimplemented
|
|
if (masked == 0) {
|
|
dprintf(PDBG_SPEW, " bar %u unimplemented\n", i);
|
|
continue;
|
|
}
|
|
|
|
// We don't support I/O BARs
|
|
if ((masked & 1) != 0) {
|
|
dprintf(PDBG_SPEW, " bar %u I/O\n", i);
|
|
continue;
|
|
}
|
|
|
|
type = (masked & 0xf) >> 1;
|
|
// type 1 needs to be below 1 MiB, which we don't support, and type 3 is reserved
|
|
if (type == 1 || type == 3) {
|
|
dprintf(PDBG_SPEW, " bar %u unknown type %u\n", i, type);
|
|
continue;
|
|
}
|
|
|
|
// type 2 is 64-bit
|
|
if (type == 2) {
|
|
uint64_t masked2;
|
|
|
|
// 64-bit is only valid on even BARs
|
|
if (i % 2 != 0)
|
|
continue;
|
|
|
|
original = pci_config_read32(new_dev, bar + 4);
|
|
pci_config_write32(new_dev, bar + 4, 0xffffffff);
|
|
masked2 = pci_config_read32(new_dev, bar + 4);
|
|
pci_config_write32(new_dev, bar + 4, original);
|
|
|
|
masked2 <<= 32;
|
|
masked2 |= masked;
|
|
|
|
bar_size = ~(masked2 & ~0xfULL) + 1;
|
|
is_64 = true;
|
|
// Mark the next bar as being part of this one
|
|
new_dev->bar_64bit[i+1] = true;
|
|
} else {
|
|
bar_size = ~(masked & ~0xf) + 1;
|
|
is_64 = false;
|
|
}
|
|
|
|
new_dev->bar_size[i] = bar_size;
|
|
new_dev->bar_64bit[i] = is_64;
|
|
|
|
dprintf(PDBG_PROBE, " bar %u size 0x%llx%s\n", i, bar_size, is_64 ? " (64-bit)" : "");
|
|
|
|
// skip reading the next BAR because we just did it
|
|
if (is_64)
|
|
i++;
|
|
}
|
|
|
|
pcie_cap = pci_find_capability(new_dev, PCI_CAP_ID_PCIE);
|
|
if (pcie_cap != 0) {
|
|
uint16_t cap;
|
|
cap = pci_config_read16(new_dev, pcie_cap + PCI_PCIE_CAP_PCIE_CAPABILITIES);
|
|
new_dev->pcie_device_port_type = (cap >> 4) & 0xf;
|
|
} else {
|
|
new_dev->pcie_device_port_type = PCI_PCIE_TYPE_UNKNOWN;
|
|
}
|
|
|
|
bridge->devices[device_num] = new_dev;
|
|
bridge->num_children++;
|
|
|
|
return new_dev;
|
|
|
|
error:
|
|
free(new_dev);
|
|
return NULL;
|
|
}
|
|
|
|
// Called after pci_bridge_probe finds a device that's a bridge in order to set
|
|
// the new bridge's secondary and subordinate bus number registers and adjust the
|
|
// subordinate bus numbers up the chain so that the new bridge's secondary bus
|
|
// is visible to the host bridge
|
|
void pci_bridge_assign_secondary_bus(pci_device_t bridge)
|
|
{
|
|
pci_device_t parent;
|
|
uint8_t parent_subordinate;
|
|
|
|
ASSERT(pci_is_bridge(bridge));
|
|
// Can only be called once for a bridge
|
|
ASSERT(bridge->secondary_bus == 0);
|
|
ASSERT(bridge->subordinate_bus == 0);
|
|
// And can't be called on the host bridge
|
|
ASSERT(bridge->bridge != NULL);
|
|
|
|
parent = bridge->bridge;
|
|
parent_subordinate = parent->subordinate_bus;
|
|
|
|
// Oops, we're out of buses!
|
|
ASSERT(parent_subordinate != 255);
|
|
|
|
dprintf(PDBG_MAP, "pci(%s): setting bridge secondary bus 0x%02x\n",
|
|
parent->name, parent_subordinate + 1);
|
|
|
|
// Bridges start with just their secondary bus under them, more
|
|
// probing is needed before giving it control of more buses
|
|
bridge->secondary_bus = parent_subordinate + 1;
|
|
bridge->subordinate_bus = parent_subordinate + 1;
|
|
pci_config_write8(bridge, PCI_CONFIG_SECONDARY_BUS_NUMBER, bridge->secondary_bus);
|
|
pci_config_write8(bridge, PCI_CONFIG_SUBORDINATE_BUS_NUMBER, bridge->subordinate_bus);
|
|
|
|
// Walk the chain of bridges up to the host bridge, bumping everyone's
|
|
// subordinate bus number so that they can see the new bus
|
|
while(parent != NULL) {
|
|
// If the subordinate buses don't match going all the way up the chain,
|
|
// then someone is allocating buses out of order, which can lead to overlap
|
|
ASSERT(parent->subordinate_bus == parent_subordinate);
|
|
|
|
parent->subordinate_bus = parent_subordinate + 1;
|
|
|
|
// Host bridges don't have PCI2PCI bridge registers, but everyone else should
|
|
if (parent->bridge != NULL) {
|
|
dprintf(PDBG_MAP, "pci(%s): setting bridge bus range %02x:%02x\n",
|
|
parent->name, parent->secondary_bus, parent->subordinate_bus);
|
|
pci_config_write8(parent, PCI_CONFIG_SUBORDINATE_BUS_NUMBER, parent->subordinate_bus);
|
|
}
|
|
|
|
parent = parent->bridge;
|
|
}
|
|
|
|
// Now that we've claimed our secondary bus, register as its owner
|
|
pci_register_bridge(bridge);
|
|
}
|
|
|
|
void pci_bridge_serr_enable(pci_device_t bridge, bool enable)
|
|
{
|
|
uint16_t v = pci_config_read16(bridge, PCI_CONFIG_COMMAND);
|
|
|
|
if (enable)
|
|
v |= PCI_CMD_SERR_ENABLE;
|
|
else
|
|
v &= ~PCI_CMD_SERR_ENABLE;
|
|
|
|
pci_config_write16(bridge, PCI_CONFIG_COMMAND, v);
|
|
|
|
}
|
|
|
|
void pci_free(pci_device_t dev)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < sizeof(dev->devices) / sizeof(dev->devices[0]); i++) {
|
|
if (dev->devices[i] != NULL)
|
|
panic("Freeing PCI bridge \"%s\" before its %uth child \"%s\"",
|
|
dev->name, i, dev->devices[i]->name);
|
|
}
|
|
|
|
if (dev->bridge != NULL) {
|
|
dev->bridge->devices[dev->device_num] = NULL;
|
|
dev->bridge->num_children--;
|
|
// If we just killed the last child of the host bridge, reset the
|
|
// allocation trackers in the host bridge. This allows us to reconfigure
|
|
// the PCIe system by tearing down all devices and then reprobing from
|
|
// the host bridge down. We don't support more complicated reconfiguration.
|
|
if (dev->bridge->bridge == NULL && dev->bridge->num_children == 0) {
|
|
pci_reinitialize_host_bridge(dev->bridge);
|
|
}
|
|
|
|
}
|
|
|
|
if (pci_is_bridge(dev) && dev->bridge != NULL)
|
|
pci_unregister_bridge(dev);
|
|
|
|
free(dev);
|
|
}
|
|
|
|
uint8_t pci_config_read8(pci_device_t dev, uint16_t offset)
|
|
{
|
|
pci_device_t bridge;
|
|
bridge = dev->bridge;
|
|
ASSERT(bridge != NULL);
|
|
|
|
return pci_bridge_config_read8(bridge, dev->device_num, offset);
|
|
}
|
|
|
|
uint16_t pci_config_read16(pci_device_t dev, uint16_t offset)
|
|
{
|
|
pci_device_t bridge;
|
|
bridge = dev->bridge;
|
|
ASSERT(bridge != NULL);
|
|
|
|
return pci_bridge_config_read16(bridge, dev->device_num, offset);
|
|
}
|
|
|
|
uint32_t pci_config_read32(pci_device_t dev, uint16_t offset)
|
|
{
|
|
pci_device_t bridge;
|
|
bridge = dev->bridge;
|
|
ASSERT(bridge != NULL);
|
|
|
|
return pci_bridge_config_read32(bridge, dev->device_num, offset);
|
|
}
|
|
|
|
void pci_config_write8(pci_device_t dev, uint16_t offset, uint8_t value)
|
|
{
|
|
pci_device_t bridge;
|
|
bridge = dev->bridge;
|
|
ASSERT(bridge != NULL);
|
|
|
|
pci_bridge_config_write8(bridge, dev->device_num, offset, value);
|
|
}
|
|
|
|
void pci_config_write16(pci_device_t dev, uint16_t offset, uint16_t value)
|
|
{
|
|
pci_device_t bridge;
|
|
bridge = dev->bridge;
|
|
ASSERT(bridge != NULL);
|
|
|
|
pci_bridge_config_write16(dev->bridge, dev->device_num, offset, value);
|
|
}
|
|
|
|
void pci_config_write32(pci_device_t dev, uint16_t offset, uint32_t value)
|
|
{
|
|
pci_device_t bridge;
|
|
bridge = dev->bridge;
|
|
ASSERT(bridge != NULL);
|
|
|
|
pci_bridge_config_write32(bridge, dev->device_num, offset, value);
|
|
}
|
|
|
|
bool pci_is_bridge(pci_device_t dev)
|
|
{
|
|
return dev->header_type == 1;
|
|
}
|
|
|
|
const char *pci_get_name(pci_device_t dev)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
return dev->name;
|
|
}
|
|
|
|
void pci_set_name(pci_device_t dev, const char *name)
|
|
{
|
|
ASSERT(strlen(name) < sizeof(dev->name));
|
|
|
|
strlcpy(dev->name, name, sizeof(dev->name));
|
|
}
|
|
|
|
pci_device_t pci_get_bridge(pci_device_t dev)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
return dev->bridge;
|
|
}
|
|
|
|
uint16_t pci_get_vendor_id(pci_device_t dev)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
return dev->vendor_id;
|
|
}
|
|
|
|
uint16_t pci_get_device_id(pci_device_t dev)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
return dev->device_id;
|
|
}
|
|
|
|
uint32_t pci_get_class_code(pci_device_t dev)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
return dev->class_code;
|
|
}
|
|
|
|
uint8_t pci_get_revision_id(pci_device_t dev)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
return dev->revision_id;
|
|
}
|
|
|
|
uint8_t pci_get_header_type(pci_device_t dev)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
return dev->header_type;
|
|
}
|
|
|
|
void pci_memory_space_enable(pci_device_t dev, bool enable)
|
|
{
|
|
uint16_t cmd = pci_config_read16(dev, PCI_CONFIG_COMMAND);
|
|
|
|
if (enable)
|
|
cmd |= PCI_CMD_MEMORY_SPACE_ENABLE;
|
|
else
|
|
cmd &= ~PCI_CMD_MEMORY_SPACE_ENABLE;
|
|
|
|
pci_config_write16(dev, PCI_CONFIG_COMMAND, cmd);
|
|
}
|
|
|
|
void pci_bus_master_enable(pci_device_t dev, bool enable)
|
|
{
|
|
uint16_t cmd = pci_config_read16(dev, PCI_CONFIG_COMMAND);
|
|
|
|
if (enable)
|
|
cmd |= PCI_CMD_BUS_MASTER_ENABLE;
|
|
else
|
|
cmd &= ~PCI_CMD_BUS_MASTER_ENABLE;
|
|
|
|
pci_config_write16(dev, PCI_CONFIG_COMMAND, cmd);
|
|
}
|
|
|
|
uint64_t pci_get_bar_size(pci_device_t dev, unsigned bar_idx)
|
|
{
|
|
ASSERT(dev != NULL);
|
|
ASSERT(bar_idx < 6);
|
|
ASSERT(dev->header_type == 0 || bar_idx < 2);
|
|
return dev->bar_size[bar_idx];
|
|
}
|
|
|
|
// This function is recursive, but we shouldn't ever have a deep enough hierarchy to
|
|
// run into issues
|
|
// There is an assumption in this function that bridges never make allocations from their
|
|
// BARs. If that assumption is false, things could get dicey
|
|
static bool pci_bridge_expand_memory_space(pci_device_t bridge, uint64_t new_size)
|
|
{
|
|
pci_device_t parent;
|
|
uint64_t needed;
|
|
uint64_t parent_starting_allocation;
|
|
|
|
parent = bridge->bridge;
|
|
|
|
needed = new_size - bridge->memory_size;
|
|
|
|
if (parent != NULL)
|
|
dprintf(PDBG_MAP, "pci(%s): expanding bridge memory space to 0x%llx (0x%llx more)\n",
|
|
bridge->name, new_size, needed);
|
|
|
|
// Do a a sanity check to make sure the allocation amounts agree all
|
|
// the way up to the host bridge. If a child bridge's base + size isn't equal to
|
|
// its parent bridge's base + allocated, someone is doing allocations out
|
|
// of order, and we're hosed. Of course, if nothing has been allocated on
|
|
// this bridge, we're ok
|
|
if (parent != NULL) {
|
|
parent_starting_allocation = parent->memory_allocated;
|
|
|
|
if (bridge->memory_size != 0) {
|
|
uint64_t bridge_limit = bridge->memory_base + bridge->memory_size;
|
|
uint64_t parent_limit = parent->memory_base + parent->memory_allocated;
|
|
if (bridge_limit != parent_limit) {
|
|
ASSERT(false /* out of order allocations */);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!pci_bridge_expand_memory_space(parent, parent->memory_size + needed))
|
|
return false;
|
|
} else if (parent == NULL) {
|
|
// If we're at the host bridge, make sure it still has addresses left to vend
|
|
if (bridge->memory_allocated + needed > bridge->memory_size) {
|
|
ASSERT(false /* PCI address space exhausted */);
|
|
return false;
|
|
}
|
|
|
|
// We're good, update the host bridge's counter of allocated memory
|
|
bridge->memory_allocated = new_size;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Recursion above finished, we're now unwinding the stack. Everything looks reasonable,
|
|
// so go ahead and expand the memory space size. We'll be doing this in reverse order from
|
|
// the checks above, doing the host bridge first and walking back down the hierarchy.
|
|
// By modifying parent->allocated and bridge->size, we make sure that we don't ever change
|
|
// the size of the host bridge's memory_size (which is not PCI-configurable).
|
|
// It'll also make sure that there's a chunk that's unallocated in the deepest bridge (the
|
|
// one for which this request was made to begin with)
|
|
|
|
// But first, if we've never had an allocation, we need to set our base
|
|
if (bridge->memory_base == 0) {
|
|
bridge->memory_base = parent->memory_base + parent_starting_allocation;
|
|
dprintf(PDBG_MAP, "pci(%s): setting bridge memory base 0x%x\n",
|
|
bridge->name, bridge->memory_base);
|
|
pci_config_write16(bridge, PCI_CONFIG_MEMORY_BASE, (bridge->memory_base >> 16) & ~0xfU);
|
|
}
|
|
|
|
// Now update our limit to reflect the new size
|
|
parent->memory_allocated += needed;
|
|
bridge->memory_size += needed;
|
|
|
|
dprintf(PDBG_MAP, "pci(%s): setting bridge memory range %08x:%08x\n",
|
|
parent->name, bridge->memory_base, bridge->memory_base + bridge->memory_size - 1);
|
|
|
|
pci_config_write16(bridge, PCI_CONFIG_MEMORY_LIMIT,
|
|
((bridge->memory_base + bridge->memory_size - 1) >> 16) & ~0xfU);
|
|
|
|
// Disable the prefetchable memory base/limit bridge memory range.
|
|
pci_config_write16(bridge, PCI_CONFIG_PREFETCHABLE_BASE, 0xFFF0);
|
|
pci_config_write16(bridge, PCI_CONFIG_PREFETCHABLE_LIMIT, 0x0000);
|
|
|
|
return true;
|
|
}
|
|
|
|
uintptr_t pci_map_bar(pci_device_t dev, unsigned bar_idx)
|
|
{
|
|
pci_device_t bridge;
|
|
uint64_t pci_addr;
|
|
uintptr_t host_addr;
|
|
uint64_t bar_size;
|
|
uint64_t allocated;
|
|
uint32_t bar_offset;
|
|
|
|
ASSERT(bar_idx < dev->num_bars);
|
|
// 64-bit BARs can only be even-numbered
|
|
if (dev->bar_64bit[bar_idx] && bar_idx % 2 != 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci(%s): trying to map odd portion %u of 64-bit bar\n",
|
|
dev->name, bar_idx);
|
|
return 0;
|
|
}
|
|
|
|
// BARs can only be mapped once
|
|
ASSERT(dev->bar_host_addr[bar_idx] == 0);
|
|
|
|
// Make sure the BAR is implemented
|
|
bar_size = dev->bar_size[bar_idx];
|
|
if (bar_size == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci(%s): trying to map unimplemented bar %u\n",
|
|
dev->name, bar_idx);
|
|
return 0;
|
|
}
|
|
|
|
dprintf(PDBG_MAP, "pci(%s): trying to map bar %u size 0x%llx\n",
|
|
dev->name, bar_idx, bar_size);
|
|
|
|
bridge = dev->bridge;
|
|
|
|
// Figure out how much of the bridge's allocation is used. BARs mappings need to
|
|
// be aligned to their size, so if the allocation isn't a multiple of this mapping's
|
|
// size, we'll need to mark a bunch of address space as used to make us aligned again
|
|
allocated = bridge->memory_allocated;
|
|
if (((bar_size - 1) & allocated) != 0)
|
|
allocated += bar_size - (allocated & (bar_size - 1));
|
|
|
|
// Can we fit this BAR into the space we already have allocated?
|
|
if (allocated + bar_size > bridge->memory_size) {
|
|
uint64_t bridge_allocation;
|
|
// Putain, we are out of space. We're going to need to ask our ancestors for
|
|
// more space. Bridges allocate in 1MB chunks, so start by rounding up
|
|
bridge_allocation = allocated + bar_size;
|
|
bridge_allocation = (bridge_allocation + 0x100000 - 1) & ~(0x100000ULL - 1);
|
|
|
|
dprintf(PDBG_MAP, "pci(%s): 0x%x memory space, 0x%x used (0x%llx aligned)\n",
|
|
bridge->name, bridge->memory_size, bridge->memory_allocated, allocated);
|
|
|
|
// Ask for our memory space size to be bumped up
|
|
if (!pci_bridge_expand_memory_space(bridge, bridge_allocation)) {
|
|
dprintf(DEBUG_CRITICAL, "pci(%s): couldn't allocate bridge memory space\n", bridge->name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// The bridge has enough memory space, so we'll tack this BAR at the end
|
|
// of the allocated region
|
|
pci_addr = bridge->memory_base + allocated;
|
|
host_addr = platform_map_pci_to_host_addr(pci_addr);
|
|
// And then mark the addresses we used as claimed
|
|
bridge->memory_allocated = allocated + bar_size;
|
|
|
|
dev->bar_pci_addr[bar_idx] = pci_addr;
|
|
dev->bar_host_addr[bar_idx] = host_addr;
|
|
|
|
dprintf(PDBG_MAP, "pci(%s): mapping bar %u at 0x%llx (host %p) size 0x%llx\n",
|
|
dev->name, bar_idx, pci_addr, (void *)host_addr, bar_size);
|
|
|
|
bar_offset = PCI_CONFIG_BASE_ADDRESS0 + (4 * bar_idx);
|
|
pci_config_write32(dev, bar_offset, (uint32_t)pci_addr);
|
|
if (dev->bar_64bit[bar_idx])
|
|
pci_config_write32(dev, bar_offset + 4, (uint32_t)(pci_addr >> 32));
|
|
|
|
return host_addr;
|
|
}
|
|
|
|
uint16_t pci_find_capability(pci_device_t dev, uint16_t capability_id)
|
|
{
|
|
uint16_t status;
|
|
uint8_t cap_pointer;
|
|
uint16_t id_and_next;
|
|
uint8_t this_cap_id;
|
|
|
|
// See if we've already cached the offset for a common capability
|
|
if (capability_id == PCI_CAP_ID_PM && dev->pm_cap_offset != 0)
|
|
return dev->pm_cap_offset;
|
|
if (capability_id == PCI_CAP_ID_PCIE && dev->pcie_cap_offset != 0)
|
|
return dev->pcie_cap_offset;
|
|
|
|
if (dev->first_cap_offset == 0) {
|
|
status = pci_config_read16(dev, PCI_CONFIG_STATUS);
|
|
if (status == 0xFFFF || (status & PCI_STATUS_CAPABILITIES_LIST) == 0) {
|
|
dprintf(DEBUG_INFO, "pci(%s): no capability list\n", dev->name);
|
|
return 0;
|
|
}
|
|
|
|
cap_pointer = pci_config_read8(dev, PCI_CONFIG_CAPABILITIES_PTR);
|
|
cap_pointer &= 0xFC;
|
|
|
|
if (cap_pointer >= 0x40 && cap_pointer != 0xfc)
|
|
dev->first_cap_offset = cap_pointer;
|
|
} else {
|
|
cap_pointer = dev->first_cap_offset;
|
|
}
|
|
|
|
while (cap_pointer >= 0x40 && cap_pointer != 0xfc) {
|
|
id_and_next = pci_config_read16(dev, cap_pointer);
|
|
this_cap_id = (id_and_next & 0xFF);
|
|
|
|
// Cache the offsets for common capabilities
|
|
if (this_cap_id == PCI_CAP_ID_PM)
|
|
dev->pm_cap_offset = cap_pointer;
|
|
else if (this_cap_id == PCI_CAP_ID_PCIE)
|
|
dev->pcie_cap_offset = cap_pointer;
|
|
|
|
if (this_cap_id == capability_id)
|
|
return cap_pointer;
|
|
|
|
cap_pointer = (id_and_next >> 8) & 0xFC;
|
|
}
|
|
|
|
dprintf(DEBUG_INFO, "pci(%s): capability 0x%02x not found\n", dev->name, capability_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint16_t pci_find_extended_capability(pci_device_t dev, uint16_t capability_id, uint8_t min_version)
|
|
{
|
|
uint32_t header;
|
|
uint16_t header_id;
|
|
uint8_t header_version;
|
|
uint16_t header_pointer;
|
|
uint16_t next_pointer;
|
|
|
|
header_pointer = 0x100;
|
|
|
|
do {
|
|
header = pci_config_read32(dev, header_pointer);
|
|
if (header == 0 || header == 0xFFFFFFFF)
|
|
break;
|
|
|
|
header_id = header & 0xFFFF;
|
|
header_version = (header >> 16) & 0xF;
|
|
next_pointer = (header >> 20) & 0xFFC;
|
|
|
|
dprintf(PDBG_PROBE, "found ecap %04x at %04x next %04x\n", header_id, header_pointer, next_pointer);
|
|
|
|
if (header_id == capability_id && header_version >= min_version)
|
|
return header_pointer;
|
|
|
|
header_pointer = next_pointer;
|
|
} while (header_pointer != 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void pci_set_powerstate(pci_device_t dev, uint8_t powerstate)
|
|
{
|
|
uint16_t pme_cap;
|
|
uint16_t pmcsr;
|
|
|
|
pme_cap = pci_find_capability(dev, PCI_CAP_ID_PM);
|
|
if (pme_cap != 0) {
|
|
uint32_t offset = pme_cap + PCI_PM_CAP_PMCSR;
|
|
|
|
pmcsr = pci_config_read16(dev, offset);
|
|
pmcsr = (pmcsr & ~3) | (powerstate & 3);
|
|
pci_config_write16(dev, offset, pmcsr);
|
|
}
|
|
}
|
|
|
|
bool pci_enable_pcie_ltr(pci_device_t dev)
|
|
{
|
|
uint32_t pcie_offset;
|
|
uint32_t ltr_offset;
|
|
uint32_t cap2 = 0;
|
|
uint32_t control2;
|
|
|
|
// The host bridge doesn't have any associated PCIe config space; it's just
|
|
// an abstraction for the hardware that translates fabric requests into PCIe
|
|
// requests. Return true here to end the recursion below
|
|
if (dev->bridge == NULL)
|
|
return true;
|
|
|
|
pcie_offset = pci_find_capability(dev, PCI_CAP_ID_PCIE);
|
|
if (pcie_offset == 0) {
|
|
dprintf(DEBUG_INFO, "pci: %s: can't enable LTR on non-PCIe endpoint\n", dev->name);
|
|
return false;
|
|
}
|
|
|
|
cap2 = pci_config_read32(dev, pcie_offset + PCI_PCIE_CAP_DEVICE_CAPABILITIES2);
|
|
if ((cap2 & (1 << 11)) == 0) {
|
|
dprintf(DEBUG_INFO, "pci: %s: LTR not supported\n", dev->name);
|
|
return false;
|
|
}
|
|
|
|
if (dev->pcie_device_port_type == PCI_PCIE_TYPE_ENDPOINT ||
|
|
dev->pcie_device_port_type == PCI_PCIE_TYPE_UPSTREAM) {
|
|
ltr_offset = pci_find_extended_capability(dev, PCI_EXT_CAP_ID_LTR, 1);
|
|
if (ltr_offset == 0) {
|
|
dprintf(DEBUG_INFO, "pci: %s: LTR capability not found\n", dev->name);
|
|
return false;
|
|
}
|
|
} else {
|
|
ltr_offset = 0;
|
|
}
|
|
|
|
if (!pci_enable_pcie_ltr(dev->bridge))
|
|
return false;
|
|
|
|
control2 = pci_config_read32(dev, pcie_offset + PCI_PCIE_CAP_DEVICE_CONTROL2);
|
|
control2 |= (1 << 10);
|
|
pci_config_write32(dev, pcie_offset + PCI_PCIE_CAP_DEVICE_CONTROL2, control2);
|
|
|
|
// Set a reasonable default for the max LTR value. As far as we can tell, this
|
|
// is a pretty meaningless parameter, so we're just hardcoding it for now until
|
|
// there's an actual reason to set it to something specific. This capability
|
|
// is only present in ports that face upstream
|
|
if (ltr_offset != 0) {
|
|
// scale of 32^4 = 1,048,576 ns and value of 8 works out to roughly 8 ms
|
|
// See PCIe r3.0 section 6.18 and section 7.25
|
|
uint16_t max_latency = 8 | (4 << 10);
|
|
pci_config_write16(dev, ltr_offset + PCI_LTR_CAP_MAX_SNOOP_LATENCY, max_latency);
|
|
pci_config_write16(dev, ltr_offset + PCI_LTR_CAP_MAX_NO_SNOOP_LATENCY, max_latency);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void pci_enable_pcie_aspm(pci_device_t dev)
|
|
{
|
|
pci_device_t bridge_dev;
|
|
uint32_t bridge_pcie_cap;
|
|
uint32_t endpoint_pcie_cap;
|
|
uint32_t bridge_link_cap;
|
|
uint32_t endpoint_link_cap;
|
|
uint32_t aspm_support;
|
|
uint16_t bridge_link_control;
|
|
uint16_t endpoint_link_control;
|
|
|
|
bridge_dev = dev->bridge;
|
|
|
|
bridge_pcie_cap = pci_find_capability(bridge_dev, PCI_CAP_ID_PCIE);
|
|
endpoint_pcie_cap = pci_find_capability(dev, PCI_CAP_ID_PCIE);
|
|
|
|
if (bridge_pcie_cap == 0 || endpoint_pcie_cap == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: Can't enable ASPM, PCIe capability not found\n", dev->name);
|
|
return;
|
|
}
|
|
|
|
bridge_link_cap = pci_config_read32(bridge_dev, bridge_pcie_cap + PCI_PCIE_CAP_LINK_CAPABILITIES);
|
|
endpoint_link_cap = pci_config_read32(dev, endpoint_pcie_cap + PCI_PCIE_CAP_LINK_CAPABILITIES);
|
|
|
|
// <rdar://problem/20988960> Force enable ASPM+L1SS when S3E doesn't advertise support
|
|
// If we ever had to support endpoints other than S3E, we could conditionalize this on the
|
|
// S3E VID/PID. For now, this hack is good enough.
|
|
endpoint_link_cap |= (0x2 << 10);
|
|
|
|
aspm_support = (bridge_link_cap >> 10) & (endpoint_link_cap >> 10) & 0x3;
|
|
|
|
// ASPM must be supported at both ends of the link
|
|
if (aspm_support == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: Can't enable ASPM (bridge cap 0x%08x endpoint cap 0x%08x)\n",
|
|
dev->name, bridge_link_cap, endpoint_link_cap);
|
|
return;
|
|
}
|
|
|
|
if (aspm_support) {
|
|
// PCIe requires us to enable L1 in the upstream port before enabling it downstream
|
|
bridge_link_control = pci_config_read16(bridge_dev, bridge_pcie_cap + PCI_PCIE_CAP_LINK_CONTROL);
|
|
bridge_link_control &= ~3;
|
|
bridge_link_control |= aspm_support;
|
|
pci_config_write16(bridge_dev, bridge_pcie_cap + PCI_PCIE_CAP_LINK_CONTROL, bridge_link_control);
|
|
|
|
endpoint_link_control = pci_config_read16(dev, endpoint_pcie_cap + PCI_PCIE_CAP_LINK_CONTROL);
|
|
endpoint_link_control &= ~3;
|
|
endpoint_link_control |= aspm_support;
|
|
pci_config_write16(dev, endpoint_pcie_cap + PCI_PCIE_CAP_LINK_CONTROL, endpoint_link_control);
|
|
}
|
|
}
|
|
|
|
void pci_set_pcie_common_clock(pci_device_t dev)
|
|
{
|
|
pci_device_t bridge;
|
|
uint32_t bridge_offset;
|
|
uint32_t dev_offset;
|
|
uint32_t control;
|
|
|
|
bridge = dev->bridge;
|
|
if (bridge == NULL || bridge->bridge == NULL) {
|
|
dprintf(DEBUG_CRITICAL, "pci: can't enable common clock on root port bridge\n");
|
|
return;
|
|
}
|
|
|
|
bridge_offset = pci_find_capability(bridge, PCI_CAP_ID_PCIE);
|
|
if (bridge_offset == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: no express capability upstream\n");
|
|
return;
|
|
}
|
|
dev_offset = pci_find_capability(dev, PCI_CAP_ID_PCIE);
|
|
if (dev_offset == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: no express capability on endpoint\n");
|
|
return;
|
|
}
|
|
|
|
control = pci_config_read16(dev, dev_offset + PCI_PCIE_CAP_LINK_CONTROL);
|
|
control |= (1 << 6);
|
|
pci_config_write16(dev, dev_offset + PCI_PCIE_CAP_LINK_CONTROL, control);
|
|
|
|
control = pci_config_read16(bridge, bridge_offset + PCI_PCIE_CAP_LINK_CONTROL);
|
|
control |= (1 << 6);
|
|
pci_config_write16(bridge, bridge_offset + PCI_PCIE_CAP_LINK_CONTROL, control);
|
|
}
|
|
|
|
void pci_enable_pcie_cpm(pci_device_t dev)
|
|
{
|
|
uint32_t pcie_cap;
|
|
uint16_t link_control;
|
|
|
|
if (dev->bridge == NULL || dev->bridge->bridge == NULL) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: can't enable CPM on root port bridge\n", dev->name);
|
|
return;
|
|
}
|
|
|
|
pcie_cap = pci_find_capability(dev, PCI_CAP_ID_PCIE);
|
|
|
|
if (pcie_cap == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: Can't enable CPM, PCIe capability not found\n", dev->name);
|
|
return;
|
|
}
|
|
|
|
link_control = pci_config_read16(dev, pcie_cap + PCI_PCIE_CAP_LINK_CONTROL);
|
|
link_control |= (1 << 8);
|
|
pci_config_write16(dev, pcie_cap + PCI_PCIE_CAP_LINK_CONTROL, link_control);
|
|
}
|
|
|
|
void pci_enable_pcie_l1ss(pci_device_t dev)
|
|
{
|
|
pci_device_t bridge;
|
|
uint32_t bridge_offset;
|
|
uint32_t dev_offset;
|
|
uint32_t bridge_cap;
|
|
uint32_t dev_cap;
|
|
uint32_t supported;
|
|
uint32_t dev_t_power_on_scale;
|
|
uint32_t dev_t_power_on_value;
|
|
uint32_t bridge_t_common_mode;
|
|
uint32_t dev_t_common_mode;
|
|
uint32_t t_common_mode;
|
|
uint32_t ltr_threshold;
|
|
uint32_t bridge_control1;
|
|
uint32_t bridge_control2;
|
|
uint32_t dev_control1;
|
|
uint32_t dev_control2;
|
|
|
|
if (dev->pcie_device_port_type != PCI_PCIE_TYPE_ENDPOINT &&
|
|
dev->pcie_device_port_type != PCI_PCIE_TYPE_UPSTREAM) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: can't get L1SS parameters for non-upstream port\n", dev->name);
|
|
return;
|
|
}
|
|
|
|
bridge = dev->bridge;
|
|
if (bridge == NULL || bridge->bridge == NULL) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: can't enable L1SS on root port bridge\n", dev->name);
|
|
return;
|
|
}
|
|
|
|
bridge_offset = pci_find_extended_capability(bridge, PCI_EXT_CAP_ID_L1SS, 1);
|
|
if (bridge_offset == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: no L1SS capability upstream\n", dev->name);
|
|
return;
|
|
}
|
|
dev_offset = pci_find_extended_capability(dev, PCI_EXT_CAP_ID_L1SS, 1);
|
|
if (dev_offset == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: no L1SS capability on endpoint\n", dev->name);
|
|
return;
|
|
}
|
|
|
|
// Make sure upstream and downstream support L1 PM substates, and that they have
|
|
// supported substates in common
|
|
bridge_cap = pci_config_read32(bridge, bridge_offset + PCI_L1SS_CAP_CAPABILITIES);
|
|
dev_cap = pci_config_read32(dev, dev_offset + PCI_L1SS_CAP_CAPABILITIES);
|
|
|
|
// <rdar://problem/20988960> Force enable ASPM+L1SS when S3E doesn't advertise support
|
|
// If we ever had to support endpoints other than S3E, we could conditionalize this on the
|
|
// S3E VID/PID. For now, this hack is good enough.
|
|
dev_cap |= 0x1f;
|
|
|
|
supported = bridge_cap & dev_cap & 0x1f;
|
|
|
|
// L1 substates ECN, section 7.xx.3: "For compatibility with possible future extensions,
|
|
// software must not enable L1 PM Substates unless the L1 PM Substates Supported bit in
|
|
// the L1 PM Substates Capabilities Register is Set."
|
|
// So, we need to make sure that bit 4 is set on both ends, and that some common bits
|
|
// are set in bits 0-3
|
|
if ((supported & 0x10) == 0 || (supported & 0xf) == 0) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: no common L1SS support (dev %08x upstream %08x)\n",
|
|
dev->name, dev_cap, bridge_cap);
|
|
return;
|
|
}
|
|
|
|
// Bit 4 just says if L1 is supported in general, but bits 0-3 map directly to control register bits
|
|
supported &= 0xf;
|
|
|
|
dev_t_power_on_scale = (dev_cap >> 16) & 0x3;
|
|
dev_t_power_on_value = (dev_cap >> 19) & 0x1f;
|
|
// t_power_on scale needs to be valid on endpoint
|
|
if (dev_t_power_on_scale == 3) {
|
|
dprintf(DEBUG_CRITICAL, "pci: %s: invalid L1SS t_power_on (dev %08x upstream %08x)\n",
|
|
dev->name, dev_cap, bridge_cap);
|
|
return;
|
|
}
|
|
|
|
// The root complex value isn't correct, so we hardcode the t_common_mode value in iBoot/SecureROM
|
|
bridge_t_common_mode = platform_get_pcie_l1ss_t_common_mode();
|
|
dev_t_common_mode = (dev_cap >> 8) & 0xff;
|
|
t_common_mode = __max(bridge_t_common_mode, dev_t_common_mode);
|
|
|
|
ltr_threshold = platform_get_pcie_l1ss_ltr_threshold();
|
|
|
|
// L1SS must be disabled before setting timing parameters
|
|
// Disable happens endpoint-first
|
|
dev_control1 = pci_config_read32(dev, dev_offset + PCI_L1SS_CAP_CONTROL1);
|
|
dev_control1 &= ~0xf;
|
|
pci_config_write32(dev, dev_offset + PCI_L1SS_CAP_CONTROL1, dev_control1);
|
|
bridge_control1 = pci_config_read32(bridge, bridge_offset + PCI_L1SS_CAP_CONTROL1);
|
|
bridge_control1 &= ~0xf;
|
|
pci_config_write32(bridge, bridge_offset + PCI_L1SS_CAP_CONTROL1, bridge_control1);
|
|
|
|
// set control2 on bridge with t_power_on
|
|
bridge_control2 = pci_config_read32(bridge, bridge_offset + PCI_L1SS_CAP_CONTROL2);
|
|
bridge_control2 &= ~((0x3 << 0) | (0x1f << 3));
|
|
// t_power_on_scale from endpoint
|
|
bridge_control2 |= dev_t_power_on_scale;
|
|
bridge_control2 |= dev_t_power_on_value << 3;
|
|
pci_config_write32(bridge, bridge_offset + PCI_L1SS_CAP_CONTROL2, bridge_control2);
|
|
|
|
// set control2 on endpoint with t_power_on
|
|
dev_control2 = pci_config_read32(dev, dev_offset + PCI_L1SS_CAP_CONTROL2);
|
|
dev_control2 &= ~((0x3 << 0) | (0x1f << 3));
|
|
// t_power_on_scale from endpoint
|
|
dev_control2 |= dev_t_power_on_scale;
|
|
dev_control2 |= dev_t_power_on_value << 3;
|
|
pci_config_write32(dev, dev_offset + PCI_L1SS_CAP_CONTROL2, dev_control2);
|
|
|
|
// set control 1 on root port with t_common_mode, enables and LTR threshold
|
|
// enables always need to happen upstream first (opposite of disable)
|
|
bridge_control1 |= supported;
|
|
if ((supported & ((1 << 3) | (1 << 1))) != 0) {
|
|
// t_common_mode and LTR threshold only applies when L1.2 is supported
|
|
bridge_control1 &= ~((0xff << 8) | (0x3ff << 16) | (0x7 << 29));
|
|
bridge_control1 |= t_common_mode << 8;
|
|
|
|
// set LTR threshold using a 1024 ns scale
|
|
bridge_control1 |= ltr_threshold << 16;
|
|
bridge_control1 |= 2 << 29;
|
|
}
|
|
pci_config_write32(bridge, bridge_offset + PCI_L1SS_CAP_CONTROL1, bridge_control1);
|
|
|
|
// set control 1 on endpoint with enables and LTR threshold
|
|
dev_control1 |= supported;
|
|
if ((supported & ((1 << 3) | (1 << 1))) != 0) {
|
|
// LTR threshold only applies when L1.2 is supported
|
|
dev_control1 &= ~((0x3ff << 16) | (0x7 << 29));
|
|
|
|
// set LTR threshold using a 1024 ns scale
|
|
dev_control1 |= ltr_threshold << 16;
|
|
dev_control1 |= 2 << 29;
|
|
}
|
|
pci_config_write32(dev, dev_offset + PCI_L1SS_CAP_CONTROL1, dev_control1);
|
|
}
|
|
|
|
#if DEBUG_BUILD
|
|
static void pci_explore(pci_device_t bridge, bool map_bars)
|
|
{
|
|
pci_device_t dev;
|
|
|
|
ASSERT(bridge != NULL);
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
dev = pci_bridge_probe(bridge, i, 0);
|
|
if (dev == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (map_bars) {
|
|
for (int j = 0; j < dev->num_bars; j++) {
|
|
if (dev->bar_size[j] != 0 && dev->bar_host_addr[j] == 0) {
|
|
pci_map_bar(dev, j);
|
|
}
|
|
|
|
if (dev->bar_64bit[j])
|
|
j++;
|
|
}
|
|
}
|
|
|
|
if (pci_is_bridge(dev)) {
|
|
if (dev->secondary_bus == 0)
|
|
pci_bridge_assign_secondary_bus(dev);
|
|
pci_explore(dev, map_bars);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pci_show_device(pci_device_t dev)
|
|
{
|
|
if (dev->bridge == NULL) {
|
|
printf("Host bridge\n");
|
|
return;
|
|
}
|
|
|
|
printf("Device %s (%02x:%02x:%02x)\n", dev->name, dev->bridge->secondary_bus, dev->device_num, 0);
|
|
|
|
printf(" Vendor:%04x Device:%04x Revision:%02x ClassCode:%06x Type:%02x\n",
|
|
dev->vendor_id, dev->device_id, dev->revision_id, dev->class_code, dev->header_type);
|
|
|
|
if (pci_is_bridge(dev)) {
|
|
printf(" PriBus:%02x SecBus:%02x SubBus:%02x\n",
|
|
dev->primary_bus, dev->secondary_bus, dev->subordinate_bus);
|
|
printf(" MemoryBase:%08x MemoryLimit:%08x (FIXME)\n", // FIXME
|
|
dev->memory_base, dev->memory_base + dev->memory_size - 1);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < dev->num_bars; i++) {
|
|
const char *format_str;
|
|
|
|
if (dev->bar_size[i] == 0)
|
|
continue;
|
|
if (dev->bar_64bit[i] && i % 2 != 0)
|
|
continue;
|
|
|
|
if (dev->bar_64bit[i])
|
|
format_str = " BAR%u MEM %016llx/%016llx (host 0x%llx)\n";
|
|
else
|
|
format_str = " BAR%u MEM %08llx/%08llx (host 0x%llx)\n";
|
|
|
|
printf(format_str, i, dev->bar_pci_addr[i], dev->bar_size[i], dev->bar_host_addr[i]);
|
|
}
|
|
}
|
|
|
|
static void pci_list_capabilities(pci_device_t dev)
|
|
{
|
|
uint32_t idreg;
|
|
uint16_t status;
|
|
uint8_t cap_pointer;
|
|
uint16_t id_and_next;
|
|
|
|
idreg = pci_config_read32(dev, PCI_CONFIG_VENDOR_ID);
|
|
if (idreg == 0xffffffff || idreg == 0xffff0001) {
|
|
printf("Device not present\n");
|
|
return;
|
|
}
|
|
|
|
status = pci_config_read16(dev, PCI_CONFIG_STATUS);
|
|
if ((status & (1 << 4)) == 0) {
|
|
printf("Device has no capabilties list (status 0x%04x)\n", status);
|
|
return;
|
|
}
|
|
|
|
cap_pointer = pci_config_read8(dev, PCI_CONFIG_CAPABILITIES_PTR);
|
|
cap_pointer &= 0xFC;
|
|
|
|
while (cap_pointer >= 0x40) {
|
|
id_and_next = pci_config_read16(dev, cap_pointer);
|
|
|
|
printf(" Capability 0x%02x Offset:0x%02x\n", id_and_next & 0xFF, cap_pointer);
|
|
|
|
cap_pointer = (id_and_next >> 8) & 0xFC;
|
|
}
|
|
}
|
|
|
|
static void pci_list_extended_capabilities(pci_device_t dev)
|
|
{
|
|
uint32_t idreg;
|
|
uint32_t header;
|
|
uint16_t header_id;
|
|
uint8_t header_version;
|
|
uint16_t header_pointer;
|
|
|
|
idreg = pci_config_read32(dev, PCI_CONFIG_VENDOR_ID);
|
|
if (idreg == 0xffffffff || idreg == 0xffff0001) {
|
|
printf("Device not present\n");
|
|
return;
|
|
}
|
|
|
|
header_pointer = 0x100;
|
|
|
|
do {
|
|
header = pci_config_read32(dev, header_pointer);
|
|
if (header == 0 || header == 0xFFFFFFFF)
|
|
break;
|
|
|
|
header_id = header & 0xFFFF;
|
|
header_version = (header >> 16) & 0xF;
|
|
|
|
printf(" Extended Capability 0x%04x Ver:0x%02x Offset:0x%04x\n",
|
|
header_id, header_version, header_pointer);
|
|
|
|
header_pointer = (header >> 20) & 0xFFC;
|
|
} while (header_pointer != 0);
|
|
}
|
|
|
|
static void pci_list(bool verbose)
|
|
{
|
|
pci_device_t dev;
|
|
|
|
for (int i = 0; i < 256; i++) {
|
|
if (pci_bridges[i] == NULL)
|
|
continue;
|
|
for(int j = 0; j < 32; j++) {
|
|
dev = pci_bridges[i]->devices[j];
|
|
if (dev != NULL) {
|
|
pci_show_device(dev);
|
|
if (verbose) {
|
|
pci_list_capabilities(dev);
|
|
pci_list_extended_capabilities(dev);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static pci_device_t pci_lookup_device(char *address)
|
|
{
|
|
char *rest;
|
|
char *next;
|
|
long bus, device, function;
|
|
|
|
bus = strtol(address, &rest, 16);
|
|
|
|
if (rest == address || rest[0] != ':')
|
|
return NULL;
|
|
if (bus < 0 || bus > 255)
|
|
return NULL;
|
|
|
|
next = rest + 1;
|
|
|
|
device = strtol(next, &rest, 16);
|
|
if (rest == address || rest[0] != ':')
|
|
return NULL;
|
|
if (device < 0 || device > 31)
|
|
return NULL;
|
|
|
|
next = rest + 1;
|
|
|
|
function = strtol(next, &rest, 16);
|
|
if (rest == address || rest[0] != '\0')
|
|
return NULL;
|
|
if (function != 0)
|
|
return NULL;
|
|
|
|
if (pci_bridges[bus] == NULL)
|
|
return NULL;
|
|
|
|
return pci_bridges[bus]->devices[device];
|
|
}
|
|
|
|
static int do_pci_debug_cmd(int argc, struct cmd_arg *args)
|
|
{
|
|
int result = -1;
|
|
const char *cmd = NULL;
|
|
|
|
if (argc >= 2)
|
|
cmd = args[1].str;
|
|
|
|
if (cmd == NULL) {
|
|
result = -1;
|
|
} else {
|
|
if (strcmp(cmd, "enumerate") == 0) {
|
|
bool map_bars = false;
|
|
if (argc >= 3 && args[2].str != NULL) {
|
|
if (strcmp(args[2].str, "-map") == 0)
|
|
map_bars = true;
|
|
}
|
|
|
|
if (pci_bridges[0] == NULL) {
|
|
printf("Can't find host bridge\n");
|
|
result = -1;
|
|
} else {
|
|
pci_explore(pci_bridges[0], map_bars);
|
|
result = 0;
|
|
}
|
|
} else if (strcmp(cmd, "list") == 0) {
|
|
bool verbose = false;
|
|
if (argc >= 3 && args[2].str != NULL) {
|
|
if (strcmp(args[2].str, "-v") == 0)
|
|
verbose = true;
|
|
}
|
|
pci_list(verbose);
|
|
result = 0;
|
|
} else if (strcmp(cmd, "aspm") == 0 && argc == 3) {
|
|
pci_device_t dev = pci_lookup_device(args[2].str);
|
|
if (dev == NULL) {
|
|
printf("Couldn't find device \"%s\"\n", args[2].str);
|
|
result = -1;
|
|
} else {
|
|
pci_enable_pcie_aspm(dev);
|
|
result = 0;
|
|
}
|
|
} else if (strcmp(cmd, "cpm") == 0 && argc == 3) {
|
|
pci_device_t dev = pci_lookup_device(args[2].str);
|
|
if (dev == NULL) {
|
|
printf("Couldn't find device \"%s\"\n", args[2].str);
|
|
result = -1;
|
|
} else {
|
|
pci_enable_pcie_cpm(dev);
|
|
result = 0;
|
|
}
|
|
} else if (strcmp(cmd, "l1ss") == 0 && (argc == 3)) {
|
|
pci_device_t dev = pci_lookup_device(args[2].str);
|
|
if (dev == NULL) {
|
|
printf("Couldn't find device \"%s\"\n", args[2].str);
|
|
result = -1;
|
|
} else {
|
|
pci_enable_pcie_l1ss(dev);
|
|
result = 0;
|
|
}
|
|
} else if(strcmp(cmd, "ltr") == 0 && argc == 3) {
|
|
pci_device_t dev = pci_lookup_device(args[2].str);
|
|
if (dev == NULL) {
|
|
printf("Couldn't find device \"%s\"\n", args[2].str);
|
|
result = -1;
|
|
} else {
|
|
pci_enable_pcie_ltr(dev);
|
|
result = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result < 0) {
|
|
printf("Usage:\n");
|
|
printf("\taspm <dev>\t\t - Enables PCIe ASPM on the given device (bb:dd:ff)\n");
|
|
printf("\tcpm <dev>\t\t - Enables PCIe CPM on the given device (bb:dd:ff)\n");
|
|
printf("\tenumerate [-map]\t - Enumerates every PCI device reachable from the host bridge\n");
|
|
printf("\tlist [-v]\t\t - Lists all enumerated PCI devices\n");
|
|
printf("\tl1ss <dev>\t - Enables PCIe L1SS on the given device (bb:dd:ff)\n");
|
|
printf("\tltr <dev>\t\t - Enables PCIe LTR on the given device (bb:dd:ff) and all upstream devices\n");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
MENU_COMMAND_DEBUG(pci, do_pci_debug_cmd, "PCI debug commands", NULL);
|