Skip to content

Commit

Permalink
Merge branch 'pci/arm64-acpi' into next
Browse files Browse the repository at this point in the history
* pci/arm64-acpi:
  ARM64: PCI: Support ACPI-based PCI host controller
  ARM64: PCI: Implement AML accessors for PCI_Config region
  ARM64: PCI: ACPI support for legacy IRQs parsing and consolidation with DT code
  ARM64: PCI: Add acpi_pci_bus_find_domain_nr()
  PCI: Factor DT-specific pci_bus_find_domain_nr() code out
  PCI: Refactor pci_bus_assign_domain_nr() for CONFIG_PCI_DOMAINS_GENERIC
  PCI/ACPI: Add generic MCFG table handling
  PCI/ACPI: Support I/O resources when parsing host bridge resources
  PCI: Add pci_unmap_iospace() to unmap I/O resources
  PCI: Add parent device field to ECAM struct pci_config_window
  PCI: Move ecam.h to linux/include/pci-ecam.h
  • Loading branch information
Bjorn Helgaas committed Aug 1, 2016
2 parents af8c34c + 0cb0786 commit c5cb85b
Show file tree
Hide file tree
Showing 16 changed files with 322 additions and 31 deletions.
2 changes: 2 additions & 0 deletions arch/arm64/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ config ARM64
select ACPI_CCA_REQUIRED if ACPI
select ACPI_GENERIC_GSI if ACPI
select ACPI_REDUCED_HARDWARE_ONLY if ACPI
select ACPI_MCFG if ACPI
select ARCH_HAS_DEVMEM_IS_ALLOWED
select ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE
select ARCH_HAS_ELF_RANDOMIZE
Expand Down Expand Up @@ -96,6 +97,7 @@ config ARM64
select OF_EARLY_FLATTREE
select OF_NUMA if NUMA && OF
select OF_RESERVED_MEM
select PCI_ECAM if ACPI
select PERF_USE_VMALLOC
select POWER_RESET
select POWER_SUPPLY
Expand Down
146 changes: 138 additions & 8 deletions arch/arm64/kernel/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#include <linux/mm.h>
#include <linux/of_pci.h>
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/pci-acpi.h>
#include <linux/pci-ecam.h>
#include <linux/slab.h>

/*
Expand Down Expand Up @@ -50,11 +53,16 @@ int pcibios_enable_device(struct pci_dev *dev, int mask)
}

/*
* Try to assign the IRQ number from DT when adding a new device
* Try to assign the IRQ number when probing a new device
*/
int pcibios_add_device(struct pci_dev *dev)
int pcibios_alloc_irq(struct pci_dev *dev)
{
dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
if (acpi_disabled)
dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
#ifdef CONFIG_ACPI
else
return acpi_pci_irq_enable(dev);
#endif

return 0;
}
Expand All @@ -65,13 +73,21 @@ int pcibios_add_device(struct pci_dev *dev)
int raw_pci_read(unsigned int domain, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *val)
{
return -ENXIO;
struct pci_bus *b = pci_find_bus(domain, bus);

if (!b)
return PCIBIOS_DEVICE_NOT_FOUND;
return b->ops->read(b, devfn, reg, len, val);
}

int raw_pci_write(unsigned int domain, unsigned int bus,
unsigned int devfn, int reg, int len, u32 val)
{
return -ENXIO;
struct pci_bus *b = pci_find_bus(domain, bus);

if (!b)
return PCIBIOS_DEVICE_NOT_FOUND;
return b->ops->write(b, devfn, reg, len, val);
}

#ifdef CONFIG_NUMA
Expand All @@ -85,10 +101,124 @@ EXPORT_SYMBOL(pcibus_to_node);
#endif

#ifdef CONFIG_ACPI
/* Root bridge scanning */

struct acpi_pci_generic_root_info {
struct acpi_pci_root_info common;
struct pci_config_window *cfg; /* config space mapping */
};

int acpi_pci_bus_find_domain_nr(struct pci_bus *bus)
{
struct pci_config_window *cfg = bus->sysdata;
struct acpi_device *adev = to_acpi_device(cfg->parent);
struct acpi_pci_root *root = acpi_driver_data(adev);

return root->segment;
}

int pcibios_root_bridge_prepare(struct pci_host_bridge *bridge)
{
if (!acpi_disabled) {
struct pci_config_window *cfg = bridge->bus->sysdata;
struct acpi_device *adev = to_acpi_device(cfg->parent);
ACPI_COMPANION_SET(&bridge->dev, adev);
}

return 0;
}

/*
* Lookup the bus range for the domain in MCFG, and set up config space
* mapping.
*/
static struct pci_config_window *
pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root)
{
struct resource *bus_res = &root->secondary;
u16 seg = root->segment;
struct pci_config_window *cfg;
struct resource cfgres;
unsigned int bsz;

/* Use address from _CBA if present, otherwise lookup MCFG */
if (!root->mcfg_addr)
root->mcfg_addr = pci_mcfg_lookup(seg, bus_res);

if (!root->mcfg_addr) {
dev_err(&root->device->dev, "%04x:%pR ECAM region not found\n",
seg, bus_res);
return NULL;
}

bsz = 1 << pci_generic_ecam_ops.bus_shift;
cfgres.start = root->mcfg_addr + bus_res->start * bsz;
cfgres.end = cfgres.start + resource_size(bus_res) * bsz - 1;
cfgres.flags = IORESOURCE_MEM;
cfg = pci_ecam_create(&root->device->dev, &cfgres, bus_res,
&pci_generic_ecam_ops);
if (IS_ERR(cfg)) {
dev_err(&root->device->dev, "%04x:%pR error %ld mapping ECAM\n",
seg, bus_res, PTR_ERR(cfg));
return NULL;
}

return cfg;
}

/* release_info: free resources allocated by init_info */
static void pci_acpi_generic_release_info(struct acpi_pci_root_info *ci)
{
struct acpi_pci_generic_root_info *ri;

ri = container_of(ci, struct acpi_pci_generic_root_info, common);
pci_ecam_free(ri->cfg);
kfree(ri);
}

static struct acpi_pci_root_ops acpi_pci_root_ops = {
.release_info = pci_acpi_generic_release_info,
};

/* Interface called from ACPI code to setup PCI host controller */
struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
/* TODO: Should be revisited when implementing PCI on ACPI */
return NULL;
int node = acpi_get_node(root->device->handle);
struct acpi_pci_generic_root_info *ri;
struct pci_bus *bus, *child;

ri = kzalloc_node(sizeof(*ri), GFP_KERNEL, node);
if (!ri)
return NULL;

ri->cfg = pci_acpi_setup_ecam_mapping(root);
if (!ri->cfg) {
kfree(ri);
return NULL;
}

acpi_pci_root_ops.pci_ops = &ri->cfg->ops->pci_ops;
bus = acpi_pci_root_create(root, &acpi_pci_root_ops, &ri->common,
ri->cfg);
if (!bus)
return NULL;

pci_bus_size_bridges(bus);
pci_bus_assign_resources(bus);

list_for_each_entry(child, &bus->children, node)
pcie_bus_configure_settings(child);

return bus;
}

void pcibios_add_bus(struct pci_bus *bus)
{
acpi_pci_add_bus(bus);
}

void pcibios_remove_bus(struct pci_bus *bus)
{
acpi_pci_remove_bus(bus);
}

#endif
3 changes: 3 additions & 0 deletions drivers/acpi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ config ACPI_PROCESSOR_IDLE
bool
select CPU_IDLE

config ACPI_MCFG
bool

config ACPI_CPPC_LIB
bool
depends on ACPI_PROCESSOR
Expand Down
1 change: 1 addition & 0 deletions drivers/acpi/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ acpi-$(CONFIG_ARCH_MIGHT_HAVE_ACPI_PDC) += processor_pdc.o
acpi-y += ec.o
acpi-$(CONFIG_ACPI_DOCK) += dock.o
acpi-y += pci_root.o pci_link.o pci_irq.o
obj-$(CONFIG_ACPI_MCFG) += pci_mcfg.o
acpi-y += acpi_lpss.o acpi_apd.o
acpi-y += acpi_platform.o
acpi-y += acpi_pnp.o
Expand Down
92 changes: 92 additions & 0 deletions drivers/acpi/pci_mcfg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2016 Broadcom
* Author: Jayachandran C <jchandra@broadcom.com>
* Copyright (C) 2016 Semihalf
* Author: Tomasz Nowicki <tn@semihalf.com>
*
* 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 (the "GPL").
*
* 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 version 2 (GPLv2) for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 (GPLv2) along with this source code.
*/

#define pr_fmt(fmt) "ACPI: " fmt

#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/pci-acpi.h>

/* Structure to hold entries from the MCFG table */
struct mcfg_entry {
struct list_head list;
phys_addr_t addr;
u16 segment;
u8 bus_start;
u8 bus_end;
};

/* List to save MCFG entries */
static LIST_HEAD(pci_mcfg_list);

phys_addr_t pci_mcfg_lookup(u16 seg, struct resource *bus_res)
{
struct mcfg_entry *e;

/*
* We expect exact match, unless MCFG entry end bus covers more than
* specified by caller.
*/
list_for_each_entry(e, &pci_mcfg_list, list) {
if (e->segment == seg && e->bus_start == bus_res->start &&
e->bus_end >= bus_res->end)
return e->addr;
}

return 0;
}

static __init int pci_mcfg_parse(struct acpi_table_header *header)
{
struct acpi_table_mcfg *mcfg;
struct acpi_mcfg_allocation *mptr;
struct mcfg_entry *e, *arr;
int i, n;

if (header->length < sizeof(struct acpi_table_mcfg))
return -EINVAL;

n = (header->length - sizeof(struct acpi_table_mcfg)) /
sizeof(struct acpi_mcfg_allocation);
mcfg = (struct acpi_table_mcfg *)header;
mptr = (struct acpi_mcfg_allocation *) &mcfg[1];

arr = kcalloc(n, sizeof(*arr), GFP_KERNEL);
if (!arr)
return -ENOMEM;

for (i = 0, e = arr; i < n; i++, mptr++, e++) {
e->segment = mptr->pci_segment;
e->addr = mptr->address;
e->bus_start = mptr->start_bus_number;
e->bus_end = mptr->end_bus_number;
list_add(&e->list, &pci_mcfg_list);
}

pr_info("MCFG table detected, %d entries\n", n);
return 0;
}

/* Interface called by ACPI - parse and save MCFG table */
void __init pci_mmcfg_late_init(void)
{
int err = acpi_table_parse(ACPI_SIG_MCFG, pci_mcfg_parse);
if (err)
pr_err("Failed to parse MCFG (%d)\n", err);
}
35 changes: 35 additions & 0 deletions drivers/acpi/pci_root.c
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,36 @@ static void acpi_pci_root_validate_resources(struct device *dev,
}
}

static void acpi_pci_root_remap_iospace(struct resource_entry *entry)
{
#ifdef PCI_IOBASE
struct resource *res = entry->res;
resource_size_t cpu_addr = res->start;
resource_size_t pci_addr = cpu_addr - entry->offset;
resource_size_t length = resource_size(res);
unsigned long port;

if (pci_register_io_range(cpu_addr, length))
goto err;

port = pci_address_to_pio(cpu_addr);
if (port == (unsigned long)-1)
goto err;

res->start = port;
res->end = port + length - 1;
entry->offset = port - pci_addr;

if (pci_remap_iospace(res, cpu_addr) < 0)
goto err;

pr_info("Remapped I/O %pa to %pR\n", &cpu_addr, res);
return;
err:
res->flags |= IORESOURCE_DISABLED;
#endif
}

int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info)
{
int ret;
Expand All @@ -740,6 +770,9 @@ int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info)
"no IO and memory resources present in _CRS\n");
else {
resource_list_for_each_entry_safe(entry, tmp, list) {
if (entry->res->flags & IORESOURCE_IO)
acpi_pci_root_remap_iospace(entry);

if (entry->res->flags & IORESOURCE_DISABLED)
resource_list_destroy_entry(entry);
else
Expand Down Expand Up @@ -811,6 +844,8 @@ static void acpi_pci_root_release_info(struct pci_host_bridge *bridge)

resource_list_for_each_entry(entry, &bridge->windows) {
res = entry->res;
if (res->flags & IORESOURCE_IO)
pci_unmap_iospace(res);
if (res->parent &&
(res->flags & (IORESOURCE_MEM | IORESOURCE_IO)))
release_resource(res);
Expand Down
6 changes: 3 additions & 3 deletions drivers/pci/ecam.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci-ecam.h>
#include <linux/slab.h>

#include "ecam.h"

/*
* 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
Expand Down Expand Up @@ -52,6 +51,7 @@ struct pci_config_window *pci_ecam_create(struct device *dev,
if (!cfg)
return ERR_PTR(-ENOMEM);

cfg->parent = dev;
cfg->ops = ops;
cfg->busr.start = busr->start;
cfg->busr.end = busr->end;
Expand Down Expand Up @@ -95,7 +95,7 @@ struct pci_config_window *pci_ecam_create(struct device *dev,
}

if (ops->init) {
err = ops->init(dev, cfg);
err = ops->init(cfg);
if (err)
goto err_exit;
}
Expand Down
Loading

0 comments on commit c5cb85b

Please sign in to comment.