mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-23 18:38:51 +09:00
![Bin Meng](/assets/img/avatar_default.png)
Commit aec241d
"dm: pci: Use the correct hose when configuring devices"
was an attempt to fix pci bridge device configuration, but unfortunately
that does not work 100%. In pciauto_config_devices(), the fix tried to
call pciauto_config_device() with a ctlr_hose which is supposed to be
the root controller hose, however when walking through a pci topology
with 2 or more pci bridges this logic simply fails.
The call chain is: pciauto_config_devices()->pciauto_config_device()
->dm_pci_hose_probe_bus(). Here the call to dm_pci_hose_probe_bus()
does not make any sense as the given hose is not the bridge device's
hose, instead it is either the root controller's hose (case#1: if it
is the 2nd pci bridge), or the bridge's parent bridge's hose (case#2:
if it is the 3rd pci bridge). In both cases the logic is wrong.
For example, for failing case#1 if the bridge device to config has the
same devfn as one of the devices under the root controller, the call
to pci_bus_find_devfn() will return the udevice of that pci device
under the root controller as the bus, but this is wrong as the udevice
is not a bus which does not contain all the necessary bits associated
with the udevice which causes further failures.
To correctly support pci bridge device configuration, we should still
call pciauto_config_device() with the pci bridge's hose directly.
In order to access valid pci region information, we need to refer to
the root controller simply by a call to pci_bus_to_hose(0) and get the
region information there in the pciauto_prescan_setup_bridge(),
pciauto_postscan_setup_bridge() and pciauto_config_device().
Signed-off-by: Bin Meng <bmeng.cn@gmail.com>
366 lines
7.9 KiB
C
366 lines
7.9 KiB
C
/*
|
|
* Copyright (c) 2014 Google, Inc
|
|
*
|
|
* (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH <www.elinos.com>
|
|
* Andreas Heppel <aheppel@sysgo.de>
|
|
*
|
|
* (C) Copyright 2002, 2003
|
|
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <pci.h>
|
|
#include <asm/io.h>
|
|
|
|
const char *pci_class_str(u8 class)
|
|
{
|
|
switch (class) {
|
|
case PCI_CLASS_NOT_DEFINED:
|
|
return "Build before PCI Rev2.0";
|
|
break;
|
|
case PCI_BASE_CLASS_STORAGE:
|
|
return "Mass storage controller";
|
|
break;
|
|
case PCI_BASE_CLASS_NETWORK:
|
|
return "Network controller";
|
|
break;
|
|
case PCI_BASE_CLASS_DISPLAY:
|
|
return "Display controller";
|
|
break;
|
|
case PCI_BASE_CLASS_MULTIMEDIA:
|
|
return "Multimedia device";
|
|
break;
|
|
case PCI_BASE_CLASS_MEMORY:
|
|
return "Memory controller";
|
|
break;
|
|
case PCI_BASE_CLASS_BRIDGE:
|
|
return "Bridge device";
|
|
break;
|
|
case PCI_BASE_CLASS_COMMUNICATION:
|
|
return "Simple comm. controller";
|
|
break;
|
|
case PCI_BASE_CLASS_SYSTEM:
|
|
return "Base system peripheral";
|
|
break;
|
|
case PCI_BASE_CLASS_INPUT:
|
|
return "Input device";
|
|
break;
|
|
case PCI_BASE_CLASS_DOCKING:
|
|
return "Docking station";
|
|
break;
|
|
case PCI_BASE_CLASS_PROCESSOR:
|
|
return "Processor";
|
|
break;
|
|
case PCI_BASE_CLASS_SERIAL:
|
|
return "Serial bus controller";
|
|
break;
|
|
case PCI_BASE_CLASS_INTELLIGENT:
|
|
return "Intelligent controller";
|
|
break;
|
|
case PCI_BASE_CLASS_SATELLITE:
|
|
return "Satellite controller";
|
|
break;
|
|
case PCI_BASE_CLASS_CRYPT:
|
|
return "Cryptographic device";
|
|
break;
|
|
case PCI_BASE_CLASS_SIGNAL_PROCESSING:
|
|
return "DSP";
|
|
break;
|
|
case PCI_CLASS_OTHERS:
|
|
return "Does not fit any class";
|
|
break;
|
|
default:
|
|
return "???";
|
|
break;
|
|
};
|
|
}
|
|
|
|
pci_dev_t pci_find_class(uint find_class, int index)
|
|
{
|
|
int bus;
|
|
int devnum;
|
|
pci_dev_t bdf;
|
|
uint32_t class;
|
|
|
|
for (bus = 0; bus <= pci_last_busno(); bus++) {
|
|
for (devnum = 0; devnum < PCI_MAX_PCI_DEVICES - 1; devnum++) {
|
|
pci_read_config_dword(PCI_BDF(bus, devnum, 0),
|
|
PCI_CLASS_REVISION, &class);
|
|
if (class >> 16 == 0xffff)
|
|
continue;
|
|
|
|
for (bdf = PCI_BDF(bus, devnum, 0);
|
|
bdf <= PCI_BDF(bus, devnum,
|
|
PCI_MAX_PCI_FUNCTIONS - 1);
|
|
bdf += PCI_BDF(0, 0, 1)) {
|
|
pci_read_config_dword(bdf, PCI_CLASS_REVISION,
|
|
&class);
|
|
class >>= 8;
|
|
|
|
if (class != find_class)
|
|
continue;
|
|
/*
|
|
* Decrement the index. We want to return the
|
|
* correct device, so index is 0 for the first
|
|
* matching device, 1 for the second, etc.
|
|
*/
|
|
if (index) {
|
|
index--;
|
|
continue;
|
|
}
|
|
/* Return index'th controller. */
|
|
return bdf;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
__weak int pci_skip_dev(struct pci_controller *hose, pci_dev_t dev)
|
|
{
|
|
/*
|
|
* Check if pci device should be skipped in configuration
|
|
*/
|
|
if (dev == PCI_BDF(hose->first_busno, 0, 0)) {
|
|
#if defined(CONFIG_PCI_CONFIG_HOST_BRIDGE) /* don't skip host bridge */
|
|
/*
|
|
* Only skip configuration if "pciconfighost" is not set
|
|
*/
|
|
if (getenv("pciconfighost") == NULL)
|
|
return 1;
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Get a virtual address associated with a BAR region */
|
|
void *pci_map_bar(pci_dev_t pdev, int bar, int flags)
|
|
{
|
|
pci_addr_t pci_bus_addr;
|
|
u32 bar_response;
|
|
|
|
/* read BAR address */
|
|
pci_read_config_dword(pdev, bar, &bar_response);
|
|
pci_bus_addr = (pci_addr_t)(bar_response & ~0xf);
|
|
|
|
/*
|
|
* Pass "0" as the length argument to pci_bus_to_virt. The arg
|
|
* isn't actualy used on any platform because u-boot assumes a static
|
|
* linear mapping. In the future, this could read the BAR size
|
|
* and pass that as the size if needed.
|
|
*/
|
|
return pci_bus_to_virt(pdev, pci_bus_addr, flags, 0, MAP_NOCACHE);
|
|
}
|
|
|
|
void pci_write_bar32(struct pci_controller *hose, pci_dev_t dev, int barnum,
|
|
u32 addr_and_ctrl)
|
|
{
|
|
int bar;
|
|
|
|
bar = PCI_BASE_ADDRESS_0 + barnum * 4;
|
|
pci_hose_write_config_dword(hose, dev, bar, addr_and_ctrl);
|
|
}
|
|
|
|
u32 pci_read_bar32(struct pci_controller *hose, pci_dev_t dev, int barnum)
|
|
{
|
|
u32 addr;
|
|
int bar;
|
|
|
|
bar = PCI_BASE_ADDRESS_0 + barnum * 4;
|
|
pci_hose_read_config_dword(hose, dev, bar, &addr);
|
|
if (addr & PCI_BASE_ADDRESS_SPACE_IO)
|
|
return addr & PCI_BASE_ADDRESS_IO_MASK;
|
|
else
|
|
return addr & PCI_BASE_ADDRESS_MEM_MASK;
|
|
}
|
|
|
|
int __pci_hose_bus_to_phys(struct pci_controller *hose,
|
|
pci_addr_t bus_addr,
|
|
unsigned long flags,
|
|
unsigned long skip_mask,
|
|
phys_addr_t *pa)
|
|
{
|
|
struct pci_region *res;
|
|
int i;
|
|
|
|
for (i = 0; i < hose->region_count; i++) {
|
|
res = &hose->regions[i];
|
|
|
|
if (((res->flags ^ flags) & PCI_REGION_TYPE) != 0)
|
|
continue;
|
|
|
|
if (res->flags & skip_mask)
|
|
continue;
|
|
|
|
if (bus_addr >= res->bus_start &&
|
|
(bus_addr - res->bus_start) < res->size) {
|
|
*pa = (bus_addr - res->bus_start + res->phys_start);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
phys_addr_t pci_hose_bus_to_phys(struct pci_controller *hose,
|
|
pci_addr_t bus_addr,
|
|
unsigned long flags)
|
|
{
|
|
phys_addr_t phys_addr = 0;
|
|
int ret;
|
|
|
|
if (!hose) {
|
|
puts("pci_hose_bus_to_phys: invalid hose\n");
|
|
return phys_addr;
|
|
}
|
|
|
|
#ifdef CONFIG_DM_PCI
|
|
/* The root controller has the region information */
|
|
hose = pci_bus_to_hose(0);
|
|
#endif
|
|
|
|
/*
|
|
* if PCI_REGION_MEM is set we do a two pass search with preference
|
|
* on matches that don't have PCI_REGION_SYS_MEMORY set
|
|
*/
|
|
if ((flags & PCI_REGION_MEM) == PCI_REGION_MEM) {
|
|
ret = __pci_hose_bus_to_phys(hose, bus_addr,
|
|
flags, PCI_REGION_SYS_MEMORY, &phys_addr);
|
|
if (!ret)
|
|
return phys_addr;
|
|
}
|
|
|
|
ret = __pci_hose_bus_to_phys(hose, bus_addr, flags, 0, &phys_addr);
|
|
|
|
if (ret)
|
|
puts("pci_hose_bus_to_phys: invalid physical address\n");
|
|
|
|
return phys_addr;
|
|
}
|
|
|
|
int __pci_hose_phys_to_bus(struct pci_controller *hose,
|
|
phys_addr_t phys_addr,
|
|
unsigned long flags,
|
|
unsigned long skip_mask,
|
|
pci_addr_t *ba)
|
|
{
|
|
struct pci_region *res;
|
|
pci_addr_t bus_addr;
|
|
int i;
|
|
|
|
for (i = 0; i < hose->region_count; i++) {
|
|
res = &hose->regions[i];
|
|
|
|
if (((res->flags ^ flags) & PCI_REGION_TYPE) != 0)
|
|
continue;
|
|
|
|
if (res->flags & skip_mask)
|
|
continue;
|
|
|
|
bus_addr = phys_addr - res->phys_start + res->bus_start;
|
|
|
|
if (bus_addr >= res->bus_start &&
|
|
bus_addr < res->bus_start + res->size) {
|
|
*ba = bus_addr;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
pci_addr_t pci_hose_phys_to_bus(struct pci_controller *hose,
|
|
phys_addr_t phys_addr,
|
|
unsigned long flags)
|
|
{
|
|
pci_addr_t bus_addr = 0;
|
|
int ret;
|
|
|
|
if (!hose) {
|
|
puts("pci_hose_phys_to_bus: invalid hose\n");
|
|
return bus_addr;
|
|
}
|
|
|
|
#ifdef CONFIG_DM_PCI
|
|
/* The root controller has the region information */
|
|
hose = pci_bus_to_hose(0);
|
|
#endif
|
|
|
|
/*
|
|
* if PCI_REGION_MEM is set we do a two pass search with preference
|
|
* on matches that don't have PCI_REGION_SYS_MEMORY set
|
|
*/
|
|
if ((flags & PCI_REGION_MEM) == PCI_REGION_MEM) {
|
|
ret = __pci_hose_phys_to_bus(hose, phys_addr,
|
|
flags, PCI_REGION_SYS_MEMORY, &bus_addr);
|
|
if (!ret)
|
|
return bus_addr;
|
|
}
|
|
|
|
ret = __pci_hose_phys_to_bus(hose, phys_addr, flags, 0, &bus_addr);
|
|
|
|
if (ret)
|
|
puts("pci_hose_phys_to_bus: invalid physical address\n");
|
|
|
|
return bus_addr;
|
|
}
|
|
|
|
pci_dev_t pci_find_device(unsigned int vendor, unsigned int device, int index)
|
|
{
|
|
struct pci_device_id ids[2] = { {}, {0, 0} };
|
|
|
|
ids[0].vendor = vendor;
|
|
ids[0].device = device;
|
|
|
|
return pci_find_devices(ids, index);
|
|
}
|
|
|
|
pci_dev_t pci_hose_find_devices(struct pci_controller *hose, int busnum,
|
|
struct pci_device_id *ids, int *indexp)
|
|
{
|
|
int found_multi = 0;
|
|
u16 vendor, device;
|
|
u8 header_type;
|
|
pci_dev_t bdf;
|
|
int i;
|
|
|
|
for (bdf = PCI_BDF(busnum, 0, 0);
|
|
bdf < PCI_BDF(busnum + 1, 0, 0);
|
|
bdf += PCI_BDF(0, 0, 1)) {
|
|
if (pci_skip_dev(hose, bdf))
|
|
continue;
|
|
|
|
if (!PCI_FUNC(bdf)) {
|
|
pci_read_config_byte(bdf, PCI_HEADER_TYPE,
|
|
&header_type);
|
|
found_multi = header_type & 0x80;
|
|
} else {
|
|
if (!found_multi)
|
|
continue;
|
|
}
|
|
|
|
pci_read_config_word(bdf, PCI_VENDOR_ID, &vendor);
|
|
pci_read_config_word(bdf, PCI_DEVICE_ID, &device);
|
|
|
|
for (i = 0; ids[i].vendor != 0; i++) {
|
|
if (vendor == ids[i].vendor &&
|
|
device == ids[i].device) {
|
|
if ((*indexp) <= 0)
|
|
return bdf;
|
|
|
|
(*indexp)--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|