Merge branch 'acpi-riscv'

Merge ACPI and irqchip updates related to external interrupt controller
support on RISC-V:

 - Add ACPI device enumeration support for interrupt controller probing
   including taking dependencies into account (Sunil V L).

 - Implement ACPI-based interrupt controller probing on RISC-V (Sunil V L).

 - Add ACPI support for AIA in riscv-intc and add ACPI support to
   riscv-imsic, riscv-aplic, and sifive-plic (Sunil V L).

* acpi-riscv:
  irqchip/sifive-plic: Add ACPI support
  irqchip/riscv-aplic: Add ACPI support
  irqchip/riscv-imsic: Add ACPI support
  irqchip/riscv-imsic-state: Create separate function for DT
  irqchip/riscv-intc: Add ACPI support for AIA
  ACPI: RISC-V: Implement function to add implicit dependencies
  ACPI: RISC-V: Initialize GSI mapping structures
  ACPI: RISC-V: Implement function to reorder irqchip probe entries
  ACPI: RISC-V: Implement PCI related functionality
  ACPI: pci_link: Clear the dependencies after probe
  ACPI: bus: Add RINTC IRQ model for RISC-V
  ACPI: scan: Define weak function to populate dependencies
  ACPI: scan: Add RISC-V interrupt controllers to honor list
  ACPI: scan: Refactor dependency creation
  ACPI: bus: Add acpi_riscv_init() function
  ACPI: scan: Add a weak arch_sort_irqchip_probe() to order the IRQCHIP probe
  arm64: PCI: Migrate ACPI related functions to pci-acpi.c
This commit is contained in:
Rafael J. Wysocki 2024-09-11 21:44:22 +02:00
commit 45de40574f
25 changed files with 1116 additions and 378 deletions

View File

@ -6,28 +6,7 @@
* Copyright (C) 2014 ARM Ltd.
*/
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/pci-acpi.h>
#include <linux/pci-ecam.h>
#include <linux/slab.h>
#ifdef CONFIG_ACPI
/*
* Try to assign the IRQ number when probing a new device
*/
int pcibios_alloc_irq(struct pci_dev *dev)
{
if (!acpi_disabled)
acpi_pci_irq_enable(dev);
return 0;
}
#endif
/*
* raw_pci_read/write - Platform-specific PCI config space access.
@ -61,173 +40,3 @@ int pcibus_to_node(struct pci_bus *bus)
EXPORT_SYMBOL(pcibus_to_node);
#endif
#ifdef CONFIG_ACPI
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)
{
struct pci_config_window *cfg;
struct acpi_device *adev;
struct device *bus_dev;
if (acpi_disabled)
return 0;
cfg = bridge->bus->sysdata;
/*
* On Hyper-V there is no corresponding ACPI device for a root bridge,
* therefore ->parent is set as NULL by the driver. And set 'adev' as
* NULL in this case because there is no proper ACPI device.
*/
if (!cfg->parent)
adev = NULL;
else
adev = to_acpi_device(cfg->parent);
bus_dev = &bridge->bus->dev;
ACPI_COMPANION_SET(&bridge->dev, adev);
set_dev_node(bus_dev, acpi_get_node(acpi_device_handle(adev)));
return 0;
}
static int pci_acpi_root_prepare_resources(struct acpi_pci_root_info *ci)
{
struct resource_entry *entry, *tmp;
int status;
status = acpi_pci_probe_root_resources(ci);
resource_list_for_each_entry_safe(entry, tmp, &ci->resources) {
if (!(entry->res->flags & IORESOURCE_WINDOW))
resource_list_destroy_entry(entry);
}
return status;
}
/*
* 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 device *dev = &root->device->dev;
struct resource *bus_res = &root->secondary;
u16 seg = root->segment;
const struct pci_ecam_ops *ecam_ops;
struct resource cfgres;
struct acpi_device *adev;
struct pci_config_window *cfg;
int ret;
ret = pci_mcfg_lookup(root, &cfgres, &ecam_ops);
if (ret) {
dev_err(dev, "%04x:%pR ECAM region not found\n", seg, bus_res);
return NULL;
}
adev = acpi_resource_consumer(&cfgres);
if (adev)
dev_info(dev, "ECAM area %pR reserved by %s\n", &cfgres,
dev_name(&adev->dev));
else
dev_warn(dev, FW_BUG "ECAM area %pR not reserved in ACPI namespace\n",
&cfgres);
cfg = pci_ecam_create(dev, &cfgres, bus_res, ecam_ops);
if (IS_ERR(cfg)) {
dev_err(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(ci->ops);
kfree(ri);
}
/* Interface called from ACPI code to setup PCI host controller */
struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
struct acpi_pci_generic_root_info *ri;
struct pci_bus *bus, *child;
struct acpi_pci_root_ops *root_ops;
struct pci_host_bridge *host;
ri = kzalloc(sizeof(*ri), GFP_KERNEL);
if (!ri)
return NULL;
root_ops = kzalloc(sizeof(*root_ops), GFP_KERNEL);
if (!root_ops) {
kfree(ri);
return NULL;
}
ri->cfg = pci_acpi_setup_ecam_mapping(root);
if (!ri->cfg) {
kfree(ri);
kfree(root_ops);
return NULL;
}
root_ops->release_info = pci_acpi_generic_release_info;
root_ops->prepare_resources = pci_acpi_root_prepare_resources;
root_ops->pci_ops = (struct pci_ops *)&ri->cfg->ops->pci_ops;
bus = acpi_pci_root_create(root, root_ops, &ri->common, ri->cfg);
if (!bus)
return NULL;
/* If we must preserve the resource configuration, claim now */
host = pci_find_host_bridge(bus);
if (host->preserve_config)
pci_bus_claim_resources(bus);
/*
* Assign whatever was left unassigned. If we didn't claim above,
* this will reassign everything.
*/
pci_assign_unassigned_root_bus_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

View File

@ -13,6 +13,7 @@ config 32BIT
config RISCV
def_bool y
select ACPI_GENERIC_GSI if ACPI
select ACPI_MCFG if (ACPI && PCI)
select ACPI_PPTT if ACPI
select ACPI_REDUCED_HARDWARE_ONLY if ACPI
select ACPI_SPCR_TABLE if ACPI
@ -188,6 +189,7 @@ config RISCV
select OF_EARLY_FLATTREE
select OF_IRQ
select PCI_DOMAINS_GENERIC if PCI
select PCI_ECAM if (ACPI && PCI)
select PCI_MSI if PCI
select RISCV_ALTERNATIVE if !XIP_KERNEL
select RISCV_APLIC

View File

@ -12,8 +12,63 @@
#include <asm-generic/irq.h>
#define INVALID_CONTEXT UINT_MAX
void riscv_set_intc_hwnode_fn(struct fwnode_handle *(*fn)(void));
struct fwnode_handle *riscv_get_intc_hwnode(void);
#ifdef CONFIG_ACPI
enum riscv_irqchip_type {
ACPI_RISCV_IRQCHIP_INTC = 0x00,
ACPI_RISCV_IRQCHIP_IMSIC = 0x01,
ACPI_RISCV_IRQCHIP_PLIC = 0x02,
ACPI_RISCV_IRQCHIP_APLIC = 0x03,
};
int riscv_acpi_get_gsi_info(struct fwnode_handle *fwnode, u32 *gsi_base,
u32 *id, u32 *nr_irqs, u32 *nr_idcs);
struct fwnode_handle *riscv_acpi_get_gsi_domain_id(u32 gsi);
unsigned long acpi_rintc_index_to_hartid(u32 index);
unsigned long acpi_rintc_ext_parent_to_hartid(unsigned int plic_id, unsigned int ctxt_idx);
unsigned int acpi_rintc_get_plic_nr_contexts(unsigned int plic_id);
unsigned int acpi_rintc_get_plic_context(unsigned int plic_id, unsigned int ctxt_idx);
int __init acpi_rintc_get_imsic_mmio_info(u32 index, struct resource *res);
#else
static inline int riscv_acpi_get_gsi_info(struct fwnode_handle *fwnode, u32 *gsi_base,
u32 *id, u32 *nr_irqs, u32 *nr_idcs)
{
return 0;
}
static inline unsigned long acpi_rintc_index_to_hartid(u32 index)
{
return INVALID_HARTID;
}
static inline unsigned long acpi_rintc_ext_parent_to_hartid(unsigned int plic_id,
unsigned int ctxt_idx)
{
return INVALID_HARTID;
}
static inline unsigned int acpi_rintc_get_plic_nr_contexts(unsigned int plic_id)
{
return INVALID_CONTEXT;
}
static inline unsigned int acpi_rintc_get_plic_context(unsigned int plic_id, unsigned int ctxt_idx)
{
return INVALID_CONTEXT;
}
static inline int __init acpi_rintc_get_imsic_mmio_info(u32 index, struct resource *res)
{
return 0;
}
#endif /* CONFIG_ACPI */
#endif /* _ASM_RISCV_IRQ_H */

View File

@ -311,29 +311,26 @@ void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size)
#ifdef CONFIG_PCI
/*
* These interfaces are defined just to enable building ACPI core.
* TODO: Update it with actual implementation when external interrupt
* controller support is added in RISC-V ACPI.
* raw_pci_read/write - Platform-specific PCI config space access.
*/
int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn,
int reg, int len, u32 *val)
int raw_pci_read(unsigned int domain, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *val)
{
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)
int raw_pci_write(unsigned int domain, unsigned int bus,
unsigned int devfn, int reg, int len, u32 val)
{
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);
}
int acpi_pci_bus_find_domain_nr(struct pci_bus *bus)
{
return -1;
}
struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
return NULL;
}
#endif /* CONFIG_PCI */

View File

@ -1203,6 +1203,9 @@ static int __init acpi_bus_init_irq(void)
case ACPI_IRQ_MODEL_LPIC:
message = "LPIC";
break;
case ACPI_IRQ_MODEL_RINTC:
message = "RINTC";
break;
default:
pr_info("Unknown interrupt routing model\n");
return -ENODEV;
@ -1459,6 +1462,7 @@ static int __init acpi_init(void)
acpi_hest_init();
acpi_ghes_init();
acpi_arm_init();
acpi_riscv_init();
acpi_scan_init();
acpi_ec_init();
acpi_debugfs_init();

View File

@ -748,6 +748,8 @@ static int acpi_pci_link_add(struct acpi_device *device,
if (result)
kfree(link);
acpi_dev_clear_dependencies(device);
return result < 0 ? result : 1;
}

View File

@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += rhct.o
obj-y += rhct.o init.o irq.o
obj-$(CONFIG_ACPI_PROCESSOR_IDLE) += cpuidle.o
obj-$(CONFIG_ACPI_CPPC_LIB) += cppc.o

13
drivers/acpi/riscv/init.c Normal file
View File

@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023-2024, Ventana Micro Systems Inc
* Author: Sunil V L <sunilvl@ventanamicro.com>
*/
#include <linux/acpi.h>
#include "init.h"
void __init acpi_riscv_init(void)
{
riscv_acpi_init_gsi_mapping();
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <linux/init.h>
void __init riscv_acpi_init_gsi_mapping(void);

335
drivers/acpi/riscv/irq.c Normal file
View File

@ -0,0 +1,335 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023-2024, Ventana Micro Systems Inc
* Author: Sunil V L <sunilvl@ventanamicro.com>
*/
#include <linux/acpi.h>
#include <linux/sort.h>
#include <linux/irq.h>
#include "init.h"
struct riscv_ext_intc_list {
acpi_handle handle;
u32 gsi_base;
u32 nr_irqs;
u32 nr_idcs;
u32 id;
u32 type;
struct list_head list;
};
struct acpi_irq_dep_ctx {
int rc;
unsigned int index;
acpi_handle handle;
};
LIST_HEAD(ext_intc_list);
static int irqchip_cmp_func(const void *in0, const void *in1)
{
struct acpi_probe_entry *elem0 = (struct acpi_probe_entry *)in0;
struct acpi_probe_entry *elem1 = (struct acpi_probe_entry *)in1;
return (elem0->type > elem1->type) - (elem0->type < elem1->type);
}
/*
* On RISC-V, RINTC structures in MADT should be probed before any other
* interrupt controller structures and IMSIC before APLIC. The interrupt
* controller subtypes in MADT of ACPI spec for RISC-V are defined in
* the incremental order like RINTC(24)->IMSIC(25)->APLIC(26)->PLIC(27).
* Hence, simply sorting the subtypes in incremental order will
* establish the required order.
*/
void arch_sort_irqchip_probe(struct acpi_probe_entry *ap_head, int nr)
{
struct acpi_probe_entry *ape = ap_head;
if (nr == 1 || !ACPI_COMPARE_NAMESEG(ACPI_SIG_MADT, ape->id))
return;
sort(ape, nr, sizeof(*ape), irqchip_cmp_func, NULL);
}
static acpi_status riscv_acpi_update_gsi_handle(u32 gsi_base, acpi_handle handle)
{
struct riscv_ext_intc_list *ext_intc_element;
struct list_head *i, *tmp;
list_for_each_safe(i, tmp, &ext_intc_list) {
ext_intc_element = list_entry(i, struct riscv_ext_intc_list, list);
if (gsi_base == ext_intc_element->gsi_base) {
ext_intc_element->handle = handle;
return AE_OK;
}
}
return AE_NOT_FOUND;
}
int riscv_acpi_get_gsi_info(struct fwnode_handle *fwnode, u32 *gsi_base,
u32 *id, u32 *nr_irqs, u32 *nr_idcs)
{
struct riscv_ext_intc_list *ext_intc_element;
struct list_head *i;
list_for_each(i, &ext_intc_list) {
ext_intc_element = list_entry(i, struct riscv_ext_intc_list, list);
if (ext_intc_element->handle == ACPI_HANDLE_FWNODE(fwnode)) {
*gsi_base = ext_intc_element->gsi_base;
*id = ext_intc_element->id;
*nr_irqs = ext_intc_element->nr_irqs;
if (nr_idcs)
*nr_idcs = ext_intc_element->nr_idcs;
return 0;
}
}
return -ENODEV;
}
struct fwnode_handle *riscv_acpi_get_gsi_domain_id(u32 gsi)
{
struct riscv_ext_intc_list *ext_intc_element;
struct acpi_device *adev;
struct list_head *i;
list_for_each(i, &ext_intc_list) {
ext_intc_element = list_entry(i, struct riscv_ext_intc_list, list);
if (gsi >= ext_intc_element->gsi_base &&
gsi < (ext_intc_element->gsi_base + ext_intc_element->nr_irqs)) {
adev = acpi_fetch_acpi_dev(ext_intc_element->handle);
if (!adev)
return NULL;
return acpi_fwnode_handle(adev);
}
}
return NULL;
}
static int __init riscv_acpi_register_ext_intc(u32 gsi_base, u32 nr_irqs, u32 nr_idcs,
u32 id, u32 type)
{
struct riscv_ext_intc_list *ext_intc_element;
ext_intc_element = kzalloc(sizeof(*ext_intc_element), GFP_KERNEL);
if (!ext_intc_element)
return -ENOMEM;
ext_intc_element->gsi_base = gsi_base;
ext_intc_element->nr_irqs = nr_irqs;
ext_intc_element->nr_idcs = nr_idcs;
ext_intc_element->id = id;
list_add_tail(&ext_intc_element->list, &ext_intc_list);
return 0;
}
static acpi_status __init riscv_acpi_create_gsi_map(acpi_handle handle, u32 level,
void *context, void **return_value)
{
acpi_status status;
u64 gbase;
if (!acpi_has_method(handle, "_GSB")) {
acpi_handle_err(handle, "_GSB method not found\n");
return AE_ERROR;
}
status = acpi_evaluate_integer(handle, "_GSB", NULL, &gbase);
if (ACPI_FAILURE(status)) {
acpi_handle_err(handle, "failed to evaluate _GSB method\n");
return status;
}
status = riscv_acpi_update_gsi_handle((u32)gbase, handle);
if (ACPI_FAILURE(status)) {
acpi_handle_err(handle, "failed to find the GSI mapping entry\n");
return status;
}
return AE_OK;
}
static int __init riscv_acpi_aplic_parse_madt(union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_aplic *aplic = (struct acpi_madt_aplic *)header;
return riscv_acpi_register_ext_intc(aplic->gsi_base, aplic->num_sources, aplic->num_idcs,
aplic->id, ACPI_RISCV_IRQCHIP_APLIC);
}
static int __init riscv_acpi_plic_parse_madt(union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_plic *plic = (struct acpi_madt_plic *)header;
return riscv_acpi_register_ext_intc(plic->gsi_base, plic->num_irqs, 0,
plic->id, ACPI_RISCV_IRQCHIP_PLIC);
}
void __init riscv_acpi_init_gsi_mapping(void)
{
/* There can be either PLIC or APLIC */
if (acpi_table_parse_madt(ACPI_MADT_TYPE_PLIC, riscv_acpi_plic_parse_madt, 0) > 0) {
acpi_get_devices("RSCV0001", riscv_acpi_create_gsi_map, NULL, NULL);
return;
}
if (acpi_table_parse_madt(ACPI_MADT_TYPE_APLIC, riscv_acpi_aplic_parse_madt, 0) > 0)
acpi_get_devices("RSCV0002", riscv_acpi_create_gsi_map, NULL, NULL);
}
static acpi_handle riscv_acpi_get_gsi_handle(u32 gsi)
{
struct riscv_ext_intc_list *ext_intc_element;
struct list_head *i;
list_for_each(i, &ext_intc_list) {
ext_intc_element = list_entry(i, struct riscv_ext_intc_list, list);
if (gsi >= ext_intc_element->gsi_base &&
gsi < (ext_intc_element->gsi_base + ext_intc_element->nr_irqs))
return ext_intc_element->handle;
}
return NULL;
}
static acpi_status riscv_acpi_irq_get_parent(struct acpi_resource *ares, void *context)
{
struct acpi_irq_dep_ctx *ctx = context;
struct acpi_resource_irq *irq;
struct acpi_resource_extended_irq *eirq;
switch (ares->type) {
case ACPI_RESOURCE_TYPE_IRQ:
irq = &ares->data.irq;
if (ctx->index >= irq->interrupt_count) {
ctx->index -= irq->interrupt_count;
return AE_OK;
}
ctx->handle = riscv_acpi_get_gsi_handle(irq->interrupts[ctx->index]);
return AE_CTRL_TERMINATE;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
eirq = &ares->data.extended_irq;
if (eirq->producer_consumer == ACPI_PRODUCER)
return AE_OK;
if (ctx->index >= eirq->interrupt_count) {
ctx->index -= eirq->interrupt_count;
return AE_OK;
}
/* Support GSIs only */
if (eirq->resource_source.string_length)
return AE_OK;
ctx->handle = riscv_acpi_get_gsi_handle(eirq->interrupts[ctx->index]);
return AE_CTRL_TERMINATE;
}
return AE_OK;
}
static int riscv_acpi_irq_get_dep(acpi_handle handle, unsigned int index, acpi_handle *gsi_handle)
{
struct acpi_irq_dep_ctx ctx = {-EINVAL, index, NULL};
if (!gsi_handle)
return 0;
acpi_walk_resources(handle, METHOD_NAME__CRS, riscv_acpi_irq_get_parent, &ctx);
*gsi_handle = ctx.handle;
if (*gsi_handle)
return 1;
return 0;
}
static u32 riscv_acpi_add_prt_dep(acpi_handle handle)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_pci_routing_table *entry;
struct acpi_handle_list dep_devices;
acpi_handle gsi_handle;
acpi_handle link_handle;
acpi_status status;
u32 count = 0;
status = acpi_get_irq_routing_table(handle, &buffer);
if (ACPI_FAILURE(status)) {
acpi_handle_err(handle, "failed to get IRQ routing table\n");
kfree(buffer.pointer);
return 0;
}
entry = buffer.pointer;
while (entry && (entry->length > 0)) {
if (entry->source[0]) {
acpi_get_handle(handle, entry->source, &link_handle);
dep_devices.count = 1;
dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL);
if (!dep_devices.handles) {
acpi_handle_err(handle, "failed to allocate memory\n");
continue;
}
dep_devices.handles[0] = link_handle;
count += acpi_scan_add_dep(handle, &dep_devices);
} else {
gsi_handle = riscv_acpi_get_gsi_handle(entry->source_index);
dep_devices.count = 1;
dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL);
if (!dep_devices.handles) {
acpi_handle_err(handle, "failed to allocate memory\n");
continue;
}
dep_devices.handles[0] = gsi_handle;
count += acpi_scan_add_dep(handle, &dep_devices);
}
entry = (struct acpi_pci_routing_table *)
((unsigned long)entry + entry->length);
}
kfree(buffer.pointer);
return count;
}
static u32 riscv_acpi_add_irq_dep(acpi_handle handle)
{
struct acpi_handle_list dep_devices;
acpi_handle gsi_handle;
u32 count = 0;
int i;
for (i = 0;
riscv_acpi_irq_get_dep(handle, i, &gsi_handle);
i++) {
dep_devices.count = 1;
dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL);
if (!dep_devices.handles) {
acpi_handle_err(handle, "failed to allocate memory\n");
continue;
}
dep_devices.handles[0] = gsi_handle;
count += acpi_scan_add_dep(handle, &dep_devices);
}
return count;
}
u32 arch_acpi_add_auto_dep(acpi_handle handle)
{
if (acpi_has_method(handle, "_PRT"))
return riscv_acpi_add_prt_dep(handle);
return riscv_acpi_add_irq_dep(handle);
}

View File

@ -861,6 +861,9 @@ static const char * const acpi_honor_dep_ids[] = {
"INTC1095", /* IVSC (ADL) driver must be loaded to allow i2c access to camera sensors */
"INTC100A", /* IVSC (RPL) driver must be loaded to allow i2c access to camera sensors */
"INTC10CF", /* IVSC (MTL) driver must be loaded to allow i2c access to camera sensors */
"RSCV0001", /* RISC-V PLIC */
"RSCV0002", /* RISC-V APLIC */
"PNP0C0F", /* PCI Link Device */
NULL
};
@ -2013,6 +2016,49 @@ void acpi_scan_hotplug_enabled(struct acpi_hotplug_profile *hotplug, bool val)
mutex_unlock(&acpi_scan_lock);
}
int acpi_scan_add_dep(acpi_handle handle, struct acpi_handle_list *dep_devices)
{
u32 count;
int i;
for (count = 0, i = 0; i < dep_devices->count; i++) {
struct acpi_device_info *info;
struct acpi_dep_data *dep;
bool skip, honor_dep;
acpi_status status;
status = acpi_get_object_info(dep_devices->handles[i], &info);
if (ACPI_FAILURE(status)) {
acpi_handle_debug(handle, "Error reading _DEP device info\n");
continue;
}
skip = acpi_info_matches_ids(info, acpi_ignore_dep_ids);
honor_dep = acpi_info_matches_ids(info, acpi_honor_dep_ids);
kfree(info);
if (skip)
continue;
dep = kzalloc(sizeof(*dep), GFP_KERNEL);
if (!dep)
continue;
count++;
dep->supplier = dep_devices->handles[i];
dep->consumer = handle;
dep->honor_dep = honor_dep;
mutex_lock(&acpi_dep_list_lock);
list_add_tail(&dep->node, &acpi_dep_list);
mutex_unlock(&acpi_dep_list_lock);
}
acpi_handle_list_free(dep_devices);
return count;
}
static void acpi_scan_init_hotplug(struct acpi_device *adev)
{
struct acpi_hardware_id *hwid;
@ -2032,11 +2078,21 @@ static void acpi_scan_init_hotplug(struct acpi_device *adev)
}
}
u32 __weak arch_acpi_add_auto_dep(acpi_handle handle) { return 0; }
static u32 acpi_scan_check_dep(acpi_handle handle)
{
struct acpi_handle_list dep_devices;
u32 count;
int i;
u32 count = 0;
/*
* Some architectures like RISC-V need to add dependencies for
* all devices which use GSI to the interrupt controller so that
* interrupt controller is probed before any of those devices.
* Instead of mandating _DEP on all the devices, detect the
* dependency and add automatically.
*/
count += arch_acpi_add_auto_dep(handle);
/*
* Check for _HID here to avoid deferring the enumeration of:
@ -2045,48 +2101,14 @@ static u32 acpi_scan_check_dep(acpi_handle handle)
* Still, checking for _HID catches more then just these cases ...
*/
if (!acpi_has_method(handle, "_DEP") || !acpi_has_method(handle, "_HID"))
return 0;
return count;
if (!acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices)) {
acpi_handle_debug(handle, "Failed to evaluate _DEP.\n");
return 0;
return count;
}
for (count = 0, i = 0; i < dep_devices.count; i++) {
struct acpi_device_info *info;
struct acpi_dep_data *dep;
bool skip, honor_dep;
acpi_status status;
status = acpi_get_object_info(dep_devices.handles[i], &info);
if (ACPI_FAILURE(status)) {
acpi_handle_debug(handle, "Error reading _DEP device info\n");
continue;
}
skip = acpi_info_matches_ids(info, acpi_ignore_dep_ids);
honor_dep = acpi_info_matches_ids(info, acpi_honor_dep_ids);
kfree(info);
if (skip)
continue;
dep = kzalloc(sizeof(*dep), GFP_KERNEL);
if (!dep)
continue;
count++;
dep->supplier = dep_devices.handles[i];
dep->consumer = handle;
dep->honor_dep = honor_dep;
mutex_lock(&acpi_dep_list_lock);
list_add_tail(&dep->node , &acpi_dep_list);
mutex_unlock(&acpi_dep_list_lock);
}
acpi_handle_list_free(&dep_devices);
count += acpi_scan_add_dep(handle, &dep_devices);
return count;
}
@ -2757,6 +2779,8 @@ static int __init acpi_match_madt(union acpi_subtable_headers *header,
return 0;
}
void __weak arch_sort_irqchip_probe(struct acpi_probe_entry *ap_head, int nr) { }
int __init __acpi_probe_device_table(struct acpi_probe_entry *ap_head, int nr)
{
int count = 0;
@ -2765,6 +2789,7 @@ int __init __acpi_probe_device_table(struct acpi_probe_entry *ap_head, int nr)
return 0;
mutex_lock(&acpi_probe_mutex);
arch_sort_irqchip_probe(ap_head, nr);
for (ape = ap_head; nr; ape++, nr--) {
if (ACPI_COMPARE_NAMESEG(ACPI_SIG_MADT, ape->id)) {
acpi_probe_count = 0;

View File

@ -4,6 +4,7 @@
* Copyright (C) 2022 Ventana Micro Systems Inc.
*/
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/cpu.h>
@ -189,17 +190,22 @@ static int aplic_direct_starting_cpu(unsigned int cpu)
}
static int aplic_direct_parse_parent_hwirq(struct device *dev, u32 index,
u32 *parent_hwirq, unsigned long *parent_hartid)
u32 *parent_hwirq, unsigned long *parent_hartid,
struct aplic_priv *priv)
{
struct of_phandle_args parent;
unsigned long hartid;
int rc;
/*
* Currently, only OF fwnode is supported so extend this
* function for ACPI support.
*/
if (!is_of_node(dev->fwnode))
return -EINVAL;
if (!is_of_node(dev->fwnode)) {
hartid = acpi_rintc_ext_parent_to_hartid(priv->acpi_aplic_id, index);
if (hartid == INVALID_HARTID)
return -ENODEV;
*parent_hartid = hartid;
*parent_hwirq = RV_IRQ_EXT;
return 0;
}
rc = of_irq_parse_one(to_of_node(dev->fwnode), index, &parent);
if (rc)
@ -237,7 +243,7 @@ int aplic_direct_setup(struct device *dev, void __iomem *regs)
/* Setup per-CPU IDC and target CPU mask */
current_cpu = get_cpu();
for (i = 0; i < priv->nr_idcs; i++) {
rc = aplic_direct_parse_parent_hwirq(dev, i, &hwirq, &hartid);
rc = aplic_direct_parse_parent_hwirq(dev, i, &hwirq, &hartid, priv);
if (rc) {
dev_warn(dev, "parent irq for IDC%d not found\n", i);
continue;

View File

@ -4,8 +4,10 @@
* Copyright (C) 2022 Ventana Micro Systems Inc.
*/
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/irqchip/riscv-aplic.h>
#include <linux/irqchip/riscv-imsic.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@ -125,23 +127,26 @@ static void aplic_init_hw_irqs(struct aplic_priv *priv)
writel(0, priv->regs + APLIC_DOMAINCFG);
}
#ifdef CONFIG_ACPI
static const struct acpi_device_id aplic_acpi_match[] = {
{ "RSCV0002", 0 },
{}
};
MODULE_DEVICE_TABLE(acpi, aplic_acpi_match);
#endif
int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *regs)
{
struct device_node *np = to_of_node(dev->fwnode);
struct of_phandle_args parent;
int rc;
/*
* Currently, only OF fwnode is supported so extend this
* function for ACPI support.
*/
if (!np)
return -EINVAL;
/* Save device pointer and register base */
priv->dev = dev;
priv->regs = regs;
if (np) {
/* Find out number of interrupt sources */
rc = of_property_read_u32(np, "riscv,num-sources", &priv->nr_irqs);
if (rc) {
@ -159,6 +164,14 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *
while (!of_irq_parse_one(np, priv->nr_idcs, &parent))
priv->nr_idcs++;
}
} else {
rc = riscv_acpi_get_gsi_info(dev->fwnode, &priv->gsi_base, &priv->acpi_aplic_id,
&priv->nr_irqs, &priv->nr_idcs);
if (rc) {
dev_err(dev, "failed to find GSI mapping\n");
return rc;
}
}
/* Setup initial state APLIC interrupts */
aplic_init_hw_irqs(priv);
@ -184,7 +197,11 @@ static int aplic_probe(struct platform_device *pdev)
* If msi-parent property is present then setup APLIC MSI
* mode otherwise setup APLIC direct mode.
*/
if (is_of_node(dev->fwnode))
msi_mode = of_property_present(to_of_node(dev->fwnode), "msi-parent");
else
msi_mode = imsic_acpi_get_fwnode(NULL) ? 1 : 0;
if (msi_mode)
rc = aplic_msi_setup(dev, regs);
else
@ -192,6 +209,11 @@ static int aplic_probe(struct platform_device *pdev)
if (rc)
dev_err(dev, "failed to setup APLIC in %s mode\n", msi_mode ? "MSI" : "direct");
#ifdef CONFIG_ACPI
if (!acpi_disabled)
acpi_dev_clear_dependencies(ACPI_COMPANION(dev));
#endif
return rc;
}
@ -204,6 +226,7 @@ static struct platform_driver aplic_driver = {
.driver = {
.name = "riscv-aplic",
.of_match_table = aplic_match,
.acpi_match_table = ACPI_PTR(aplic_acpi_match),
},
.probe = aplic_probe,
};

View File

@ -28,6 +28,7 @@ struct aplic_priv {
u32 gsi_base;
u32 nr_irqs;
u32 nr_idcs;
u32 acpi_aplic_id;
void __iomem *regs;
struct aplic_msicfg msicfg;
};

View File

@ -175,6 +175,7 @@ static const struct msi_domain_template aplic_msi_template = {
int aplic_msi_setup(struct device *dev, void __iomem *regs)
{
const struct imsic_global_config *imsic_global;
struct irq_domain *msi_domain;
struct aplic_priv *priv;
struct aplic_msicfg *mc;
phys_addr_t pa;
@ -257,8 +258,14 @@ int aplic_msi_setup(struct device *dev, void __iomem *regs)
* IMSIC and the IMSIC MSI domains are created later through
* the platform driver probing so we set it explicitly here.
*/
if (is_of_node(dev->fwnode))
if (is_of_node(dev->fwnode)) {
of_msi_configure(dev, to_of_node(dev->fwnode));
} else {
msi_domain = irq_find_matching_fwnode(imsic_acpi_get_fwnode(dev),
DOMAIN_BUS_PLATFORM_MSI);
if (msi_domain)
dev_set_msi_domain(dev, msi_domain);
}
}
if (!msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, &aplic_msi_template,

View File

@ -5,13 +5,16 @@
*/
#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/acpi.h>
#include <linux/cpu.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqchip/riscv-imsic.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <linux/smp.h>
@ -182,7 +185,7 @@ static int __init imsic_early_dt_init(struct device_node *node, struct device_no
int rc;
/* Setup IMSIC state */
rc = imsic_setup_state(fwnode);
rc = imsic_setup_state(fwnode, NULL);
if (rc) {
pr_err("%pfwP: failed to setup state (error %d)\n", fwnode, rc);
return rc;
@ -199,3 +202,62 @@ static int __init imsic_early_dt_init(struct device_node *node, struct device_no
}
IRQCHIP_DECLARE(riscv_imsic, "riscv,imsics", imsic_early_dt_init);
#ifdef CONFIG_ACPI
static struct fwnode_handle *imsic_acpi_fwnode;
struct fwnode_handle *imsic_acpi_get_fwnode(struct device *dev)
{
return imsic_acpi_fwnode;
}
static int __init imsic_early_acpi_init(union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_imsic *imsic = (struct acpi_madt_imsic *)header;
int rc;
imsic_acpi_fwnode = irq_domain_alloc_named_fwnode("imsic");
if (!imsic_acpi_fwnode) {
pr_err("unable to allocate IMSIC FW node\n");
return -ENOMEM;
}
/* Setup IMSIC state */
rc = imsic_setup_state(imsic_acpi_fwnode, imsic);
if (rc) {
pr_err("%pfwP: failed to setup state (error %d)\n", imsic_acpi_fwnode, rc);
return rc;
}
/* Do early setup of IMSIC state and IPIs */
rc = imsic_early_probe(imsic_acpi_fwnode);
if (rc) {
irq_domain_free_fwnode(imsic_acpi_fwnode);
imsic_acpi_fwnode = NULL;
return rc;
}
rc = imsic_platform_acpi_probe(imsic_acpi_fwnode);
#ifdef CONFIG_PCI
if (!rc)
pci_msi_register_fwnode_provider(&imsic_acpi_get_fwnode);
#endif
if (rc)
pr_err("%pfwP: failed to register IMSIC for MSI functionality (error %d)\n",
imsic_acpi_fwnode, rc);
/*
* Even if imsic_platform_acpi_probe() fails, the IPI part of IMSIC can
* continue to work. So, no need to return failure. This is similar to
* DT where IPI works but MSI probe fails for some reason.
*/
return 0;
}
IRQCHIP_ACPI_DECLARE(riscv_imsic, ACPI_MADT_TYPE_IMSIC, NULL,
1, imsic_early_acpi_init);
#endif

View File

@ -5,6 +5,7 @@
*/
#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/acpi.h>
#include <linux/bitmap.h>
#include <linux/cpu.h>
#include <linux/interrupt.h>
@ -348,18 +349,37 @@ int imsic_irqdomain_init(void)
return 0;
}
static int imsic_platform_probe(struct platform_device *pdev)
static int imsic_platform_probe_common(struct fwnode_handle *fwnode)
{
struct device *dev = &pdev->dev;
if (imsic && imsic->fwnode != dev->fwnode) {
dev_err(dev, "fwnode mismatch\n");
if (imsic && imsic->fwnode != fwnode) {
pr_err("%pfwP: fwnode mismatch\n", fwnode);
return -ENODEV;
}
return imsic_irqdomain_init();
}
static int imsic_platform_dt_probe(struct platform_device *pdev)
{
return imsic_platform_probe_common(pdev->dev.fwnode);
}
#ifdef CONFIG_ACPI
/*
* On ACPI based systems, PCI enumeration happens early during boot in
* acpi_scan_init(). PCI enumeration expects MSI domain setup before
* it calls pci_set_msi_domain(). Hence, unlike in DT where
* imsic-platform drive probe happens late during boot, ACPI based
* systems need to setup the MSI domain early.
*/
int imsic_platform_acpi_probe(struct fwnode_handle *fwnode)
{
return imsic_platform_probe_common(fwnode);
}
#endif
static const struct of_device_id imsic_platform_match[] = {
{ .compatible = "riscv,imsics" },
{}
@ -370,6 +390,6 @@ static struct platform_driver imsic_platform_driver = {
.name = "riscv-imsic",
.of_match_table = imsic_platform_match,
},
.probe = imsic_platform_probe,
.probe = imsic_platform_dt_probe,
};
builtin_platform_driver(imsic_platform_driver);

View File

@ -5,6 +5,7 @@
*/
#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/acpi.h>
#include <linux/cpu.h>
#include <linux/bitmap.h>
#include <linux/interrupt.h>
@ -510,73 +511,11 @@ static int __init imsic_matrix_init(void)
return 0;
}
static int __init imsic_get_parent_hartid(struct fwnode_handle *fwnode,
u32 index, unsigned long *hartid)
{
struct of_phandle_args parent;
int rc;
/*
* Currently, only OF fwnode is supported so extend this
* function for ACPI support.
*/
if (!is_of_node(fwnode))
return -EINVAL;
rc = of_irq_parse_one(to_of_node(fwnode), index, &parent);
if (rc)
return rc;
/*
* Skip interrupts other than external interrupts for
* current privilege level.
*/
if (parent.args[0] != RV_IRQ_EXT)
return -EINVAL;
return riscv_of_parent_hartid(parent.np, hartid);
}
static int __init imsic_get_mmio_resource(struct fwnode_handle *fwnode,
u32 index, struct resource *res)
{
/*
* Currently, only OF fwnode is supported so extend this
* function for ACPI support.
*/
if (!is_of_node(fwnode))
return -EINVAL;
return of_address_to_resource(to_of_node(fwnode), index, res);
}
static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
static int __init imsic_populate_global_dt(struct fwnode_handle *fwnode,
struct imsic_global_config *global,
u32 *nr_parent_irqs,
u32 *nr_mmios)
u32 *nr_parent_irqs)
{
unsigned long hartid;
struct resource res;
int rc;
u32 i;
/*
* Currently, only OF fwnode is supported so extend this
* function for ACPI support.
*/
if (!is_of_node(fwnode))
return -EINVAL;
*nr_parent_irqs = 0;
*nr_mmios = 0;
/* Find number of parent interrupts */
while (!imsic_get_parent_hartid(fwnode, *nr_parent_irqs, &hartid))
(*nr_parent_irqs)++;
if (!*nr_parent_irqs) {
pr_err("%pfwP: no parent irqs available\n", fwnode);
return -EINVAL;
}
/* Find number of guest index bits in MSI address */
rc = of_property_read_u32(to_of_node(fwnode), "riscv,guest-index-bits",
@ -623,6 +562,93 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
if (rc)
global->nr_guest_ids = global->nr_ids;
return 0;
}
static int __init imsic_populate_global_acpi(struct fwnode_handle *fwnode,
struct imsic_global_config *global,
u32 *nr_parent_irqs, void *opaque)
{
struct acpi_madt_imsic *imsic = (struct acpi_madt_imsic *)opaque;
global->guest_index_bits = imsic->guest_index_bits;
global->hart_index_bits = imsic->hart_index_bits;
global->group_index_bits = imsic->group_index_bits;
global->group_index_shift = imsic->group_index_shift;
global->nr_ids = imsic->num_ids;
global->nr_guest_ids = imsic->num_guest_ids;
return 0;
}
static int __init imsic_get_parent_hartid(struct fwnode_handle *fwnode,
u32 index, unsigned long *hartid)
{
struct of_phandle_args parent;
int rc;
if (!is_of_node(fwnode)) {
if (hartid)
*hartid = acpi_rintc_index_to_hartid(index);
if (!hartid || (*hartid == INVALID_HARTID))
return -EINVAL;
return 0;
}
rc = of_irq_parse_one(to_of_node(fwnode), index, &parent);
if (rc)
return rc;
/*
* Skip interrupts other than external interrupts for
* current privilege level.
*/
if (parent.args[0] != RV_IRQ_EXT)
return -EINVAL;
return riscv_of_parent_hartid(parent.np, hartid);
}
static int __init imsic_get_mmio_resource(struct fwnode_handle *fwnode,
u32 index, struct resource *res)
{
if (!is_of_node(fwnode))
return acpi_rintc_get_imsic_mmio_info(index, res);
return of_address_to_resource(to_of_node(fwnode), index, res);
}
static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
struct imsic_global_config *global,
u32 *nr_parent_irqs,
u32 *nr_mmios,
void *opaque)
{
unsigned long hartid;
struct resource res;
int rc;
u32 i;
*nr_parent_irqs = 0;
*nr_mmios = 0;
/* Find number of parent interrupts */
while (!imsic_get_parent_hartid(fwnode, *nr_parent_irqs, &hartid))
(*nr_parent_irqs)++;
if (!*nr_parent_irqs) {
pr_err("%pfwP: no parent irqs available\n", fwnode);
return -EINVAL;
}
if (is_of_node(fwnode))
rc = imsic_populate_global_dt(fwnode, global, nr_parent_irqs);
else
rc = imsic_populate_global_acpi(fwnode, global, nr_parent_irqs, opaque);
if (rc)
return rc;
/* Sanity check guest index bits */
i = BITS_PER_LONG - IMSIC_MMIO_PAGE_SHIFT;
if (i < global->guest_index_bits) {
@ -688,7 +714,7 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
return 0;
}
int __init imsic_setup_state(struct fwnode_handle *fwnode)
int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque)
{
u32 i, j, index, nr_parent_irqs, nr_mmios, nr_handlers = 0;
struct imsic_global_config *global;
@ -729,7 +755,7 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode)
}
/* Parse IMSIC fwnode */
rc = imsic_parse_fwnode(fwnode, global, &nr_parent_irqs, &nr_mmios);
rc = imsic_parse_fwnode(fwnode, global, &nr_parent_irqs, &nr_mmios, opaque);
if (rc)
goto out_free_local;

View File

@ -102,7 +102,7 @@ void imsic_vector_debug_show_summary(struct seq_file *m, int ind);
void imsic_state_online(void);
void imsic_state_offline(void);
int imsic_setup_state(struct fwnode_handle *fwnode);
int imsic_setup_state(struct fwnode_handle *fwnode, void *opaque);
int imsic_irqdomain_init(void);
#endif

View File

@ -250,6 +250,85 @@ IRQCHIP_DECLARE(andes, "andestech,cpu-intc", riscv_intc_init);
#ifdef CONFIG_ACPI
struct rintc_data {
union {
u32 ext_intc_id;
struct {
u32 context_id : 16,
reserved : 8,
aplic_plic_id : 8;
};
};
unsigned long hart_id;
u64 imsic_addr;
u32 imsic_size;
};
static u32 nr_rintc;
static struct rintc_data *rintc_acpi_data[NR_CPUS];
#define for_each_matching_plic(_plic_id) \
unsigned int _plic; \
\
for (_plic = 0; _plic < nr_rintc; _plic++) \
if (rintc_acpi_data[_plic]->aplic_plic_id != _plic_id) \
continue; \
else
unsigned int acpi_rintc_get_plic_nr_contexts(unsigned int plic_id)
{
unsigned int nctx = 0;
for_each_matching_plic(plic_id)
nctx++;
return nctx;
}
static struct rintc_data *get_plic_context(unsigned int plic_id, unsigned int ctxt_idx)
{
unsigned int ctxt = 0;
for_each_matching_plic(plic_id) {
if (ctxt == ctxt_idx)
return rintc_acpi_data[_plic];
ctxt++;
}
return NULL;
}
unsigned long acpi_rintc_ext_parent_to_hartid(unsigned int plic_id, unsigned int ctxt_idx)
{
struct rintc_data *data = get_plic_context(plic_id, ctxt_idx);
return data ? data->hart_id : INVALID_HARTID;
}
unsigned int acpi_rintc_get_plic_context(unsigned int plic_id, unsigned int ctxt_idx)
{
struct rintc_data *data = get_plic_context(plic_id, ctxt_idx);
return data ? data->context_id : INVALID_CONTEXT;
}
unsigned long acpi_rintc_index_to_hartid(u32 index)
{
return index >= nr_rintc ? INVALID_HARTID : rintc_acpi_data[index]->hart_id;
}
int acpi_rintc_get_imsic_mmio_info(u32 index, struct resource *res)
{
if (index >= nr_rintc)
return -1;
res->start = rintc_acpi_data[index]->imsic_addr;
res->end = res->start + rintc_acpi_data[index]->imsic_size - 1;
res->flags = IORESOURCE_MEM;
return 0;
}
static int __init riscv_intc_acpi_init(union acpi_subtable_headers *header,
const unsigned long end)
{
@ -258,6 +337,15 @@ static int __init riscv_intc_acpi_init(union acpi_subtable_headers *header,
int rc;
rintc = (struct acpi_madt_rintc *)header;
rintc_acpi_data[nr_rintc] = kzalloc(sizeof(*rintc_acpi_data[0]), GFP_KERNEL);
if (!rintc_acpi_data[nr_rintc])
return -ENOMEM;
rintc_acpi_data[nr_rintc]->ext_intc_id = rintc->ext_intc_id;
rintc_acpi_data[nr_rintc]->hart_id = rintc->hart_id;
rintc_acpi_data[nr_rintc]->imsic_addr = rintc->imsic_addr;
rintc_acpi_data[nr_rintc]->imsic_size = rintc->imsic_size;
nr_rintc++;
/*
* The ACPI MADT will have one INTC for each CPU (or HART)
@ -277,6 +365,8 @@ static int __init riscv_intc_acpi_init(union acpi_subtable_headers *header,
rc = riscv_intc_init_common(fn, &riscv_intc_chip);
if (rc)
irq_domain_free_fwnode(fn);
else
acpi_set_irq_model(ACPI_IRQ_MODEL_RINTC, riscv_acpi_get_gsi_domain_id);
return rc;
}

View File

@ -4,6 +4,7 @@
* Copyright (C) 2018 Christoph Hellwig
*/
#define pr_fmt(fmt) "riscv-plic: " fmt
#include <linux/acpi.h>
#include <linux/cpu.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@ -71,6 +72,8 @@ struct plic_priv {
unsigned long plic_quirks;
unsigned int nr_irqs;
unsigned long *prio_save;
u32 gsi_base;
int acpi_plic_id;
};
struct plic_handler {
@ -325,6 +328,10 @@ static int plic_irq_domain_translate(struct irq_domain *d,
{
struct plic_priv *priv = d->host_data;
/* For DT, gsi_base is always zero. */
if (fwspec->param[0] >= priv->gsi_base)
fwspec->param[0] = fwspec->param[0] - priv->gsi_base;
if (test_bit(PLIC_QUIRK_EDGE_INTERRUPT, &priv->plic_quirks))
return irq_domain_translate_twocell(d, fwspec, hwirq, type);
@ -426,17 +433,36 @@ static const struct of_device_id plic_match[] = {
{}
};
#ifdef CONFIG_ACPI
static const struct acpi_device_id plic_acpi_match[] = {
{ "RSCV0001", 0 },
{}
};
MODULE_DEVICE_TABLE(acpi, plic_acpi_match);
#endif
static int plic_parse_nr_irqs_and_contexts(struct fwnode_handle *fwnode,
u32 *nr_irqs, u32 *nr_contexts)
u32 *nr_irqs, u32 *nr_contexts,
u32 *gsi_base, u32 *id)
{
int rc;
/*
* Currently, only OF fwnode is supported so extend this
* function for ACPI support.
*/
if (!is_of_node(fwnode))
if (!is_of_node(fwnode)) {
rc = riscv_acpi_get_gsi_info(fwnode, gsi_base, id, nr_irqs, NULL);
if (rc) {
pr_err("%pfwP: failed to find GSI mapping\n", fwnode);
return rc;
}
*nr_contexts = acpi_rintc_get_plic_nr_contexts(*id);
if (WARN_ON(!*nr_contexts)) {
pr_err("%pfwP: no PLIC context available\n", fwnode);
return -EINVAL;
}
return 0;
}
rc = of_property_read_u32(to_of_node(fwnode), "riscv,ndev", nr_irqs);
if (rc) {
@ -450,23 +476,29 @@ static int plic_parse_nr_irqs_and_contexts(struct fwnode_handle *fwnode,
return -EINVAL;
}
*gsi_base = 0;
*id = 0;
return 0;
}
static int plic_parse_context_parent(struct fwnode_handle *fwnode, u32 context,
u32 *parent_hwirq, int *parent_cpu)
u32 *parent_hwirq, int *parent_cpu, u32 id)
{
struct of_phandle_args parent;
unsigned long hartid;
int rc;
/*
* Currently, only OF fwnode is supported so extend this
* function for ACPI support.
*/
if (!is_of_node(fwnode))
if (!is_of_node(fwnode)) {
hartid = acpi_rintc_ext_parent_to_hartid(id, context);
if (hartid == INVALID_HARTID)
return -EINVAL;
*parent_cpu = riscv_hartid_to_cpuid(hartid);
*parent_hwirq = RV_IRQ_EXT;
return 0;
}
rc = of_irq_parse_one(to_of_node(fwnode), context, &parent);
if (rc)
return rc;
@ -489,6 +521,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
struct plic_priv *priv;
irq_hw_number_t hwirq;
void __iomem *regs;
int id, context_id;
u32 gsi_base;
if (is_of_node(fwnode)) {
const struct of_device_id *id;
@ -501,10 +535,12 @@ static int plic_probe(struct fwnode_handle *fwnode)
if (!regs)
return -ENOMEM;
} else {
return -ENODEV;
regs = devm_platform_ioremap_resource(to_platform_device(fwnode->dev), 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
}
error = plic_parse_nr_irqs_and_contexts(fwnode, &nr_irqs, &nr_contexts);
error = plic_parse_nr_irqs_and_contexts(fwnode, &nr_irqs, &nr_contexts, &gsi_base, &id);
if (error)
goto fail_free_regs;
@ -518,6 +554,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
priv->plic_quirks = plic_quirks;
priv->nr_irqs = nr_irqs;
priv->regs = regs;
priv->gsi_base = gsi_base;
priv->acpi_plic_id = id;
priv->prio_save = bitmap_zalloc(nr_irqs, GFP_KERNEL);
if (!priv->prio_save) {
@ -526,12 +564,23 @@ static int plic_probe(struct fwnode_handle *fwnode)
}
for (i = 0; i < nr_contexts; i++) {
error = plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu);
error = plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu,
priv->acpi_plic_id);
if (error) {
pr_warn("%pfwP: hwirq for context%d not found\n", fwnode, i);
continue;
}
if (is_of_node(fwnode)) {
context_id = i;
} else {
context_id = acpi_rintc_get_plic_context(priv->acpi_plic_id, i);
if (context_id == INVALID_CONTEXT) {
pr_warn("%pfwP: invalid context id for context%d\n", fwnode, i);
continue;
}
}
/*
* Skip contexts other than external interrupts for our
* privilege level.
@ -569,10 +618,10 @@ static int plic_probe(struct fwnode_handle *fwnode)
cpumask_set_cpu(cpu, &priv->lmask);
handler->present = true;
handler->hart_base = priv->regs + CONTEXT_BASE +
i * CONTEXT_SIZE;
context_id * CONTEXT_SIZE;
raw_spin_lock_init(&handler->enable_lock);
handler->enable_base = priv->regs + CONTEXT_ENABLE_BASE +
i * CONTEXT_ENABLE_SIZE;
context_id * CONTEXT_ENABLE_SIZE;
handler->priv = priv;
handler->enable_save = kcalloc(DIV_ROUND_UP(nr_irqs, 32),
@ -588,7 +637,7 @@ done:
nr_handlers++;
}
priv->irqdomain = irq_domain_add_linear(to_of_node(fwnode), nr_irqs + 1,
priv->irqdomain = irq_domain_create_linear(fwnode, nr_irqs + 1,
&plic_irqdomain_ops, priv);
if (WARN_ON(!priv->irqdomain))
goto fail_cleanup_contexts;
@ -626,13 +675,18 @@ done:
}
}
#ifdef CONFIG_ACPI
if (!acpi_disabled)
acpi_dev_clear_dependencies(ACPI_COMPANION(fwnode->dev));
#endif
pr_info("%pfwP: mapped %d interrupts with %d handlers for %d contexts.\n",
fwnode, nr_irqs, nr_handlers, nr_contexts);
return 0;
fail_cleanup_contexts:
for (i = 0; i < nr_contexts; i++) {
if (plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu))
if (plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu, priv->acpi_plic_id))
continue;
if (parent_hwirq != RV_IRQ_EXT || cpu < 0)
continue;
@ -663,6 +717,7 @@ static struct platform_driver plic_driver = {
.name = "riscv-plic",
.of_match_table = plic_match,
.suppress_bind_attrs = true,
.acpi_match_table = ACPI_PTR(plic_acpi_match),
},
.probe = plic_platform_probe,
};

View File

@ -15,6 +15,7 @@
#include <linux/pci_hotplug.h>
#include <linux/module.h>
#include <linux/pci-acpi.h>
#include <linux/pci-ecam.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/rwsem.h>
@ -1541,3 +1542,184 @@ static int __init acpi_pci_init(void)
return 0;
}
arch_initcall(acpi_pci_init);
#if defined(CONFIG_ARM64) || defined(CONFIG_RISCV)
/*
* Try to assign the IRQ number when probing a new device
*/
int pcibios_alloc_irq(struct pci_dev *dev)
{
if (!acpi_disabled)
acpi_pci_irq_enable(dev);
return 0;
}
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)
{
struct pci_config_window *cfg;
struct acpi_device *adev;
struct device *bus_dev;
if (acpi_disabled)
return 0;
cfg = bridge->bus->sysdata;
/*
* On Hyper-V there is no corresponding ACPI device for a root bridge,
* therefore ->parent is set as NULL by the driver. And set 'adev' as
* NULL in this case because there is no proper ACPI device.
*/
if (!cfg->parent)
adev = NULL;
else
adev = to_acpi_device(cfg->parent);
bus_dev = &bridge->bus->dev;
ACPI_COMPANION_SET(&bridge->dev, adev);
set_dev_node(bus_dev, acpi_get_node(acpi_device_handle(adev)));
return 0;
}
static int pci_acpi_root_prepare_resources(struct acpi_pci_root_info *ci)
{
struct resource_entry *entry, *tmp;
int status;
status = acpi_pci_probe_root_resources(ci);
resource_list_for_each_entry_safe(entry, tmp, &ci->resources) {
if (!(entry->res->flags & IORESOURCE_WINDOW))
resource_list_destroy_entry(entry);
}
return status;
}
/*
* 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 device *dev = &root->device->dev;
struct resource *bus_res = &root->secondary;
u16 seg = root->segment;
const struct pci_ecam_ops *ecam_ops;
struct resource cfgres;
struct acpi_device *adev;
struct pci_config_window *cfg;
int ret;
ret = pci_mcfg_lookup(root, &cfgres, &ecam_ops);
if (ret) {
dev_err(dev, "%04x:%pR ECAM region not found\n", seg, bus_res);
return NULL;
}
adev = acpi_resource_consumer(&cfgres);
if (adev)
dev_info(dev, "ECAM area %pR reserved by %s\n", &cfgres,
dev_name(&adev->dev));
else
dev_warn(dev, FW_BUG "ECAM area %pR not reserved in ACPI namespace\n",
&cfgres);
cfg = pci_ecam_create(dev, &cfgres, bus_res, ecam_ops);
if (IS_ERR(cfg)) {
dev_err(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(ci->ops);
kfree(ri);
}
/* Interface called from ACPI code to setup PCI host controller */
struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
struct acpi_pci_generic_root_info *ri;
struct pci_bus *bus, *child;
struct acpi_pci_root_ops *root_ops;
struct pci_host_bridge *host;
ri = kzalloc(sizeof(*ri), GFP_KERNEL);
if (!ri)
return NULL;
root_ops = kzalloc(sizeof(*root_ops), GFP_KERNEL);
if (!root_ops) {
kfree(ri);
return NULL;
}
ri->cfg = pci_acpi_setup_ecam_mapping(root);
if (!ri->cfg) {
kfree(ri);
kfree(root_ops);
return NULL;
}
root_ops->release_info = pci_acpi_generic_release_info;
root_ops->prepare_resources = pci_acpi_root_prepare_resources;
root_ops->pci_ops = (struct pci_ops *)&ri->cfg->ops->pci_ops;
bus = acpi_pci_root_create(root, root_ops, &ri->common, ri->cfg);
if (!bus)
return NULL;
/* If we must preserve the resource configuration, claim now */
host = pci_find_host_bridge(bus);
if (host->preserve_config)
pci_bus_claim_resources(bus);
/*
* Assign whatever was left unassigned. If we didn't claim above,
* this will reassign everything.
*/
pci_assign_unassigned_root_bus_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

View File

@ -993,6 +993,8 @@ static inline void acpi_put_acpi_dev(struct acpi_device *adev)
int acpi_wait_for_acpi_ipmi(void);
int acpi_scan_add_dep(acpi_handle handle, struct acpi_handle_list *dep_devices);
u32 arch_acpi_add_auto_dep(acpi_handle handle);
#else /* CONFIG_ACPI */
static inline int register_acpi_bus_type(void *bus) { return 0; }

View File

@ -107,6 +107,7 @@ enum acpi_irq_model_id {
ACPI_IRQ_MODEL_PLATFORM,
ACPI_IRQ_MODEL_GIC,
ACPI_IRQ_MODEL_LPIC,
ACPI_IRQ_MODEL_RINTC,
ACPI_IRQ_MODEL_COUNT
};
@ -1343,6 +1344,8 @@ struct acpi_probe_entry {
kernel_ulong_t driver_data;
};
void arch_sort_irqchip_probe(struct acpi_probe_entry *ap_head, int nr);
#define ACPI_DECLARE_PROBE_ENTRY(table, name, table_id, subtable, \
valid, data, fn) \
static const struct acpi_probe_entry __acpi_probe_##name \
@ -1529,6 +1532,12 @@ void acpi_arm_init(void);
static inline void acpi_arm_init(void) { }
#endif
#ifdef CONFIG_RISCV
void acpi_riscv_init(void);
#else
static inline void acpi_riscv_init(void) { }
#endif
#ifdef CONFIG_ACPI_PCC
void acpi_init_pcc(void);
#else

View File

@ -8,6 +8,8 @@
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/fwnode.h>
#include <asm/csr.h>
#define IMSIC_MMIO_PAGE_SHIFT 12
@ -84,4 +86,11 @@ static inline const struct imsic_global_config *imsic_get_global_config(void)
#endif
#ifdef CONFIG_ACPI
int imsic_platform_acpi_probe(struct fwnode_handle *fwnode);
struct fwnode_handle *imsic_acpi_get_fwnode(struct device *dev);
#else
static inline struct fwnode_handle *imsic_acpi_get_fwnode(struct device *dev) { return NULL; }
#endif
#endif