Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# ACPI Configuration for ARM64
#
config ACPI_IORT
bool
config ACPI_GTDT
bool
config ACPI_AGDI
bool "Arm Generic Diagnostic Dump and Reset Device Interface"
depends on ARM_SDE_INTERFACE
help
Arm Generic Diagnostic Dump and Reset Device Interface (AGDI) is
a standard that enables issuing a non-maskable diagnostic dump and
reset command.
If set, the kernel parses AGDI table and listens for the command.
config ACPI_APMT
bool
config ACPI_MPAM
bool
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_ACPI_AGDI) += agdi.o
obj-$(CONFIG_ACPI_APMT) += apmt.o
obj-$(CONFIG_ACPI_FFH) += ffh.o
obj-$(CONFIG_ACPI_GTDT) += gtdt.o
obj-$(CONFIG_ACPI_IORT) += iort.o
obj-$(CONFIG_ACPI_MPAM) += mpam.o
obj-$(CONFIG_ACPI_PROCESSOR_IDLE) += cpuidle.o
obj-$(CONFIG_ARM_AMBA) += amba.o
obj-y += dma.o init.o
obj-y += thermal_cpufreq.o
+205
View File
@@ -0,0 +1,205 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* This file implements handling of
* Arm Generic Diagnostic Dump and Reset Interface table (AGDI)
*
* Copyright (c) 2022, Ampere Computing LLC
*/
#define pr_fmt(fmt) "ACPI: AGDI: " fmt
#include <linux/acpi.h>
#include <linux/arm_sdei.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include "init.h"
struct agdi_data {
unsigned char flags; /* AGDI Signaling Mode */
int sdei_event;
unsigned int gsiv;
bool use_nmi;
int irq;
};
static int agdi_sdei_handler(u32 sdei_event, struct pt_regs *regs, void *arg)
{
nmi_panic(regs, "Arm Generic Diagnostic Dump and Reset SDEI event issued");
return 0;
}
static int agdi_sdei_probe(struct platform_device *pdev,
struct agdi_data *adata)
{
int err;
err = sdei_event_register(adata->sdei_event, agdi_sdei_handler, pdev);
if (err) {
dev_err(&pdev->dev, "Failed to register for SDEI event %d\n",
adata->sdei_event);
return err;
}
err = sdei_event_enable(adata->sdei_event);
if (err) {
sdei_event_unregister(adata->sdei_event);
dev_err(&pdev->dev, "Failed to enable event %d\n",
adata->sdei_event);
return err;
}
return 0;
}
static irqreturn_t agdi_interrupt_handler_nmi(int irq, void *dev_id)
{
nmi_panic(NULL, "Arm Generic Diagnostic Dump and Reset NMI Interrupt event issued\n");
return IRQ_HANDLED;
}
static irqreturn_t agdi_interrupt_handler_irq(int irq, void *dev_id)
{
panic("Arm Generic Diagnostic Dump and Reset Interrupt event issued\n");
return IRQ_HANDLED;
}
static int agdi_interrupt_probe(struct platform_device *pdev,
struct agdi_data *adata)
{
unsigned long irq_flags;
int ret;
int irq;
irq = acpi_register_gsi(NULL, adata->gsiv, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_HIGH);
if (irq < 0) {
dev_err(&pdev->dev, "cannot register GSI#%d (%d)\n", adata->gsiv, irq);
return irq;
}
irq_flags = IRQF_PERCPU | IRQF_NOBALANCING | IRQF_NO_AUTOEN |
IRQF_NO_THREAD;
/* try NMI first */
ret = request_nmi(irq, &agdi_interrupt_handler_nmi, irq_flags,
"agdi_interrupt_nmi", NULL);
if (!ret) {
enable_nmi(irq);
adata->irq = irq;
adata->use_nmi = true;
return 0;
}
/* Then try normal interrupt */
ret = request_irq(irq, &agdi_interrupt_handler_irq,
irq_flags, "agdi_interrupt_irq", NULL);
if (ret) {
dev_err(&pdev->dev, "cannot register IRQ %d\n", ret);
acpi_unregister_gsi(adata->gsiv);
return ret;
}
enable_irq(irq);
adata->irq = irq;
return 0;
}
static int agdi_probe(struct platform_device *pdev)
{
struct agdi_data *adata = dev_get_platdata(&pdev->dev);
if (!adata)
return -EINVAL;
if (adata->flags & ACPI_AGDI_SIGNALING_MODE)
return agdi_interrupt_probe(pdev, adata);
else
return agdi_sdei_probe(pdev, adata);
}
static void agdi_sdei_remove(struct platform_device *pdev,
struct agdi_data *adata)
{
int err, i;
err = sdei_event_disable(adata->sdei_event);
if (err) {
dev_err(&pdev->dev, "Failed to disable sdei-event #%d (%pe)\n",
adata->sdei_event, ERR_PTR(err));
return;
}
for (i = 0; i < 3; i++) {
err = sdei_event_unregister(adata->sdei_event);
if (err != -EINPROGRESS)
break;
schedule();
}
if (err)
dev_err(&pdev->dev, "Failed to unregister sdei-event #%d (%pe)\n",
adata->sdei_event, ERR_PTR(err));
}
static void agdi_interrupt_remove(struct platform_device *pdev,
struct agdi_data *adata)
{
if (adata->irq == -1)
return;
if (adata->use_nmi)
free_nmi(adata->irq, NULL);
else
free_irq(adata->irq, NULL);
acpi_unregister_gsi(adata->gsiv);
}
static void agdi_remove(struct platform_device *pdev)
{
struct agdi_data *adata = dev_get_platdata(&pdev->dev);
if (adata->flags & ACPI_AGDI_SIGNALING_MODE)
agdi_interrupt_remove(pdev, adata);
else
agdi_sdei_remove(pdev, adata);
}
static struct platform_driver agdi_driver = {
.driver = {
.name = "agdi",
},
.probe = agdi_probe,
.remove = agdi_remove,
};
void __init acpi_agdi_init(void)
{
struct acpi_table_agdi *agdi_table;
struct agdi_data pdata = { 0 };
struct platform_device *pdev;
acpi_status status;
status = acpi_get_table(ACPI_SIG_AGDI, 0,
(struct acpi_table_header **) &agdi_table);
if (ACPI_FAILURE(status))
return;
if (agdi_table->flags & ACPI_AGDI_SIGNALING_MODE)
pdata.gsiv = agdi_table->gsiv;
else
pdata.sdei_event = agdi_table->sdei_event;
pdata.irq = -1;
pdata.flags = agdi_table->flags;
pdev = platform_device_register_data(NULL, "agdi", 0, &pdata, sizeof(pdata));
if (IS_ERR(pdev))
goto err_put_table;
if (platform_driver_register(&agdi_driver))
platform_device_unregister(pdev);
err_put_table:
acpi_put_table((struct acpi_table_header *)agdi_table);
}
+117
View File
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ACPI support for platform bus type.
*
* Copyright (C) 2015, Linaro Ltd
* Author: Graeme Gregory <graeme.gregory@linaro.org>
*/
#include <linux/acpi.h>
#include <linux/amba/bus.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "init.h"
static const struct acpi_device_id amba_id_list[] = {
{"ARMH0061", 0}, /* PL061 GPIO Device */
{"ARMH0330", 0}, /* ARM DMA Controller DMA-330 */
{"", 0},
};
static void amba_register_dummy_clk(void)
{
struct clk *amba_dummy_clk;
amba_dummy_clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, 0, 0);
clk_register_clkdev(amba_dummy_clk, "apb_pclk", NULL);
}
static int amba_handler_attach(struct acpi_device *adev,
const struct acpi_device_id *id)
{
struct acpi_device *parent = acpi_dev_parent(adev);
struct amba_device *dev;
struct resource_entry *rentry;
struct list_head resource_list;
bool address_found = false;
int irq_no = 0;
int ret;
/* If the ACPI node already has a physical device attached, skip it. */
if (adev->physical_node_count)
return 0;
dev = amba_device_alloc(dev_name(&adev->dev), 0, 0);
if (!dev) {
dev_err(&adev->dev, "%s(): amba_device_alloc() failed\n",
__func__);
return -ENOMEM;
}
INIT_LIST_HEAD(&resource_list);
ret = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);
if (ret < 0)
goto err_free;
list_for_each_entry(rentry, &resource_list, node) {
switch (resource_type(rentry->res)) {
case IORESOURCE_MEM:
if (!address_found) {
dev->res = *rentry->res;
dev->res.name = dev_name(&dev->dev);
address_found = true;
}
break;
case IORESOURCE_IRQ:
if (irq_no < AMBA_NR_IRQS)
dev->irq[irq_no++] = rentry->res->start;
break;
default:
dev_warn(&adev->dev, "Invalid resource\n");
break;
}
}
acpi_dev_free_resource_list(&resource_list);
/*
* If the ACPI node has a parent and that parent has a physical device
* attached to it, that physical device should be the parent of
* the amba device we are about to create.
*/
if (parent)
dev->dev.parent = acpi_get_first_physical_node(parent);
device_set_node(&dev->dev, acpi_fwnode_handle(adev));
ret = amba_device_add(dev, &iomem_resource);
if (ret) {
dev_err(&adev->dev, "%s(): amba_device_add() failed (%d)\n",
__func__, ret);
goto err_free;
}
return 1;
err_free:
amba_device_put(dev);
return ret;
}
static struct acpi_scan_handler amba_handler = {
.ids = amba_id_list,
.attach = amba_handler_attach,
};
void __init acpi_amba_init(void)
{
amba_register_dummy_clk();
acpi_scan_add_handler(&amba_handler);
}
+180
View File
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ARM APMT table support.
* Design document number: ARM DEN0117.
*
* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES.
*
*/
#define pr_fmt(fmt) "ACPI: APMT: " fmt
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include "init.h"
#define DEV_NAME "arm-cs-arch-pmu"
/* There can be up to 3 resources: page 0 and 1 address, and interrupt. */
#define DEV_MAX_RESOURCE_COUNT 3
/* Root pointer to the mapped APMT table */
static struct acpi_table_header *apmt_table;
static int __init apmt_init_resources(struct resource *res,
struct acpi_apmt_node *node)
{
int irq, trigger;
int num_res = 0;
res[num_res].start = node->base_address0;
res[num_res].end = node->base_address0 + SZ_4K - 1;
res[num_res].flags = IORESOURCE_MEM;
num_res++;
if (node->flags & ACPI_APMT_FLAGS_DUAL_PAGE) {
res[num_res].start = node->base_address1;
res[num_res].end = node->base_address1 + SZ_4K - 1;
res[num_res].flags = IORESOURCE_MEM;
num_res++;
}
if (node->ovflw_irq != 0) {
trigger = (node->ovflw_irq_flags & ACPI_APMT_OVFLW_IRQ_FLAGS_MODE);
trigger = (trigger == ACPI_APMT_OVFLW_IRQ_FLAGS_MODE_LEVEL) ?
ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
irq = acpi_register_gsi(NULL, node->ovflw_irq, trigger,
ACPI_ACTIVE_HIGH);
if (irq <= 0) {
pr_warn("APMT could not register gsi hwirq %d\n", irq);
return num_res;
}
res[num_res].start = irq;
res[num_res].end = irq;
res[num_res].flags = IORESOURCE_IRQ;
num_res++;
}
return num_res;
}
/**
* apmt_add_platform_device() - Allocate a platform device for APMT node
* @node: Pointer to device ACPI APMT node
* @fwnode: fwnode associated with the APMT node
*
* Returns: 0 on success, <0 failure
*/
static int __init apmt_add_platform_device(struct acpi_apmt_node *node,
struct fwnode_handle *fwnode)
{
struct platform_device *pdev;
int ret, count;
struct resource res[DEV_MAX_RESOURCE_COUNT];
pdev = platform_device_alloc(DEV_NAME, PLATFORM_DEVID_AUTO);
if (!pdev)
return -ENOMEM;
memset(res, 0, sizeof(res));
count = apmt_init_resources(res, node);
ret = platform_device_add_resources(pdev, res, count);
if (ret)
goto dev_put;
/*
* Add a copy of APMT node pointer to platform_data to be used to
* retrieve APMT data information.
*/
ret = platform_device_add_data(pdev, &node, sizeof(node));
if (ret)
goto dev_put;
pdev->dev.fwnode = fwnode;
ret = platform_device_add(pdev);
if (ret)
goto dev_put;
return 0;
dev_put:
platform_device_put(pdev);
return ret;
}
static int __init apmt_init_platform_devices(void)
{
struct acpi_apmt_node *apmt_node;
struct acpi_table_apmt *apmt;
struct fwnode_handle *fwnode;
u64 offset, end;
int ret;
/*
* apmt_table and apmt both point to the start of APMT table, but
* have different struct types
*/
apmt = (struct acpi_table_apmt *)apmt_table;
offset = sizeof(*apmt);
end = apmt->header.length;
while (offset < end) {
apmt_node = ACPI_ADD_PTR(struct acpi_apmt_node, apmt,
offset);
fwnode = acpi_alloc_fwnode_static();
if (!fwnode)
return -ENOMEM;
ret = apmt_add_platform_device(apmt_node, fwnode);
if (ret) {
acpi_free_fwnode_static(fwnode);
return ret;
}
offset += apmt_node->length;
}
return 0;
}
void __init acpi_apmt_init(void)
{
acpi_status status;
int ret;
/**
* APMT table nodes will be used at runtime after the apmt init,
* so we don't need to call acpi_put_table() to release
* the APMT table mapping.
*/
status = acpi_get_table(ACPI_SIG_APMT, 0, &apmt_table);
if (ACPI_FAILURE(status)) {
if (status != AE_NOT_FOUND) {
const char *msg = acpi_format_exception(status);
pr_err("Failed to get APMT table, %s\n", msg);
}
return;
}
ret = apmt_init_platform_devices();
if (ret) {
pr_err("Failed to initialize APMT platform devices, ret: %d\n", ret);
acpi_put_table(apmt_table);
}
}
+66
View File
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ARM64 CPU idle arch support
*
* Copyright (C) 2014 ARM Ltd.
* Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
*/
#include <linux/acpi.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/psci.h>
#include <acpi/processor.h>
#define ARM64_LPI_IS_RETENTION_STATE(arch_flags) (!(arch_flags))
static int psci_acpi_cpu_init_idle(unsigned int cpu)
{
int i;
struct acpi_lpi_state *lpi;
struct acpi_processor *pr = per_cpu(processors, cpu);
if (unlikely(!pr || !pr->flags.has_lpi))
return -EINVAL;
/*
* If the PSCI cpu_suspend function hook has not been initialized
* idle states must not be enabled, so bail out
*/
if (!psci_ops.cpu_suspend)
return -EOPNOTSUPP;
for (i = 1; i < pr->power.count; i++) {
u32 state;
lpi = &pr->power.lpi_states[i];
/*
* Only bits[31:0] represent a PSCI power_state while
* bits[63:32] must be 0x0 as per ARM ACPI FFH Specification
*/
state = lpi->address;
if (!psci_power_state_is_valid(state)) {
pr_warn("Invalid PSCI power state %#x\n", state);
return -EINVAL;
}
}
return 0;
}
int acpi_processor_ffh_lpi_probe(unsigned int cpu)
{
return psci_acpi_cpu_init_idle(cpu);
}
__cpuidle int acpi_processor_ffh_lpi_enter(struct acpi_lpi_state *lpi)
{
u32 state = lpi->address;
if (ARM64_LPI_IS_RETENTION_STATE(lpi->arch_flags))
return CPU_PM_CPU_IDLE_ENTER_RETENTION_PARAM_RCU(psci_cpu_suspend_enter,
lpi->index, state);
else
return CPU_PM_CPU_IDLE_ENTER_PARAM_RCU(psci_cpu_suspend_enter,
lpi->index, state);
}
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/acpi.h>
#include <linux/acpi_iort.h>
#include <linux/device.h>
#include <linux/dma-direct.h>
void acpi_arch_dma_setup(struct device *dev)
{
int ret;
u64 end, mask;
const struct bus_dma_region *map = NULL;
/*
* If @dev is expected to be DMA-capable then the bus code that created
* it should have initialised its dma_mask pointer by this point. For
* now, we'll continue the legacy behaviour of coercing it to the
* coherent mask if not, but we'll no longer do so quietly.
*/
if (!dev->dma_mask) {
dev_warn(dev, "DMA mask not set\n");
dev->dma_mask = &dev->coherent_dma_mask;
}
if (dev->coherent_dma_mask)
end = dev->coherent_dma_mask;
else
end = (1ULL << 32) - 1;
if (dev->dma_range_map) {
dev_dbg(dev, "dma_range_map already set\n");
return;
}
ret = acpi_dma_get_range(dev, &map);
if (!ret && map) {
end = dma_range_map_max(map);
dev->dma_range_map = map;
}
if (ret == -ENODEV)
ret = iort_dma_get_ranges(dev, &end);
if (!ret) {
/*
* Limit coherent and dma mask based on size retrieved from
* firmware.
*/
mask = DMA_BIT_MASK(ilog2(end) + 1);
dev->bus_dma_limit = end;
dev->coherent_dma_mask = min(dev->coherent_dma_mask, mask);
*dev->dma_mask = min(*dev->dma_mask, mask);
}
}
+107
View File
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
#include <linux/slab.h>
/*
* Implements ARM64 specific callbacks to support ACPI FFH Operation Region as
* specified in https://developer.arm.com/docs/den0048/latest
*/
struct acpi_ffh_data {
struct acpi_ffh_info info;
void (*invoke_ffh_fn)(unsigned long a0, unsigned long a1,
unsigned long a2, unsigned long a3,
unsigned long a4, unsigned long a5,
unsigned long a6, unsigned long a7,
struct arm_smccc_res *args,
struct arm_smccc_quirk *res);
void (*invoke_ffh64_fn)(const struct arm_smccc_1_2_regs *args,
struct arm_smccc_1_2_regs *res);
};
int acpi_ffh_address_space_arch_setup(void *handler_ctxt, void **region_ctxt)
{
enum arm_smccc_conduit conduit;
struct acpi_ffh_data *ffh_ctxt;
if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2)
return -EOPNOTSUPP;
conduit = arm_smccc_1_1_get_conduit();
if (conduit == SMCCC_CONDUIT_NONE) {
pr_err("%s: invalid SMCCC conduit\n", __func__);
return -EOPNOTSUPP;
}
ffh_ctxt = kzalloc_obj(*ffh_ctxt);
if (!ffh_ctxt)
return -ENOMEM;
if (conduit == SMCCC_CONDUIT_SMC) {
ffh_ctxt->invoke_ffh_fn = __arm_smccc_smc;
ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_smc;
} else {
ffh_ctxt->invoke_ffh_fn = __arm_smccc_hvc;
ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_hvc;
}
memcpy(ffh_ctxt, handler_ctxt, sizeof(ffh_ctxt->info));
*region_ctxt = ffh_ctxt;
return AE_OK;
}
static bool acpi_ffh_smccc_owner_allowed(u32 fid)
{
int owner = ARM_SMCCC_OWNER_NUM(fid);
if (owner == ARM_SMCCC_OWNER_STANDARD ||
owner == ARM_SMCCC_OWNER_SIP || owner == ARM_SMCCC_OWNER_OEM)
return true;
return false;
}
int acpi_ffh_address_space_arch_handler(acpi_integer *value, void *region_context)
{
int ret = 0;
struct acpi_ffh_data *ffh_ctxt = region_context;
if (ffh_ctxt->info.offset == 0) {
/* SMC/HVC 32bit call */
struct arm_smccc_res res;
u32 a[8] = { 0 }, *ptr = (u32 *)value;
if (!ARM_SMCCC_IS_FAST_CALL(*ptr) || ARM_SMCCC_IS_64(*ptr) ||
!acpi_ffh_smccc_owner_allowed(*ptr) ||
ffh_ctxt->info.length > 32) {
ret = AE_ERROR;
} else {
int idx, len = ffh_ctxt->info.length >> 2;
for (idx = 0; idx < len; idx++)
a[idx] = *(ptr + idx);
ffh_ctxt->invoke_ffh_fn(a[0], a[1], a[2], a[3], a[4],
a[5], a[6], a[7], &res, NULL);
memcpy(value, &res, sizeof(res));
}
} else if (ffh_ctxt->info.offset == 1) {
/* SMC/HVC 64bit call */
struct arm_smccc_1_2_regs *r = (struct arm_smccc_1_2_regs *)value;
if (!ARM_SMCCC_IS_FAST_CALL(r->a0) || !ARM_SMCCC_IS_64(r->a0) ||
!acpi_ffh_smccc_owner_allowed(r->a0) ||
ffh_ctxt->info.length > sizeof(*r)) {
ret = AE_ERROR;
} else {
ffh_ctxt->invoke_ffh64_fn(r, r);
memcpy(value, r, ffh_ctxt->info.length);
}
} else {
ret = AE_ERROR;
}
return ret;
}
+458
View File
@@ -0,0 +1,458 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ARM Specific GTDT table Support
*
* Copyright (C) 2016, Linaro Ltd.
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
* Fu Wei <fu.wei@linaro.org>
* Hanjun Guo <hanjun.guo@linaro.org>
*/
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <clocksource/arm_arch_timer.h>
#undef pr_fmt
#define pr_fmt(fmt) "ACPI GTDT: " fmt
/**
* struct acpi_gtdt_descriptor - Store the key info of GTDT for all functions
* @gtdt: The pointer to the struct acpi_table_gtdt of GTDT table.
* @gtdt_end: The pointer to the end of GTDT table.
* @platform_timer: The pointer to the start of Platform Timer Structure
*
* The struct store the key info of GTDT table, it should be initialized by
* acpi_gtdt_init.
*/
struct acpi_gtdt_descriptor {
struct acpi_table_gtdt *gtdt;
void *gtdt_end;
void *platform_timer;
};
struct gtdt_v3 {
struct acpi_table_gtdt gtdt_v2;
struct acpi_gtdt_el2 el2_vtimer;
};
static struct acpi_gtdt_descriptor acpi_gtdt_desc __initdata;
static __init struct acpi_gtdt_el2 *gtdt_to_el2_vtimer(struct acpi_table_gtdt *gtdt)
{
if (gtdt->header.revision < 3)
return NULL;
return &container_of(gtdt, struct gtdt_v3, gtdt_v2)->el2_vtimer;
}
static __init bool platform_timer_valid(void *platform_timer)
{
struct acpi_gtdt_header *gh = platform_timer;
void *platform_timer_begin;
if (acpi_gtdt_desc.gtdt->header.revision >= 3)
platform_timer_begin = container_of(acpi_gtdt_desc.gtdt, struct gtdt_v3, gtdt_v2) + 1;
else
platform_timer_begin = acpi_gtdt_desc.gtdt + 1;
return (platform_timer >= platform_timer_begin &&
platform_timer + sizeof(*gh) <= acpi_gtdt_desc.gtdt_end &&
gh->length != 0 &&
platform_timer + gh->length <= acpi_gtdt_desc.gtdt_end);
}
static __init void *next_platform_timer(void *platform_timer)
{
struct acpi_gtdt_header *gh = platform_timer;
return platform_timer + gh->length;
}
#define for_each_platform_timer(_g) \
for (_g = acpi_gtdt_desc.platform_timer; platform_timer_valid(_g);\
_g = next_platform_timer(_g))
static inline bool is_timer_block(void *platform_timer)
{
struct acpi_gtdt_header *gh = platform_timer;
return gh->type == ACPI_GTDT_TYPE_TIMER_BLOCK;
}
static inline bool is_non_secure_watchdog(void *platform_timer)
{
struct acpi_gtdt_header *gh = platform_timer;
struct acpi_gtdt_watchdog *wd = platform_timer;
if (gh->type != ACPI_GTDT_TYPE_WATCHDOG)
return false;
return !(wd->timer_flags & ACPI_GTDT_WATCHDOG_SECURE);
}
static int __init map_gt_gsi(u32 interrupt, u32 flags)
{
int trigger, polarity;
trigger = (flags & ACPI_GTDT_INTERRUPT_MODE) ? ACPI_EDGE_SENSITIVE
: ACPI_LEVEL_SENSITIVE;
polarity = (flags & ACPI_GTDT_INTERRUPT_POLARITY) ? ACPI_ACTIVE_LOW
: ACPI_ACTIVE_HIGH;
return acpi_register_gsi(NULL, interrupt, trigger, polarity);
}
/**
* acpi_gtdt_map_ppi() - Map the PPIs of per-cpu arch_timer.
* @type: the type of PPI.
*
* Note: Secure state is not managed by the kernel on ARM64 systems.
* So we only handle the non-secure timer PPIs,
* ARCH_TIMER_PHYS_SECURE_PPI is treated as invalid type.
*
* Return: the mapped PPI value, 0 if error.
*/
int __init acpi_gtdt_map_ppi(int type)
{
struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt;
struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt);
switch (type) {
case ARCH_TIMER_PHYS_NONSECURE_PPI:
return map_gt_gsi(gtdt->non_secure_el1_interrupt,
gtdt->non_secure_el1_flags);
case ARCH_TIMER_VIRT_PPI:
return map_gt_gsi(gtdt->virtual_timer_interrupt,
gtdt->virtual_timer_flags);
case ARCH_TIMER_HYP_PPI:
return map_gt_gsi(gtdt->non_secure_el2_interrupt,
gtdt->non_secure_el2_flags);
case ARCH_TIMER_HYP_VIRT_PPI:
if (el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv)
return map_gt_gsi(el2_vtimer->virtual_el2_timer_gsiv,
el2_vtimer->virtual_el2_timer_flags);
return 0;
default:
pr_err("Failed to map timer interrupt: invalid type.\n");
}
return 0;
}
/**
* acpi_gtdt_c3stop() - Got c3stop info from GTDT according to the type of PPI.
* @type: the type of PPI.
*
* Return: true if the timer HW state is lost when a CPU enters an idle state,
* false otherwise
*/
bool __init acpi_gtdt_c3stop(int type)
{
struct acpi_table_gtdt *gtdt = acpi_gtdt_desc.gtdt;
struct acpi_gtdt_el2 *el2_vtimer = gtdt_to_el2_vtimer(gtdt);
switch (type) {
case ARCH_TIMER_PHYS_NONSECURE_PPI:
return !(gtdt->non_secure_el1_flags & ACPI_GTDT_ALWAYS_ON);
case ARCH_TIMER_VIRT_PPI:
return !(gtdt->virtual_timer_flags & ACPI_GTDT_ALWAYS_ON);
case ARCH_TIMER_HYP_PPI:
return !(gtdt->non_secure_el2_flags & ACPI_GTDT_ALWAYS_ON);
case ARCH_TIMER_HYP_VIRT_PPI:
return el2_vtimer && el2_vtimer->virtual_el2_timer_gsiv &&
!(el2_vtimer->virtual_el2_timer_flags & ACPI_GTDT_ALWAYS_ON);
default:
pr_err("Failed to get c3stop info: invalid type.\n");
}
return false;
}
/**
* acpi_gtdt_init() - Get the info of GTDT table to prepare for further init.
* @table: The pointer to GTDT table.
* @platform_timer_count: It points to a integer variable which is used
* for storing the number of platform timers.
* This pointer could be NULL, if the caller
* doesn't need this info.
*
* Return: 0 if success, -EINVAL if error.
*/
int __init acpi_gtdt_init(struct acpi_table_header *table,
int *platform_timer_count)
{
void *platform_timer;
struct acpi_table_gtdt *gtdt;
u32 cnt = 0;
gtdt = container_of(table, struct acpi_table_gtdt, header);
if ((gtdt->header.revision >= 3 && gtdt->header.length < sizeof(struct gtdt_v3)) ||
(gtdt->header.revision == 2 && gtdt->header.length < sizeof(*gtdt))) {
pr_err(FW_BUG "GTDT with invalid size %d\n", gtdt->header.length);
return -EINVAL;
}
acpi_gtdt_desc.gtdt = gtdt;
acpi_gtdt_desc.gtdt_end = (void *)table + table->length;
acpi_gtdt_desc.platform_timer = NULL;
if (platform_timer_count)
*platform_timer_count = 0;
if (table->revision < 2) {
pr_warn("Revision:%d doesn't support Platform Timers.\n",
table->revision);
return 0;
}
if (!gtdt->platform_timer_count) {
pr_debug("No Platform Timer.\n");
return 0;
}
acpi_gtdt_desc.platform_timer = (void *)gtdt + gtdt->platform_timer_offset;
for_each_platform_timer(platform_timer)
cnt++;
if (cnt != gtdt->platform_timer_count) {
cnt = min(cnt, gtdt->platform_timer_count);
pr_err(FW_BUG "limiting Platform Timer count to %d\n", cnt);
}
if (!cnt) {
acpi_gtdt_desc.platform_timer = NULL;
return 0;
}
if (platform_timer_count)
*platform_timer_count = cnt;
return 0;
}
static int __init gtdt_parse_timer_block(struct acpi_gtdt_timer_block *block,
struct arch_timer_mem *timer_mem)
{
int i;
struct arch_timer_mem_frame *frame;
struct acpi_gtdt_timer_entry *gtdt_frame;
if (!block->timer_count) {
pr_err(FW_BUG "GT block present, but frame count is zero.\n");
return -ENODEV;
}
if (block->timer_count > ARCH_TIMER_MEM_MAX_FRAMES) {
pr_err(FW_BUG "GT block lists %d frames, ACPI spec only allows 8\n",
block->timer_count);
return -EINVAL;
}
timer_mem->cntctlbase = (phys_addr_t)block->block_address;
/*
* The CNTCTLBase frame is 4KB (register offsets 0x000 - 0xFFC).
* See ARM DDI 0487A.k_iss10775, page I1-5129, Table I1-3
* "CNTCTLBase memory map".
*/
timer_mem->size = SZ_4K;
gtdt_frame = (void *)block + block->timer_offset;
if (gtdt_frame + block->timer_count != (void *)block + block->header.length)
return -EINVAL;
/*
* Get the GT timer Frame data for every GT Block Timer
*/
for (i = 0; i < block->timer_count; i++, gtdt_frame++) {
if (gtdt_frame->common_flags & ACPI_GTDT_GT_IS_SECURE_TIMER)
continue;
if (gtdt_frame->frame_number >= ARCH_TIMER_MEM_MAX_FRAMES ||
!gtdt_frame->base_address || !gtdt_frame->timer_interrupt)
goto error;
frame = &timer_mem->frame[gtdt_frame->frame_number];
/* duplicate frame */
if (frame->valid)
goto error;
frame->phys_irq = map_gt_gsi(gtdt_frame->timer_interrupt,
gtdt_frame->timer_flags);
if (frame->phys_irq <= 0) {
pr_warn("failed to map physical timer irq in frame %d.\n",
gtdt_frame->frame_number);
goto error;
}
if (gtdt_frame->virtual_timer_interrupt) {
frame->virt_irq =
map_gt_gsi(gtdt_frame->virtual_timer_interrupt,
gtdt_frame->virtual_timer_flags);
if (frame->virt_irq <= 0) {
pr_warn("failed to map virtual timer irq in frame %d.\n",
gtdt_frame->frame_number);
goto error;
}
} else {
pr_debug("virtual timer in frame %d not implemented.\n",
gtdt_frame->frame_number);
}
frame->cntbase = gtdt_frame->base_address;
/*
* The CNTBaseN frame is 4KB (register offsets 0x000 - 0xFFC).
* See ARM DDI 0487A.k_iss10775, page I1-5130, Table I1-4
* "CNTBaseN memory map".
*/
frame->size = SZ_4K;
frame->valid = true;
}
return 0;
error:
do {
if (gtdt_frame->common_flags & ACPI_GTDT_GT_IS_SECURE_TIMER ||
gtdt_frame->frame_number >= ARCH_TIMER_MEM_MAX_FRAMES)
continue;
frame = &timer_mem->frame[gtdt_frame->frame_number];
if (frame->phys_irq > 0)
acpi_unregister_gsi(gtdt_frame->timer_interrupt);
frame->phys_irq = 0;
if (frame->virt_irq > 0)
acpi_unregister_gsi(gtdt_frame->virtual_timer_interrupt);
frame->virt_irq = 0;
} while (i-- > 0 && gtdt_frame--);
return -EINVAL;
}
/*
* Initialize a SBSA generic Watchdog platform device info from GTDT
*/
static int __init gtdt_import_sbsa_gwdt(struct acpi_gtdt_watchdog *wd,
int index)
{
struct platform_device *pdev;
int irq;
/*
* According to SBSA specification the size of refresh and control
* frames of SBSA Generic Watchdog is SZ_4K(Offset 0x000 – 0xFFF).
*/
struct resource res[] = {
DEFINE_RES_MEM(wd->control_frame_address, SZ_4K),
DEFINE_RES_MEM(wd->refresh_frame_address, SZ_4K),
{},
};
int nr_res = ARRAY_SIZE(res);
pr_debug("found a Watchdog (0x%llx/0x%llx gsi:%u flags:0x%x).\n",
wd->refresh_frame_address, wd->control_frame_address,
wd->timer_interrupt, wd->timer_flags);
if (!(wd->refresh_frame_address && wd->control_frame_address)) {
pr_err(FW_BUG "failed to get the Watchdog base address.\n");
return -EINVAL;
}
irq = map_gt_gsi(wd->timer_interrupt, wd->timer_flags);
res[2] = DEFINE_RES_IRQ(irq);
if (irq <= 0) {
pr_warn("failed to map the Watchdog interrupt.\n");
nr_res--;
}
/*
* Add a platform device named "sbsa-gwdt" to match the platform driver.
* "sbsa-gwdt": SBSA(Server Base System Architecture) Generic Watchdog
* The platform driver can get device info below by matching this name.
*/
pdev = platform_device_register_simple("sbsa-gwdt", index, res, nr_res);
if (IS_ERR(pdev)) {
if (irq > 0)
acpi_unregister_gsi(wd->timer_interrupt);
return PTR_ERR(pdev);
}
return 0;
}
static int __init gtdt_platform_timer_init(void)
{
void *platform_timer;
struct acpi_table_header *table;
int ret, timer_count, gwdt_count = 0, mmio_timer_count = 0;
if (acpi_disabled)
return 0;
if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_GTDT, 0, &table)))
return -EINVAL;
/*
* Note: Even though the global variable acpi_gtdt_desc has been
* initialized by acpi_gtdt_init() while initializing the arch timers,
* when we call this function to get SBSA watchdogs info from GTDT, the
* pointers stashed in it are stale (since they are early temporary
* mappings carried out before acpi_permanent_mmap is set) and we need
* to re-initialize them with permanent mapped pointer values to let the
* GTDT parsing possible.
*/
ret = acpi_gtdt_init(table, &timer_count);
if (ret || !timer_count)
goto out_put_gtdt;
for_each_platform_timer(platform_timer) {
ret = 0;
if (is_non_secure_watchdog(platform_timer)) {
ret = gtdt_import_sbsa_gwdt(platform_timer, gwdt_count);
if (ret)
continue;
gwdt_count++;
} else if (is_timer_block(platform_timer)) {
struct arch_timer_mem atm = {};
struct platform_device *pdev;
ret = gtdt_parse_timer_block(platform_timer, &atm);
if (ret)
continue;
pdev = platform_device_register_data(NULL, "gtdt-arm-mmio-timer",
mmio_timer_count, &atm,
sizeof(atm));
if (IS_ERR(pdev)) {
pr_err("Can't register timer %d\n", mmio_timer_count);
continue;
}
mmio_timer_count++;
}
}
if (gwdt_count)
pr_info("found %d SBSA generic Watchdog(s).\n", gwdt_count);
if (mmio_timer_count)
pr_info("found %d Generic MMIO timer(s).\n", mmio_timer_count);
out_put_gtdt:
acpi_put_table(table);
return ret;
}
device_initcall(gtdt_platform_timer_init);
+15
View File
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/acpi.h>
#include "init.h"
void __init acpi_arch_init(void)
{
if (IS_ENABLED(CONFIG_ACPI_AGDI))
acpi_agdi_init();
if (IS_ENABLED(CONFIG_ACPI_APMT))
acpi_apmt_init();
if (IS_ENABLED(CONFIG_ACPI_IORT))
acpi_iort_init();
if (IS_ENABLED(CONFIG_ARM_AMBA))
acpi_amba_init();
}
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <linux/init.h>
void __init acpi_agdi_init(void);
void __init acpi_apmt_init(void);
void __init acpi_iort_init(void);
void __init acpi_amba_init(void);
File diff suppressed because it is too large Load Diff
+411
View File
@@ -0,0 +1,411 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2025 Arm Ltd.
/* Parse the MPAM ACPI table feeding the discovered nodes into the driver */
#define pr_fmt(fmt) "ACPI MPAM: " fmt
#include <linux/acpi.h>
#include <linux/arm_mpam.h>
#include <linux/bits.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/platform_device.h>
#include <acpi/processor.h>
/*
* Flags for acpi_table_mpam_msc.*_interrupt_flags.
* See 2.1.1 Interrupt Flags, Table 5, of DEN0065B_MPAM_ACPI_3.0-bet.
*/
#define ACPI_MPAM_MSC_IRQ_MODE BIT(0)
#define ACPI_MPAM_MSC_IRQ_TYPE_MASK GENMASK(2, 1)
#define ACPI_MPAM_MSC_IRQ_TYPE_WIRED 0
#define ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_MASK BIT(3)
#define ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_PROCESSOR 0
#define ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_PROCESSOR_CONTAINER 1
#define ACPI_MPAM_MSC_IRQ_AFFINITY_VALID BIT(4)
/*
* Encodings for the MSC node body interface type field.
* See 2.1 MPAM MSC node, Table 4 of DEN0065B_MPAM_ACPI_3.0-bet.
*/
#define ACPI_MPAM_MSC_IFACE_MMIO 0x00
#define ACPI_MPAM_MSC_IFACE_PCC 0x0a
static bool _is_ppi_partition(u32 flags)
{
u32 aff_type, is_ppi;
bool ret;
is_ppi = FIELD_GET(ACPI_MPAM_MSC_IRQ_AFFINITY_VALID, flags);
if (!is_ppi)
return false;
aff_type = FIELD_GET(ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_MASK, flags);
ret = (aff_type == ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_PROCESSOR_CONTAINER);
if (ret)
pr_err_once("Partitioned interrupts not supported\n");
return ret;
}
static int acpi_mpam_register_irq(struct platform_device *pdev,
u32 intid, u32 flags)
{
int irq;
u32 int_type;
int trigger;
if (!intid)
return -EINVAL;
if (_is_ppi_partition(flags))
return -EINVAL;
trigger = FIELD_GET(ACPI_MPAM_MSC_IRQ_MODE, flags);
int_type = FIELD_GET(ACPI_MPAM_MSC_IRQ_TYPE_MASK, flags);
if (int_type != ACPI_MPAM_MSC_IRQ_TYPE_WIRED)
return -EINVAL;
irq = acpi_register_gsi(&pdev->dev, intid, trigger, ACPI_ACTIVE_HIGH);
if (irq < 0)
pr_err_once("Failed to register interrupt 0x%x with ACPI\n", intid);
return irq;
}
static void acpi_mpam_parse_irqs(struct platform_device *pdev,
struct acpi_mpam_msc_node *tbl_msc,
struct resource *res, int *res_idx)
{
u32 flags, intid;
int irq;
intid = tbl_msc->overflow_interrupt;
flags = tbl_msc->overflow_interrupt_flags;
irq = acpi_mpam_register_irq(pdev, intid, flags);
if (irq > 0)
res[(*res_idx)++] = DEFINE_RES_IRQ_NAMED(irq, "overflow");
intid = tbl_msc->error_interrupt;
flags = tbl_msc->error_interrupt_flags;
irq = acpi_mpam_register_irq(pdev, intid, flags);
if (irq > 0)
res[(*res_idx)++] = DEFINE_RES_IRQ_NAMED(irq, "error");
}
static int acpi_mpam_parse_resource(struct mpam_msc *msc,
struct acpi_mpam_resource_node *res)
{
int level, nid;
u32 cache_id;
switch (res->locator_type) {
case ACPI_MPAM_LOCATION_TYPE_PROCESSOR_CACHE:
cache_id = res->locator.cache_locator.cache_reference;
level = find_acpi_cache_level_from_id(cache_id);
if (level <= 0) {
pr_err_once("Bad level (%d) for cache with id %u\n", level, cache_id);
return -EINVAL;
}
return mpam_ris_create(msc, res->ris_index, MPAM_CLASS_CACHE,
level, cache_id);
case ACPI_MPAM_LOCATION_TYPE_MEMORY:
nid = pxm_to_node(res->locator.memory_locator.proximity_domain);
if (nid == NUMA_NO_NODE) {
pr_debug("Bad proximity domain %lld, using node 0 instead\n",
res->locator.memory_locator.proximity_domain);
nid = 0;
}
return mpam_ris_create(msc, res->ris_index, MPAM_CLASS_MEMORY,
MPAM_CLASS_ID_DEFAULT, nid);
default:
/* These get discovered later and are treated as unknown */
return 0;
}
}
int acpi_mpam_parse_resources(struct mpam_msc *msc,
struct acpi_mpam_msc_node *tbl_msc)
{
int i, err;
char *ptr, *table_end;
struct acpi_mpam_resource_node *resource;
table_end = (char *)tbl_msc + tbl_msc->length;
ptr = (char *)(tbl_msc + 1);
for (i = 0; i < tbl_msc->num_resource_nodes; i++) {
u64 max_deps, remaining_table;
if (ptr + sizeof(*resource) > table_end)
return -EINVAL;
resource = (struct acpi_mpam_resource_node *)ptr;
remaining_table = table_end - ptr;
max_deps = remaining_table / sizeof(struct acpi_mpam_func_deps);
if (resource->num_functional_deps > max_deps) {
pr_debug("MSC has impossible number of functional dependencies\n");
return -EINVAL;
}
err = acpi_mpam_parse_resource(msc, resource);
if (err)
return err;
ptr += sizeof(*resource);
ptr += resource->num_functional_deps * sizeof(struct acpi_mpam_func_deps);
}
return 0;
}
/*
* Creates the device power management link and returns true if the
* acpi id is valid and usable for cpu affinity. This is the case
* when the linked device is a processor or a processor container.
*/
static bool __init parse_msc_pm_link(struct acpi_mpam_msc_node *tbl_msc,
struct platform_device *pdev,
u32 *acpi_id)
{
char hid[sizeof(tbl_msc->hardware_id_linked_device) + 1] = { 0 };
bool acpi_id_valid = false;
struct acpi_device *buddy;
char uid[11];
int len;
memcpy(hid, &tbl_msc->hardware_id_linked_device,
sizeof(tbl_msc->hardware_id_linked_device));
if (!strcmp(hid, ACPI_PROCESSOR_CONTAINER_HID)) {
*acpi_id = tbl_msc->instance_id_linked_device;
acpi_id_valid = true;
}
len = snprintf(uid, sizeof(uid), "%u",
tbl_msc->instance_id_linked_device);
if (len >= sizeof(uid)) {
pr_debug("Failed to convert uid of device for power management.");
return acpi_id_valid;
}
buddy = acpi_dev_get_first_match_dev(hid, uid, -1);
if (buddy) {
device_link_add(&pdev->dev, &buddy->dev, DL_FLAG_STATELESS);
acpi_dev_put(buddy);
}
return acpi_id_valid;
}
static int decode_interface_type(struct acpi_mpam_msc_node *tbl_msc,
enum mpam_msc_iface *iface)
{
switch (tbl_msc->interface_type) {
case ACPI_MPAM_MSC_IFACE_MMIO:
*iface = MPAM_IFACE_MMIO;
return 0;
case ACPI_MPAM_MSC_IFACE_PCC:
*iface = MPAM_IFACE_PCC;
return 0;
default:
return -EINVAL;
}
}
static struct platform_device * __init acpi_mpam_parse_msc(struct acpi_mpam_msc_node *tbl_msc)
{
struct platform_device *pdev __free(platform_device_put) =
platform_device_alloc("mpam_msc", tbl_msc->identifier);
int next_res = 0, next_prop = 0, err;
/* pcc, nrdy, affinity and a sentinel */
struct property_entry props[4] = { 0 };
/* mmio, 2xirq, no sentinel. */
struct resource res[3] = { 0 };
struct acpi_device *companion;
enum mpam_msc_iface iface;
char uid[16];
u32 acpi_id;
if (!pdev)
return ERR_PTR(-ENOMEM);
/* Some power management is described in the namespace: */
err = snprintf(uid, sizeof(uid), "%u", tbl_msc->identifier);
if (err > 0 && err < sizeof(uid)) {
companion = acpi_dev_get_first_match_dev("ARMHAA5C", uid, -1);
if (companion) {
ACPI_COMPANION_SET(&pdev->dev, companion);
acpi_dev_put(companion);
} else {
pr_debug("MSC.%u: missing namespace entry\n", tbl_msc->identifier);
}
}
if (decode_interface_type(tbl_msc, &iface)) {
pr_debug("MSC.%u: unknown interface type\n", tbl_msc->identifier);
return ERR_PTR(-EINVAL);
}
if (iface == MPAM_IFACE_MMIO) {
res[next_res++] = DEFINE_RES_MEM_NAMED(tbl_msc->base_address,
tbl_msc->mmio_size,
"MPAM:MSC");
} else if (iface == MPAM_IFACE_PCC) {
props[next_prop++] = PROPERTY_ENTRY_U32("pcc-channel",
tbl_msc->base_address);
}
acpi_mpam_parse_irqs(pdev, tbl_msc, res, &next_res);
WARN_ON_ONCE(next_res > ARRAY_SIZE(res));
err = platform_device_add_resources(pdev, res, next_res);
if (err)
return ERR_PTR(err);
props[next_prop++] = PROPERTY_ENTRY_U32("arm,not-ready-us",
tbl_msc->max_nrdy_usec);
/*
* The MSC's CPU affinity is described via its linked power
* management device, but only if it points at a Processor or
* Processor Container.
*/
if (parse_msc_pm_link(tbl_msc, pdev, &acpi_id))
props[next_prop++] = PROPERTY_ENTRY_U32("cpu_affinity", acpi_id);
WARN_ON_ONCE(next_prop > ARRAY_SIZE(props) - 1);
err = device_create_managed_software_node(&pdev->dev, props, NULL);
if (err)
return ERR_PTR(err);
/*
* Stash the table entry for acpi_mpam_parse_resources() to discover
* what this MSC controls.
*/
err = platform_device_add_data(pdev, tbl_msc, tbl_msc->length);
if (err)
return ERR_PTR(err);
err = platform_device_add(pdev);
if (err)
return ERR_PTR(err);
return_ptr(pdev);
}
static int __init acpi_mpam_parse(void)
{
char *table_end, *table_offset;
struct acpi_mpam_msc_node *tbl_msc;
struct platform_device *pdev;
if (acpi_disabled || !system_supports_mpam())
return 0;
struct acpi_table_header *table __free(acpi_put_table) =
acpi_get_table_pointer(ACPI_SIG_MPAM, 0);
if (IS_ERR(table))
return 0;
if (table->revision < 1) {
pr_debug("MPAM ACPI table revision %d not supported\n", table->revision);
return 0;
}
table_offset = (char *)(table + 1);
table_end = (char *)table + table->length;
while (table_offset < table_end) {
tbl_msc = (struct acpi_mpam_msc_node *)table_offset;
if (table_offset + sizeof(*tbl_msc) > table_end ||
table_offset + tbl_msc->length > table_end) {
pr_err("MSC entry overlaps end of ACPI table\n");
return -EINVAL;
}
table_offset += tbl_msc->length;
/*
* If any of the reserved fields are set, make no attempt to
* parse the MSC structure. This MSC will still be counted by
* acpi_mpam_count_msc(), meaning the MPAM driver can't probe
* against all MSC, and will never be enabled. There is no way
* to enable it safely, because we cannot determine safe
* system-wide partid and pmg ranges in this situation.
*/
if (tbl_msc->reserved || tbl_msc->reserved1 || tbl_msc->reserved2) {
pr_err_once("Unrecognised MSC, MPAM not usable\n");
pr_debug("MSC.%u: reserved field set\n", tbl_msc->identifier);
continue;
}
if (!tbl_msc->mmio_size) {
pr_debug("MSC.%u: marked as disabled\n", tbl_msc->identifier);
continue;
}
pdev = acpi_mpam_parse_msc(tbl_msc);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
}
return 0;
}
/**
* acpi_mpam_count_msc() - Count the number of MSC described by firmware.
*
* Returns the number of MSCs, or zero for an error.
*
* This can be called before or in parallel with acpi_mpam_parse().
*/
int acpi_mpam_count_msc(void)
{
char *table_end, *table_offset;
struct acpi_mpam_msc_node *tbl_msc;
int count = 0;
if (acpi_disabled || !system_supports_mpam())
return 0;
struct acpi_table_header *table __free(acpi_put_table) =
acpi_get_table_pointer(ACPI_SIG_MPAM, 0);
if (IS_ERR(table))
return 0;
if (table->revision < 1)
return 0;
table_offset = (char *)(table + 1);
table_end = (char *)table + table->length;
while (table_offset < table_end) {
tbl_msc = (struct acpi_mpam_msc_node *)table_offset;
if (table_offset + sizeof(*tbl_msc) > table_end)
return -EINVAL;
if (tbl_msc->length < sizeof(*tbl_msc))
return -EINVAL;
if (tbl_msc->length > table_end - table_offset)
return -EINVAL;
table_offset += tbl_msc->length;
if (!tbl_msc->mmio_size)
continue;
count++;
}
return count;
}
/*
* Call after ACPI devices have been created, which happens behind acpi_scan_init()
* called from subsys_initcall(). PCC requires the mailbox driver, which is
* initialised from postcore_initcall().
*/
subsys_initcall_sync(acpi_mpam_parse);
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/acpi.h>
#include <linux/export.h>
#include "../internal.h"
#define SMCCC_SOC_ID_T241 0x036b0241
int acpi_arch_thermal_cpufreq_pctg(void)
{
s32 soc_id = arm_smccc_get_soc_id_version();
/*
* Check JEP106 code for NVIDIA Tegra241 chip (036b:0241) and
* reduce the CPUFREQ Thermal reduction percentage to 5%.
*/
if (soc_id == SMCCC_SOC_ID_T241)
return 5;
return 0;
}
EXPORT_SYMBOL_GPL(acpi_arch_thermal_cpufreq_pctg);