// 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
#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>
#include <linux/dax.h>
#include <linux/memory-tiers.h>
static u8 hmat_revision;
static int hmat_disable __initdata;
void __init disable_hmat(void)
{
hmat_disable = 1;
}
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;
};
enum {
NODE_ACCESS_CLASS_GENPORT_SINK_LOCAL = ACCESS_COORDINATE_MAX,
NODE_ACCESS_CLASS_GENPORT_SINK_CPU,
NODE_ACCESS_CLASS_MAX,
};
struct memory_target {
struct list_head node;
unsigned int memory_pxm;
unsigned int processor_pxm;
struct resource memregions;
struct access_coordinate coord[NODE_ACCESS_CLASS_MAX];
struct list_head caches;
struct node_cache_attrs cache_attrs;
u8 gen_port_device_handle[ACPI_SRAT_DEVICE_HANDLE_SIZE];
bool registered;
bool ext_updated; /* externally updated */
};
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 struct memory_target *acpi_find_genport_target(u32 uid)
{
struct memory_target *target;
u32 target_uid;
u8 *uid_ptr;
list_for_each_entry(target, &targets, node) {
uid_ptr = target->gen_port_device_handle + 8;
target_uid = *(u32 *)uid_ptr;
if (uid == target_uid)
return target;
}
return NULL;
}
/**
* acpi_get_genport_coordinates - Retrieve the access coordinates for a generic port
* @uid: ACPI unique id
* @coord: The access coordinates written back out for the generic port.
* Expect 2 levels array.
*
* Return: 0 on success. Errno on failure.
*
* Only supports device handles that are ACPI. Assume ACPI0016 HID for CXL.
*/
int acpi_get_genport_coordinates(u32 uid,
struct access_coordinate *coord)
{
struct memory_target *target;
guard(mutex)(&target_lock);
target = acpi_find_genport_target(uid);
if (!target)
return -ENOENT;
coord[ACCESS_COORDINATE_LOCAL] =
target->coord[NODE_ACCESS_CLASS_GENPORT_SINK_LOCAL];
coord[ACCESS_COORDINATE_CPU] =
target->coord[NODE_ACCESS_CLASS_GENPORT_SINK_CPU];
return 0;
}
EXPORT_SYMBOL_NS_GPL(acpi_get_genport_coordinates, "CXL");
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 struct memory_target *alloc_target(unsigned int mem_pxm)
{
struct memory_target *target;
target = find_mem_target(mem_pxm);
if (!target) {
target = kzalloc(sizeof(*target), GFP_KERNEL);
if (!target)
return NULL;
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);
}
return target;
}
static