Commit 6379ba57 authored by Rahul Singh's avatar Rahul Singh
Browse files

xen/arm: Add quirks for PCIE N1SDP


Signed-off-by: Rahul Singh's avatarRahul Singh <rahul.singh@arm.com>
parent 68b71d5d
Pipeline #376210937 failed with stages
in 0 seconds
......@@ -5,3 +5,4 @@ obj-y += pci-host-generic.o
obj-y += pci-host-common.o
obj-y += ecam.o
obj-y += pci-host-zynqmp.o
obj-y += pcie-n1sdp.o
......@@ -39,7 +39,7 @@ void __iomem *pci_ecam_map_bus(struct pci_host_bridge *bridge,
return base + (PCI_DEVFN2(sbdf) << devfn_shift) + where;
}
static int pci_ecam_register_mmio_handler(struct domain *d,
int pci_ecam_register_mmio_handler(struct domain *d,
struct pci_host_bridge *bridge,
const struct mmio_handler_ops *ops,
void *priv)
......@@ -50,7 +50,7 @@ static int pci_ecam_register_mmio_handler(struct domain *d,
return 0;
}
static int pci_ecam_need_p2m_mapping(struct domain *d,
int pci_ecam_need_p2m_mapping(struct domain *d,
struct pci_host_bridge *bridge,
u64 addr, u64 len)
{
......
/*
* Copyright (C) 2020 ARM Ltd.
*
* 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 <http://www.gnu.org/licenses/>.
*
* N1SDP PCI quirks to access the config space.
*
* This file implement quirk to mask the following issues:
* - PCIE SLVERR: config space accesses to invalid PCIe BDFs cause a bus
* error (signalled as an asynchronous SError)
* - MCFG BDF mapping: the root complex is mapped separately from the device
* config space
* - Non 32-bit accesses to config space are not supported.
*
* At boot time the SCP board firmware creates a discovery table with
* the root complex' base address and the valid BDF values, discovered while
* scanning the config space and catching the SErrors.
*
*/
#include <asm/device.h>
#include <asm/io.h>
#include <xen/pci.h>
#include <xen/sizes.h>
#include <xen/vmap.h>
#include <asm/pci.h>
/* Platform specific values as hardcoded in the firmware. */
#define AP_NS_SHARED_MEM_BASE 0x06000000
#define MAX_SEGMENTS 2 /* Two PCIe root complexes. */
#define BDF_TABLE_SIZE SZ_16K
/*
* Shared memory layout as written by the SCP upon boot time:
* ----
* Discover data header --> RC base address
* \-> BDF Count
* Discover data --> BDF 0...n
* ----
*/
struct pcie_discovery_data {
u32 rc_base_addr;
u32 nr_bdfs;
u32 valid_bdfs[0];
} *pcie_discovery_data[MAX_SEGMENTS];
void __iomem *rc_remapped_addr[MAX_SEGMENTS];
/*
* Copy data from IO memory space to "real" memory space.
*/
void memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)
{
while (count && !IS_ALIGNED((unsigned long)from, 8)) {
*(u8 *)to = __raw_readb(from);
from++;
to++;
count--;
}
while (count >= 8) {
*(u64 *)to = __raw_readq(from);
from += 8;
to += 8;
count -= 8;
}
while (count) {
*(u8 *)to = __raw_readb(from);
from++;
to++;
count--;
}
}
/*
* map_bus() is called before we do a config space access for a certain
* device. We use this to check whether this device is valid, avoiding
* config space accesses which would result in an SError otherwise.
*/
static void __iomem *pci_n1sdp_map_bus(struct pci_host_bridge *bridge,
uint32_t sbdf, uint32_t where)
{
const struct pci_ecam_ops *ops = bridge->sysdata;
struct pci_config_window *cfg = bridge->cfg;
unsigned int devfn_shift = ops->bus_shift - 8;
pci_sbdf_t sbdf_t = (pci_sbdf_t)sbdf;
unsigned int busn = sbdf_t.bus;
unsigned int segment = sbdf_t.seg;
unsigned int devfn = PCI_DEVFN(sbdf_t.dev, sbdf_t.fn);
unsigned int bdf_addr;
unsigned int table_count, i;
if ( segment >= MAX_SEGMENTS ||
busn < cfg->busn_start || busn > cfg->busn_end )
return NULL;
/* The PCIe root complex has a separate config space mapping. */
if ( busn == 0 && devfn == 0 )
return rc_remapped_addr[segment] + where;
/* Accesses beyond the vendor ID always go to existing devices. */
if ( where > 0 )
return pci_ecam_map_bus(bridge, sbdf, where);
busn -= cfg->busn_start;
bdf_addr = (busn << ops->bus_shift) + (devfn << devfn_shift);
table_count = pcie_discovery_data[segment]->nr_bdfs;
for ( i = 0; i < table_count; i++ ) {
if ( bdf_addr == pcie_discovery_data[segment]->valid_bdfs[i] )
return pci_ecam_map_bus(bridge, sbdf, where);
}
return NULL;
}
static int pci_n1sdp_init(struct pci_config_window *cfg, unsigned int segment)
{
paddr_t table_base;
struct pcie_discovery_data *shared_data;
size_t bdfs_size;
if ( segment >= MAX_SEGMENTS )
return -ENODEV;
table_base = AP_NS_SHARED_MEM_BASE + segment * BDF_TABLE_SIZE;
shared_data = ioremap_nocache(table_base, BDF_TABLE_SIZE);
if ( !shared_data )
return -ENOMEM;
/* Copy the valid BDFs structure to allocated normal memory. */
bdfs_size = sizeof(struct pcie_discovery_data) +
sizeof(u32) * shared_data->nr_bdfs;
pcie_discovery_data[segment] = _xmalloc(bdfs_size, PAGE_SIZE);
if ( !pcie_discovery_data[segment] )
return -ENOMEM;
memcpy_fromio(pcie_discovery_data[segment], shared_data, bdfs_size);
rc_remapped_addr[segment] = ioremap_nocache(shared_data->rc_base_addr,
PCI_CFG_SPACE_EXP_SIZE);
if ( !rc_remapped_addr[segment] ) {
printk(XENLOG_ERR "Cannot remap root port base\n");
return -ENOMEM;
}
iounmap(shared_data);
return 0;
}
static int pci_n1sdp_pcie_init(struct pci_config_window *cfg)
{
return pci_n1sdp_init(cfg, 0);
}
static int pci_n1sdp_ccix_init(struct pci_config_window *cfg)
{
return pci_n1sdp_init(cfg, 1);
}
int pci_generic_config_read32(struct pci_host_bridge *bridge, uint32_t sbdf,
uint32_t where, uint32_t size, uint32_t *val)
{
void __iomem *addr;
addr = bridge->ops->map_bus(bridge, sbdf, where & ~0x3);
if ( !addr ) {
*val = ~0;
return -ENODEV;
}
*val = readl(addr);
if ( size <= 2 )
*val = (*val >> (8 * (where & 3))) & ((1 << (size * 8)) - 1);
return 0;
}
int pci_generic_config_write32(struct pci_host_bridge *bridge, uint32_t sbdf,
uint32_t where, uint32_t size, uint32_t val)
{
void __iomem *addr;
u32 mask, tmp;
addr = bridge->ops->map_bus(bridge, sbdf, where & ~0x3);
if ( !addr )
return -ENODEV;
if ( size == 4 ) {
writel(val, addr);
return 0;
}
mask = ~(((1 << (size * 8)) - 1) << ((where & 0x3) * 8));
tmp = readl(addr) & mask;
tmp |= val << ((where & 0x3) * 8);
writel(tmp, addr);
return 0;
}
struct pci_ecam_ops pci_n1sdp_pcie_ecam_ops = {
.bus_shift = 20,
.init = pci_n1sdp_pcie_init,
.pci_ops = {
.map_bus = pci_n1sdp_map_bus,
.read = pci_generic_config_read32,
.write = pci_generic_config_write32,
.register_mmio_handler = pci_ecam_register_mmio_handler,
.need_p2m_mapping = pci_ecam_need_p2m_mapping,
}
};
struct pci_ecam_ops pci_n1sdp_ccix_ecam_ops = {
.bus_shift = 20,
.init = pci_n1sdp_ccix_init,
.pci_ops = {
.map_bus = pci_n1sdp_map_bus,
.read = pci_generic_config_read32,
.write = pci_generic_config_write32,
.register_mmio_handler = pci_ecam_register_mmio_handler,
.need_p2m_mapping = pci_ecam_need_p2m_mapping,
}
};
struct dt_device_match n1sdp_pcie_dt_match[] = {
{ .compatible = "arm,n1sdp-pcie" },
{ },
};
static int n1sdp_pcie_dt_init(struct dt_device_node *dev, const void *data)
{
const struct dt_device_match *of_id;
u32 segment;
if ( !dt_property_read_u32(dev, "linux,pci-domain", &segment )) {
printk(XENLOG_ERR "N1SDP PCI requires linux,pci-domain property\n");
return -EINVAL;
}
of_id = dt_match_node(n1sdp_pcie_dt_match, dev->dev.of_node);
printk("Found PCI host bridge %s compatible:%s \n", dt_node_full_name(dev),
of_id->compatible);
switch (segment) {
case 0:
n1sdp_pcie_dt_match[0].data = &pci_n1sdp_pcie_ecam_ops;
return pci_host_common_probe(dev, data);
case 1:
n1sdp_pcie_dt_match[0].data = &pci_n1sdp_ccix_ecam_ops;
return pci_host_common_probe(dev, data);
}
printk(XENLOG_ERR "Invalid segment number, must be smaller than %d\n",
MAX_SEGMENTS);
return -EINVAL;
}
DT_DEVICE_START(pci_gen, "PCIE N1SDP", DEVICE_PCI)
.dt_match = n1sdp_pcie_dt_match,
.init = n1sdp_pcie_dt_init,
DT_DEVICE_END
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/
......@@ -122,9 +122,15 @@ int pci_host_iterate_bridges(struct domain *d,
bool pci_host_bridge_need_p2m_mapping(struct domain *d,
const struct dt_device_node *node,
u64 addr, u64 len);
int pci_msi_conf_write_intercept(struct pci_dev *, unsigned int reg,
int pci_msi_conf_write_intercept(struct pci_dev *, unsigned int reg,
unsigned int size, uint32_t *data);
int pci_ecam_register_mmio_handler(struct domain *d,
struct pci_host_bridge *bridge,
const struct mmio_handler_ops *ops,
void *priv);
int pci_ecam_need_p2m_mapping(struct domain *d,
struct pci_host_bridge *bridge,
u64 addr, u64 len);
static always_inline bool is_pci_passthrough_enabled(void)
{
return !!pci_passthrough_enabled;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment