// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019, Intel Corporation.
*
* Heterogeneous Memory Attributes Table (HMAT) representation
*
* This program parses and reports the platform's HMAT tables, and registers
* the applicable attributes with the node's interfaces.
*/
#define pr_fmt(fmt) "acpi/hmat: " fmt
#define dev_fmt(fmt) "acpi/hmat: " fmt
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/platform_device.h>
#include <linux/list_sort.h>
#include <linux/memregion.h>
#include <linux/memory.h>
#include <linux/mutex.h>
#include <linux/node.h>
#include <linux/sysfs.h>
static u8 hmat_revision;
static LIST_HEAD(targets);
static LIST_HEAD(initiators);
static LIST_HEAD(localities);
static DEFINE_MUTEX(target_lock);
/*
* The defined enum order is used to prioritize attributes to break ties when
* selecting the best performing node.
*/
enum locality_types {
WRITE_LATENCY,
READ_LATENCY,
WRITE_BANDWIDTH,
READ_BANDWIDTH,
};
static struct memory_locality *localities_types[4];
struct target_cache {
struct list_head node;
struct node_cache_attrs cache_attrs;
};
struct memory_target {
struct list_head node;
unsigned int memory_pxm;
unsigned int processor_pxm;
struct resource memregions;
struct node_hmem_attrs hmem_attrs[2];
struct list_head caches;
struct node_cache_attrs cache_attrs;
bool registered;
};
struct memory_initiator {
struct list_head node;
unsigned int processor_pxm;
bool has_cpu;
};
struct memory_locality {
struct list_head node;
struct acpi_hmat_locality *hmat_loc;
};
static struct memory_initiator *find_mem_initiator(unsigned int cpu_pxm)
{
struct memory_initiator *initiator;
list_for_each_entry(initiator, &initiators, node)
if (initiator->processor_pxm == cpu_pxm)
return initiator;
return NULL;
}
static struct memory_target *find_mem_target(unsigned int mem_pxm)
{
struct memory_target *target;
list_for_each_entry(target, &targets, node)
if (target->memory_pxm == mem_pxm)
return target;
return NULL;
}
static __init void alloc_memory_initiator(unsigned int cpu_pxm)
{
struct memory_initiator *initiator;
if (pxm_to_node(cpu_pxm) == NUMA_NO_NODE)
return;
initiator = find_mem_initiator(cpu_pxm);
if (initiator)
return;
initiator = kzalloc(sizeof(*initiator), GFP_KERNEL);
if (!initiator)
return;
initiator->processor_pxm = cpu_pxm;
initiator->has_cpu = node_state(pxm_to_node(cpu_pxm), N_CPU);
list_add_tail(&initiator->node, &initiators);
}
static __init void alloc_memory_target(unsigned int mem_pxm,
resource_size_t start, resource_size_t len)
{
struct memory_target *target;
target = find_mem_target(mem_pxm);
if (!target) {
target = kzalloc(sizeof(*target), GFP_KERNEL);
if (!target)
return;
target->memory_pxm = mem_pxm;
target->processor_pxm = PXM_INVAL;
target->memregions = (struct resource) {
.name = "ACPI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
list_add_tail(&target->node, &targets);
INIT_LIST_HEAD(&target->caches);
}
/*
* There are potentially multiple ranges per PXM, so record each
* in the per-target memregions resource tree.
*/
if (!__request_region(&target->memregions, start, len, "memory target",
IORESOURCE_MEM))
pr_warn("failed to reserve %#llx - %#llx in pxm: %d\n",
start, start + len, mem_pxm);
}
static __init const char *hmat_data_type(u8 type)
{
switch (type) {
case ACPI_HMAT_ACCESS_LATENCY:
return "Access Latency";
case ACPI_HMAT_READ_LATENCY:
return "Read Latency";
case ACPI_HMAT_WRITE_LATENCY:
return "Write Latency";
case ACPI_HMAT_ACCESS_BANDWIDTH:
return "Access Bandwidth";
case ACPI_HMAT_READ_BANDWIDTH:
return "Read Bandwidth";
case ACPI_HMAT_WRITE_BANDWIDTH:
return "Write Bandwidth";
default:
return "Reserved";
}
}
static __init const char *hmat_data_type_suffix(u8 type)
{
switch (type) {
case ACPI_HMAT_ACCESS_LATENCY:
case ACPI_HMAT_READ_LATENCY:
case ACPI_HMAT_WRITE_LATENCY:
return " nsec";
case ACPI_HMAT_ACCESS_BANDWIDTH:
case ACPI_HMAT_READ_BANDWIDTH:
case ACPI_HMAT_WRITE_BANDWIDTH:
return " MB/s";
default:
return "";
}
}
static u32 hmat_normalize(u16 entry, u64 base, u8 type)
{
u32 value;
/*
* Check for invalid and overflow values
*/
if (entry == 0xffff || !entry)
return 0;
else