blob: 21b07474539bbfcfcbfd5733089fecb2ce6504ca [file] [log] [blame]
/*
* Copyright (c) 2013-2014 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief PCI probe and information routines
*
* Module implements routines for PCI bus initialization and query.
*
* USAGE
* To use the driver, the platform must define:
* - Numbers of BUSes:
* - PCI_BUS_NUMBERS;
* - Register addresses:
* - PCI_CTRL_ADDR_REG;
* - PCI_CTRL_DATA_REG;
* - pci_pin2irq() - the routine that converts the PCI interrupt pin
* number to IRQ number.
*
* About scanning the PCI buses:
* At every new usage of this API, the code should call pci_bus_scan_init().
* It should own a struct pci_dev_info, filled in with the parameters it is
* interested to look for: class and/or vendor_id/device_id.
*
* Then it can loop on pci_bus_scan() providing a pointer on that structure.
* Such function can be called as long as it returns 1. At every successful
* return of pci_bus_scan() it means the provided structure pointer will have
* been updated with the current scan result which the code might be interested
* in. On pci_bus_scan() returning 0, the code should discard the result and
* stop calling pci_bus_scan(). If it wants to retrieve the result, it will
* have to restart the procedure all over again.
*
* EXAMPLE
* struct pci_dev_info info = {
* .class_type = PCI_CLASS_COMM_CTLR
* };
*
* pci_bus_scan_init();
*
* while (pci_bus_scan(&info)) {
* ...
* do something with "info" which holds a valid result, i.e. some
* device information matching the PCI class PCI_CLASS_COMM_CTLR
* ...
* }
*
* INTERNALS
* The whole logic runs around a structure: struct lookup_data, which exists
* on one instantiation called 'lookup'.
* Such structure is used for 2 distinct roles:
* - to match devices the caller is looking for
* - to loop on PCI bus, devices, function and BARs
*
* The search criteria are the class and/or the vendor_id/device_id of a PCI
* device. The caller first initializes the lookup structure by calling
* pci_bus_scan_init(), which will reset the search criteria as well as the
* loop parameters to 0. At the very first subsequent call of pci_bus_scan()
* the lookup structure will store the search criteria. Then the loop starts.
* For each bus it will run through each device on which it will loop on each
* function and BARs, as long as the criteria does not match or until it hit
* the limit of bus/dev/functions to scan.
*
* On a successful match, it will stop the loop, fill in the caller's
* pci_dev_info structure with the found device information, and return 1.
* Hopefully, the lookup structure still remembers where it stopped and the
* original search criteria. Thus, when the caller asks to scan again for
* a possible result next, the loop will restart where it stopped.
* That will work as long as there are relevant results found.
*/
#include <kernel.h>
#include <arch/cpu.h>
#include <sys/printk.h>
#include <toolchain.h>
#include <linker/sections.h>
#include <soc.h>
#include <pci/pci_mgr.h>
#include <pci/pci.h>
#ifdef CONFIG_PCI_ENUMERATION
/* NOTE. These parameters may need to be configurable */
#define LSPCI_MAX_BUS PCI_BUS_NUMBERS /* maximum number of buses to scan */
#define LSPCI_MAX_DEV 32 /* maximum number of devices to scan */
#define LSPCI_MAX_FUNC PCI_MAX_FUNCTIONS /* maximum functions to scan */
#define LSPCI_MAX_REG 64 /* maximum device registers to read */
/* Base Address Register configuration fields */
#define BAR_SPACE(x) ((x) & 0x00000001)
#define BAR_TYPE(x) ((x) & 0x00000006)
#define BAR_TYPE_32BIT 0
#define BAR_TYPE_64BIT 4
#define BAR_PREFETCH(x) (((x) >> 3) & 0x00000001)
#define BAR_ADDR(x) (((x) >> 4) & 0x0fffffff)
#define BAR_IO_MASK(x) ((x) & ~0x3)
#define BAR_MEM_MASK(x) ((x) & ~0xf)
struct lookup_data {
struct pci_dev_info info;
u32_t bus:9;
u32_t dev:6;
u32_t func:4;
u32_t baridx:3;
u32_t barofs:3;
u32_t unused:7;
u8_t buses;
};
static struct lookup_data __noinit lookup;
/**
* @brief Return the configuration for the specified BAR
*
* @return 0 if BAR is implemented, -1 if not.
*/
static int pci_bar_config_get(union pci_addr_reg pci_ctrl_addr, u32_t *config)
{
u32_t old_value;
/* save the current setting */
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(old_value), &old_value);
/* write to the BAR to see how large it is */
pci_write(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(u32_t), 0xffffffff);
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(*config), config);
/* put back the old configuration */
pci_write(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(old_value), old_value);
/* check if this BAR is implemented */
if (*config != 0xffffffff && *config != 0U) {
return 0;
}
/* BAR not supported */
return -1;
}
/**
* @brief Retrieve the I/O address and IRQ of the specified BAR
*
* @return -1 on error, 0 if 32 bit BAR retrieved or 1 if 64 bit BAR retrieved
*
* NOTE: Routine does not set up parameters for 64 bit BARS, they are ignored.
*/
static int pci_bar_params_get(union pci_addr_reg pci_ctrl_addr,
struct pci_dev_info *dev_info,
int max_bars)
{
u32_t bar_value;
u32_t bar_config;
u32_t bar_hival;
u32_t addr;
u32_t mask;
pci_ctrl_addr.field.reg = 4 + lookup.barofs;
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(bar_value), &bar_value);
if (pci_bar_config_get(pci_ctrl_addr, &bar_config) != 0) {
return -1;
}
if (BAR_SPACE(bar_config) == BAR_SPACE_MEM) {
dev_info->mem_type = BAR_SPACE_MEM;
mask = ~0xf;
if (BAR_TYPE(bar_config) == BAR_TYPE_64BIT) {
/* Last BAR register cannot be 64-bit */
if (++lookup.barofs >= max_bars) {
return 1;
}
/* Make sure the address is accessible */
pci_ctrl_addr.field.reg++;
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(bar_hival), &bar_hival);
if (bar_hival) {
return 1; /* Inaccessible memory */
}
}
} else {
dev_info->mem_type = BAR_SPACE_IO;
mask = ~0x3;
}
dev_info->addr = bar_value & mask;
addr = bar_config & mask;
if (addr != 0U) {
/* calculate the size of the BAR memory required */
dev_info->size = 1 << (find_lsb_set(addr) - 1);
}
return 0;
}
static bool pci_read_multifunction(union pci_addr_reg pci_ctrl_addr)
{
u32_t header_type;
pci_ctrl_addr.field.reg = 3;
pci_ctrl_addr.field.offset = 0;
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr, sizeof(header_type),
&header_type);
return header_type >> 16 & 0x80;
}
/**
* @brief Scan the specified PCI device for all sub functions
*
* @return 1 if a device has been found, 0 otherwise.
*/
static int pci_dev_scan(union pci_addr_reg pci_ctrl_addr,
struct pci_dev_info *dev_info)
{
static union pci_dev pci_dev_header;
u32_t pci_data;
int max_bars;
bool multi_function;
/* verify first if there is a valid device at this point */
pci_ctrl_addr.field.func = 0;
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(pci_data), &pci_data);
if (pci_data == 0xffffffff) {
return 0;
}
/* Check that PCI is multi a function device */
multi_function = pci_read_multifunction(pci_ctrl_addr);
/* scan all the possible functions for this device */
for (; lookup.func < LSPCI_MAX_FUNC;
lookup.baridx = 0, lookup.barofs = 0, lookup.func++) {
if (lookup.info.function != PCI_FUNCTION_ANY &&
lookup.func != lookup.info.function) {
return 0;
}
/* Skip single function device */
if (lookup.func != 0 && !multi_function) {
break;
}
pci_ctrl_addr.field.func = lookup.func;
if (lookup.func != 0) {
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(pci_data), &pci_data);
if (pci_data == 0xffffffff) {
continue;
}
}
/* get the PCI header from the device */
pci_header_get(DEFAULT_PCI_CONTROLLER,
pci_ctrl_addr, &pci_dev_header);
if (pci_dev_header.field.class == PCI_CLASS_BRIDGE_CTLR &&
pci_dev_header.field.subclass == PCI_SUBCLASS_P2P_BRIDGE) {
lookup.buses++;
}
/*
* Skip a device if its class is specified by the
* caller and does not match
*/
if (lookup.info.class_type &&
pci_dev_header.field.class != lookup.info.class_type) {
continue;
}
if (lookup.info.vendor_id && lookup.info.device_id &&
(lookup.info.vendor_id != pci_dev_header.field.vendor_id ||
lookup.info.device_id != pci_dev_header.field.device_id)) {
continue;
}
/* Get memory and interrupt information */
if ((pci_dev_header.field.hdr_type & 0x7f) == 1) {
max_bars = 2;
} else {
max_bars = PCI_MAX_BARS;
}
for (; lookup.barofs < max_bars;
lookup.baridx++, lookup.barofs++) {
/* Ignore BARs with errors */
if (pci_bar_params_get(pci_ctrl_addr, dev_info,
max_bars) != 0) {
continue;
} else if (lookup.info.bar != PCI_BAR_ANY &&
lookup.baridx != lookup.info.bar) {
continue;
} else {
dev_info->bus = lookup.bus;
dev_info->dev = lookup.dev;
dev_info->vendor_id =
pci_dev_header.field.vendor_id;
dev_info->device_id =
pci_dev_header.field.device_id;
dev_info->class_type =
pci_dev_header.field.class;
dev_info->irq = pci_pin2irq(dev_info->bus,
dev_info->dev,
pci_dev_header.field.interrupt_pin);
dev_info->function = lookup.func;
dev_info->bar = lookup.baridx;
lookup.baridx++;
lookup.barofs++;
if (lookup.barofs >= max_bars) {
lookup.baridx = 0;
lookup.barofs = 0;
lookup.func++;
}
return 1;
}
}
}
return 0;
}
void pci_bus_scan_init(void)
{
lookup.info.class_type = 0;
lookup.info.vendor_id = 0;
lookup.info.device_id = 0;
lookup.info.function = PCI_FUNCTION_ANY;
lookup.info.bar = PCI_BAR_ANY;
lookup.bus = 0;
lookup.dev = 0;
lookup.func = 0;
lookup.baridx = 0;
lookup.barofs = 0;
lookup.buses = LSPCI_MAX_BUS;
}
/**
* @brief Scans PCI bus for devices
*
* The routine scans the PCI bus for the devices on criteria provided in the
* given dev_info at first call. Which criteria can be class and/or
* vendor_id/device_id.
*
* @return 1 on success, 0 otherwise. On success, dev_info is filled in with
* currently found device information
*/
int pci_bus_scan(struct pci_dev_info *dev_info)
{
union pci_addr_reg pci_ctrl_addr;
bool init_from_dev_info =
!lookup.info.class_type &&
!lookup.info.vendor_id &&
!lookup.info.device_id &&
lookup.info.bar == PCI_BAR_ANY &&
lookup.info.function == PCI_FUNCTION_ANY;
if (init_from_dev_info) {
lookup.info.class_type = dev_info->class_type;
lookup.info.vendor_id = dev_info->vendor_id;
lookup.info.device_id = dev_info->device_id;
lookup.info.function = dev_info->function;
lookup.info.bar = dev_info->bar;
}
/* initialise the PCI controller address register value */
pci_ctrl_addr.value = 0;
if (lookup.info.function != PCI_FUNCTION_ANY) {
lookup.func = lookup.info.function;
}
/* run through the buses and devices */
for (; lookup.bus < lookup.buses; lookup.bus++) {
for (; lookup.dev < LSPCI_MAX_DEV; lookup.dev++) {
if (lookup.bus == 0 && lookup.dev == 0) {
continue;
}
pci_ctrl_addr.field.bus = lookup.bus;
pci_ctrl_addr.field.device = lookup.dev;
if (pci_dev_scan(pci_ctrl_addr, dev_info)) {
return 1;
}
if (lookup.info.function != PCI_FUNCTION_ANY) {
lookup.func = lookup.info.function;
} else {
lookup.func = 0;
}
}
lookup.dev = 0;
}
return 0;
}
#endif /* CONFIG_PCI_ENUMERATION */
static void pci_set_command_bits(struct pci_dev_info *dev_info, u32_t bits)
{
union pci_addr_reg pci_ctrl_addr;
u32_t pci_data;
pci_ctrl_addr.value = 0;
pci_ctrl_addr.field.func = dev_info->function;
pci_ctrl_addr.field.bus = dev_info->bus;
pci_ctrl_addr.field.device = dev_info->dev;
pci_ctrl_addr.field.reg = 1;
pci_read(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(u16_t), &pci_data);
pci_data = pci_data | bits;
pci_write(DEFAULT_PCI_CONTROLLER, pci_ctrl_addr,
sizeof(u16_t), pci_data);
}
void pci_enable_regs(struct pci_dev_info *dev_info)
{
pci_set_command_bits(dev_info, PCI_CMD_MEM_ENABLE);
}
void pci_enable_bus_master(struct pci_dev_info *dev_info)
{
pci_set_command_bits(dev_info, PCI_CMD_MASTER_ENABLE);
}
#ifdef CONFIG_PCI_LOG_LEVEL_DBG
/**
*
* @brief Show PCI device
*
* Shows the PCI device found provided as parameter.
*
* @return N/A
*/
void pci_show(struct pci_dev_info *dev_info)
{
printk("%x:%x.%x %X:%X class: 0x%X, %u, %s, "
"addrs: 0x%X-0x%X, IRQ %d\n",
dev_info->bus,
dev_info->dev,
dev_info->function,
dev_info->vendor_id,
dev_info->device_id,
dev_info->class_type,
dev_info->bar,
(dev_info->mem_type == BAR_SPACE_MEM) ? "MEM" : "I/O",
(u32_t)dev_info->addr,
(u32_t)(dev_info->addr + dev_info->size - 1),
dev_info->irq);
}
#endif /* CONFIG_PCI_LOG_LEVEL_DBG */