/*
* Based on Linux drivers/pci/ecam.c
* Based on Linux drivers/pci/controller/pci-host-common.c
* Based on Linux drivers/pci/controller/pci-host-generic.c
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
/*
* struct to hold pci device bar.
*/
struct pdev_bar_check
{
paddr_t start;
paddr_t end;
bool is_valid;
};
/*
* List for all the pci host bridges.
*/
static LIST_HEAD(pci_host_bridges);
static atomic_t domain_nr = ATOMIC_INIT(-1);
static int use_dt_domains = -1;
static inline void __iomem *pci_remap_cfgspace(paddr_t start, size_t len)
{
return ioremap_nocache(start, len);
}
static void pci_ecam_free(struct pci_config_window *cfg)
{
if ( cfg->win )
iounmap(cfg->win);
xfree(cfg);
}
static struct pci_config_window * __init
gen_pci_init(struct dt_device_node *dev, const struct pci_ecam_ops *ops)
{
int err, cfg_reg_idx;
u32 bus_range[2];
paddr_t addr, size;
struct pci_config_window *cfg;
cfg = xzalloc(struct pci_config_window);
if ( !cfg )
return NULL;
err = dt_property_read_u32_array(dev, "bus-range", bus_range,
ARRAY_SIZE(bus_range));
if ( err ) {
cfg->busn_start = 0;
cfg->busn_end = 0xff;
printk(XENLOG_INFO "%s: No bus range found for pci controller\n",
dt_node_full_name(dev));
} else {
cfg->busn_start = bus_range[0];
cfg->busn_end = bus_range[1];
if ( cfg->busn_end > cfg->busn_start + 0xff )
cfg->busn_end = cfg->busn_start + 0xff;
}
if ( ops->cfg_reg_index )
{
cfg_reg_idx = ops->cfg_reg_index(dev);
if ( cfg_reg_idx < 0 )
goto err_exit;
}
else
cfg_reg_idx = 0;
/* Parse our PCI ecam register address */
err = dt_device_get_address(dev, cfg_reg_idx, &addr, &size);
if ( err )
goto err_exit;
cfg->phys_addr = addr;
cfg->size = size;
/*
* On 64-bit systems, we do a single ioremap for the whole config space
* since we have enough virtual address range available. On 32-bit, we
* ioremap the config space for each bus individually.
* As of now only 64-bit is supported 32-bit is not supported.
*
* TODO: For 32-bit implement the ioremap/iounmap of config space
* dynamically for each read/write call.
*/
cfg->win = pci_remap_cfgspace(cfg->phys_addr, cfg->size);
if ( !cfg->win )
{
printk(XENLOG_ERR "ECAM ioremap failed\n");
goto err_exit;
}
printk("ECAM at [mem 0x%"PRIpaddr"-0x%"PRIpaddr"] for [bus %x-%x] \n",
cfg->phys_addr, cfg->phys_addr + cfg->size - 1,
cfg->busn_start, cfg->busn_end);
if ( ops->init )
{
err = ops->init(cfg);
if ( err )
goto err_exit;
}
return cfg;
err_exit:
pci_ecam_free(cfg);
return NULL;
}
struct pci_host_bridge *pci_alloc_host_bridge(void)
{
struct pci_host_bridge *bridge = xzalloc(struct pci_host_bridge);
if ( !bridge )
return NULL;
INIT_LIST_HEAD(&bridge->node);
return bridge;
}
void pci_add_host_bridge(struct pci_host_bridge *bridge)
{
list_add_tail(&bridge->node, &pci_host_bridges);
}
int pci_get_new_domain_nr(void)
{
if ( use_dt_domains )
return -1;
return atomic_inc_return(&domain_nr);
}
static int pci_bus_find_domain_nr(struct dt_device_node *dev)
{
int domain;
domain = dt_get_pci_domain_nr(dev);
/*
* Check DT domain and use_dt_domains values.
*
* If DT domain property is valid (domain >= 0) and
* use_dt_domains != 0, the DT assignment is valid since this means
* we have not previously allocated a domain number by using
* pci_get_new_domain_nr(); we should also update use_dt_domains to
* 1, to indicate that we have just assigned a domain number from
* DT.
*
* If DT domain property value is not valid (ie domain < 0), and we
* have not previously assigned a domain number from DT
* (use_dt_domains != 1) we should assign a domain number by
* using the:
*
* pci_get_new_domain_nr()
*
* API and update the use_dt_domains value to keep track of method we
* are using to assign domain numbers (use_dt_domains = 0).
*
* All other combinations imply we have a platform that is trying
* to mix domain numbers obtained from DT and pci_get_new_domain_nr(),
* which is a recipe for domain mishandling and it is prevented by
* invalidating the domain value (domain = -1) and printing a
* corresponding error.
*/
if ( domain >= 0 && use_dt_domains )
{
use_dt_domains = 1;
}
else if ( domain < 0 && use_dt_domains != 1 )
{
use_dt_domains = 0;
domain = pci_get_new_domain_nr();
}
else
{
domain = -1;
}
return domain;
}
int pci_host_common_probe(struct dt_device_node *dev,
const struct pci_ecam_ops *ops)
{
struct pci_host_bridge *bridge;
struct pci_config_window *cfg;
int err;
int domain;
if ( dt_device_for_passthrough(dev) )
return 0;
bridge = pci_alloc_host_bridge();
if ( !bridge )
return -ENOMEM;
/* Parse and map our Configuration Space windows */
cfg = gen_pci_init(dev, ops);
if ( !cfg )
{
err = -ENOMEM;
goto err_exit;
}
bridge->dt_node = dev;
bridge->cfg = cfg;
bridge->ops = &ops->pci_ops;
domain = pci_bus_find_domain_nr(dev);
if ( domain < 0 )
{
printk(XENLOG_ERR "Inconsistent \"linux,pci-domain\" property in DT\n");
BUG();
}
bridge->segment = domain;
pci_add_host_bridge(bridge);
return 0;
err_exit:
xfree(bridge);
return err;
}
/*
* Get host bridge node given a device attached to it.
*/
const struct dt_device_node *
pci_find_host_bridge_node(const struct pci_dev *pdev)
{
struct pci_host_bridge *bridge;
bridge = pci_find_host_bridge(pdev->seg, pdev->bus);
if ( unlikely(!bridge) )
{
printk(XENLOG_ERR "Unable to find PCI bridge for %pp\n", &pdev->sbdf);
return NULL;
}
return bridge->dt_node;
}
/*
* This function will lookup an hostbridge based on the segment and bus
* number.
*/
struct pci_host_bridge *pci_find_host_bridge(uint16_t segment, uint8_t bus)
{
struct pci_host_bridge *bridge;
list_for_each_entry( bridge, &pci_host_bridges, node )
{
if ( bridge->segment != segment )
continue;
if ( (bus < bridge->cfg->busn_start) || (bus > bridge->cfg->busn_end) )
continue;
return bridge;
}
return NULL;
}
/*
* This function will lookup an hostbridge based on config space address.
*/
int pci_get_host_bridge_segment(const struct dt_device_node *node,
uint16_t *segment)
{
struct pci_host_bridge *bridge;
list_for_each_entry( bridge, &pci_host_bridges, node )
{
if ( bridge->dt_node != node )
continue;
*segment = bridge->segment;
return 0;
}
return -EINVAL;
}
int pci_host_iterate_bridges_and_count(struct domain *d,
int (*cb)(struct domain *d,
struct pci_host_bridge *bridge))
{
struct pci_host_bridge *bridge;
int count = 0;
list_for_each_entry( bridge, &pci_host_bridges, node )
{
int ret;
ret = cb(d, bridge);
if ( ret < 0 )
return ret;
count += ret;
}
return count;
}
/*
* For each PCI host bridge we need to only map those ranges
* which are used by Domain-0 to properly initialize the bridge,
* e.g. we do not want to map ECAM configuration space which lives in
* "reg" device tree property, but we want to map other regions of
* the host bridge. The PCI aperture defined by the "ranges" device
* tree property should also be skipped.
*/
int __init pci_host_bridge_mappings(struct domain *d)
{
struct pci_host_bridge *bridge;
struct map_range_data mr_data = {
.d = d,
.p2mt = p2m_mmio_direct_dev,
.skip_mapping = false
};
list_for_each_entry( bridge, &pci_host_bridges, node )
{
const struct dt_device_node *dev = bridge->dt_node;
unsigned int i;
for ( i = 0; i < dt_number_of_address(dev); i++ )
{
uint64_t addr, size;
int err;
err = dt_device_get_address(dev, i, &addr, &size);
if ( err )
{
printk(XENLOG_ERR
"Unable to retrieve address range index=%u for %s\n",
i, dt_node_full_name(dev));
return err;
}
if ( bridge->ops->need_p2m_hwdom_mapping(d, bridge, addr) )
{
err = map_range_to_domain(dev, addr, size, &mr_data);
if ( err )
return err;
}
}
}
return 0;
}
/*
* TODO: BAR addresses and Root Complex window addresses are not guaranteed
* to be page aligned. We should check for alignment but this is not the
* right place for alignment check.
*/
static int is_bar_valid(const struct dt_device_node *dev,
paddr_t addr, paddr_t len, void *data)
{
struct pdev_bar_check *bar_data = data;
paddr_t s = bar_data->start;
paddr_t e = bar_data->end;
if ( (s >= addr) && (e <= (addr + len - 1)) )
bar_data->is_valid = true;
return 0;
}
/* TODO: Revisit this function when ACPI PCI passthrough support is added. */
bool pci_check_bar(const struct pci_dev *pdev, mfn_t start, mfn_t end)
{
int ret;
const struct dt_device_node *dt_node;
paddr_t s = mfn_to_maddr(start);
paddr_t e = mfn_to_maddr(end);
struct pdev_bar_check bar_data = {
.start = s,
.end = e,
.is_valid = false
};
if ( s >= e )
return false;
dt_node = pci_find_host_bridge_node(pdev);
if ( !dt_node )
return false;
ret = dt_for_each_range(dt_node, &is_bar_valid, &bar_data);
if ( ret < 0 )
return false;
return bar_data.is_valid;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/