// SPDX-License-Identifier: GPL-2.0
/*
* pptt.c - parsing of Processor Properties Topology Table (PPTT)
*
* Copyright (C) 2018, ARM
*
* This file implements parsing of the Processor Properties Topology Table
* which is optionally used to describe the processor and cache topology.
* Due to the relative pointers used throughout the table, this doesn't
* leverage the existing subtable parsing in the kernel.
*
* The PPTT structure is an inverted tree, with each node potentially
* holding one or two inverted tree data structures describing
* the caches available at that level. Each cache structure optionally
* contains properties describing the cache at a given level which can be
* used to override hardware probed values.
*/
#define pr_fmt(fmt) "ACPI PPTT: " fmt
#include <linux/acpi.h>
#include <linux/cacheinfo.h>
#include <acpi/processor.h>
/*
* The acpi_pptt_cache_v1 in actbl2.h, which is imported from acpica,
* only contains the cache_id field rather than all the fields of the
* Cache Type Structure. Use this alternative structure until it is
* resolved in acpica.
*/
struct acpi_pptt_cache_v1_full {
struct acpi_subtable_header header;
u16 reserved;
u32 flags;
u32 next_level_of_cache;
u32 size;
u32 number_of_sets;
u8 associativity;
u8 attributes;
u16 line_size;
u32 cache_id;
} __packed;
static struct acpi_subtable_header *fetch_pptt_subtable(struct acpi_table_header *table_hdr,
u32 pptt_ref)
{
struct acpi_subtable_header *entry;
/* there isn't a subtable at reference 0 */
if (pptt_ref < sizeof(struct acpi_subtable_header))
return NULL;
if (pptt_ref + sizeof(struct acpi_subtable_header) > table_hdr->length)
return NULL;
entry = ACPI_ADD_PTR(struct acpi_subtable_header, table_hdr, pptt_ref);
if (entry->length == 0)
return NULL;
if (pptt_ref + entry->length > table_hdr->length)
return NULL;
return entry;
}
static struct acpi_pptt_processor *fetch_pptt_node(struct acpi_table_header *table_hdr,
u32 pptt_ref)
{
return (struct acpi_pptt_processor *)fetch_pptt_subtable(table_hdr, pptt_ref);
}
static struct acpi_pptt_cache *fetch_pptt_cache(struct acpi_table_header *table_hdr,
u32 pptt_ref)
{
return (struct acpi_pptt_cache *)fetch_pptt_subtable(table_hdr, pptt_ref);
}
static struct acpi_pptt_cache_v1_full *upgrade_pptt_cache(struct acpi_pptt_cache *cache)
{
if (cache->header.length < sizeof(struct acpi_pptt_cache_v1_full))
return NULL;
/* No use for v1 if the only additional field is invalid */
if (!(cache->flags & ACPI_PPTT_CACHE_ID_VALID))
return NULL;
return (struct acpi_pptt_cache_v1_full *)cache;
}
static struct acpi_subtable_header *acpi_get_pptt_resource(struct acpi_table_header *table_hdr,
struct acpi_pptt_processor *node,
int resource)
{
u32 *ref;
if (resource >= node->number_of_priv_resources)
return NULL;
ref = ACPI_ADD_PTR(u32, node, sizeof(struct acpi_pptt_processor));
ref += resource;
return fetch_pptt_subtable(table_hdr, *ref);
}
static inline bool acpi_pptt_match_type(int table_type, int type)
{
return ((table_type & ACPI_PPTT_MASK_CACHE_TYPE) == type ||
table_type & ACPI_PPTT_CACHE_TYPE_UNIFIED & type);
}
/**
* acpi_pptt_walk_cache() - Attempt to find the requested acpi_pptt_cache
* @table_hdr: Pointer to the head of the PPTT table
* @local_level: passed res reflects this cache level
* @split_levels: Number of split cache levels (data/instruction).
* @res: cache resource in the PPTT we want to walk
* @found: returns a pointer to the requested level if found
* @level: the requested cache level
* @type: the requested cache type
*
* Attempt to find a given cache level, while counting the max number
* of cache levels for the cache node.
*
* Given a pptt resource, verify that it is a cache node, then walk
* down each level of caches, counting how many levels are found
* as well as checking the cache type (icache, dcache, unified). If a
* level & type match, then we set found, and continue the search.
* Once the entire cache branch has been walked return its max
* depth.
*
* Return: The cache structure and the level we terminated with.
*/
static unsigned int acpi_pptt_walk_cache(struct acpi_table_header *table_hdr,
unsigned int local_level,
unsigned int *split_levels,
struct acpi_subtable_header *res,
struct acpi_pptt_cache **found,
unsigned int level, int type)
{
struct acpi_pptt_cache *cache;
if (res->type != ACPI_PPTT_TYPE_CACHE)
return 0;
cache = (struct acpi_pptt_cache *) res;
while (cache) {
local_level++;
if (!(cache