feat(esp_hw_support): add cache access counter API

This commit is contained in:
Ivan Grokhotkov (bot)
2026-09-11 11:30:28 +02:00
committed by Ivan Grokhotkov
parent 7e5235a7a7
commit 16078650c8
43 changed files with 1407 additions and 1 deletions
+2 -1
View File
@@ -69,7 +69,8 @@ if(NOT non_os_build)
"port/${target}/esp_clk_tree.c"
"spi_bus_lock.c"
"heap_align_hw.c"
"clk_utils.c")
"clk_utils.c"
"esp_cache_cnt.c")
if(CONFIG_SOC_USB_OTG_SUPPORTED)
list(APPEND srcs "usb_phy/usb_phy.c")
endif()
+164
View File
@@ -0,0 +1,164 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Chip-agnostic implementation of the cache access counter API, built on
* top of the cache profile counter functions of hal/cache_ll.h and the
* unit descriptor table in soc/cache_periph.h.
*/
#include <inttypes.h>
#include "esp_cache_cnt.h"
#include "soc/soc_caps.h"
float esp_cache_cnt_miss_ratio(const esp_cache_cnt_data_t *data)
{
const uint32_t needed = ESP_CACHE_CNT_VALID_ACCESSES | ESP_CACHE_CNT_VALID_LINE_FILLS;
if ((data->valid_mask & needed) != needed || data->accesses == 0) {
return 0.0f;
}
return (float)data->line_fills / (float)data->accesses;
}
#if SOC_CACHE_CNT_SUPPORTED
#include "hal/cache_ll.h"
size_t esp_cache_cnt_num_units(void)
{
return SOC_CACHE_CNT_UNITS_NUM;
}
esp_err_t esp_cache_cnt_get_unit_info(size_t unit, esp_cache_cnt_unit_info_t *out)
{
if (unit >= SOC_CACHE_CNT_UNITS_NUM || out == NULL) {
return ESP_ERR_INVALID_ARG;
}
const cache_profile_counter_unit_t *desc = &cache_periph_profile_counter_units[unit];
out->name = desc->name;
out->cache_level = desc->level;
out->core_id = desc->core_id;
out->traffic_type = desc->traffic;
return ESP_OK;
}
esp_err_t esp_cache_cnt_start(void)
{
cache_ll_clear_profile_counter();
cache_ll_enable_profile_counter(true);
return ESP_OK;
}
esp_err_t esp_cache_cnt_stop(void)
{
cache_ll_enable_profile_counter(false);
return ESP_OK;
}
esp_err_t esp_cache_cnt_clear(void)
{
cache_ll_clear_profile_counter();
return ESP_OK;
}
esp_err_t esp_cache_cnt_get(size_t unit, esp_cache_cnt_data_t *out)
{
if (unit >= SOC_CACHE_CNT_UNITS_NUM || out == NULL) {
return ESP_ERR_INVALID_ARG;
}
*out = (esp_cache_cnt_data_t) { 0 };
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_HIT, &out->accesses)) {
out->valid_mask |= ESP_CACHE_CNT_VALID_ACCESSES;
}
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_MISS, &out->stall_events)) {
out->valid_mask |= ESP_CACHE_CNT_VALID_STALL_EVENTS;
}
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_CONFLICT, &out->conflicts)) {
out->valid_mask |= ESP_CACHE_CNT_VALID_CONFLICTS;
}
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_NXTLVL_RD, &out->line_fills)) {
out->valid_mask |= ESP_CACHE_CNT_VALID_LINE_FILLS;
}
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_NXTLVL_WR, &out->writebacks)) {
out->valid_mask |= ESP_CACHE_CNT_VALID_WRITEBACKS;
}
return ESP_OK;
}
static void print_counter(FILE *out, const esp_cache_cnt_data_t *data, uint32_t flag, uint32_t value)
{
if (data->valid_mask & flag) {
fprintf(out, " %12" PRIu32, value);
} else {
fprintf(out, " %12s", "-");
}
}
esp_err_t esp_cache_cnt_dump(FILE *out)
{
if (out == NULL) {
out = stdout;
}
fprintf(out, "%-20s %12s %12s %12s %12s %10s\n",
"unit", "accesses", "line fills", "writebacks", "conflicts", "miss rate");
for (size_t unit = 0; unit < SOC_CACHE_CNT_UNITS_NUM; unit++) {
esp_cache_cnt_unit_info_t info;
esp_cache_cnt_data_t data;
esp_cache_cnt_get_unit_info(unit, &info);
esp_cache_cnt_get(unit, &data);
fprintf(out, "%-20s", info.name);
print_counter(out, &data, ESP_CACHE_CNT_VALID_ACCESSES, data.accesses);
print_counter(out, &data, ESP_CACHE_CNT_VALID_LINE_FILLS, data.line_fills);
print_counter(out, &data, ESP_CACHE_CNT_VALID_WRITEBACKS, data.writebacks);
print_counter(out, &data, ESP_CACHE_CNT_VALID_CONFLICTS, data.conflicts);
fprintf(out, " %9.2f%%\n", 100.0f * esp_cache_cnt_miss_ratio(&data));
}
return ESP_OK;
}
#else // !SOC_CACHE_CNT_SUPPORTED
size_t esp_cache_cnt_num_units(void)
{
return 0;
}
esp_err_t esp_cache_cnt_get_unit_info(size_t unit, esp_cache_cnt_unit_info_t *out)
{
(void) unit;
(void) out;
return ESP_ERR_INVALID_ARG;
}
esp_err_t esp_cache_cnt_start(void)
{
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_cache_cnt_stop(void)
{
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_cache_cnt_clear(void)
{
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_cache_cnt_get(size_t unit, esp_cache_cnt_data_t *out)
{
(void) unit;
(void) out;
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_cache_cnt_dump(FILE *out)
{
(void) out;
return ESP_ERR_NOT_SUPPORTED;
}
#endif // !SOC_CACHE_CNT_SUPPORTED
@@ -0,0 +1,168 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include "esp_err.h"
#include "soc/cache_periph.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* Cache access counter API.
*
* The set of counters differs between chips: the number of cache levels, the
* number of request buses per cache, and which counters exist per bus all
* vary. Instead of a fixed list of caches, the API exposes a chip-defined
* list of counter "units". Each unit is one set of counters observing one
* traffic stream (e.g. "instruction fetches from core 0 into the L1 cache").
* Applications enumerate the units at runtime with esp_cache_cnt_num_units()
* and esp_cache_cnt_get_unit_info(), so they keep working when a new chip
* adds or removes units.
*/
/**
* @brief Description of one counter unit.
*/
typedef struct {
const char *name; /*!< Short human-readable name, e.g. "l1-icache-core0" */
uint8_t cache_level; /*!< Cache level the counters belong to, counting from the CPU.
On most chips there is a single level (the flash/PSRAM cache);
on the ESP32-P4, level 1 is the L1 cache in front of internal
memory and level 2 is the flash/PSRAM cache. */
cache_profile_traffic_t traffic_type; /*!< Kind of traffic observed */
int8_t core_id; /*!< Core the traffic originates from, or -1 if unknown/mixed */
} esp_cache_cnt_unit_info_t;
/**
* @name Flags for esp_cache_cnt_data_t::valid_mask
*
* Not every counter exists for every unit (e.g. instruction buses have no
* write-back counter). A field of esp_cache_cnt_data_t is only meaningful if
* the corresponding flag is set.
* @{
*/
#define ESP_CACHE_CNT_VALID_ACCESSES (1 << 0)
#define ESP_CACHE_CNT_VALID_STALL_EVENTS (1 << 1)
#define ESP_CACHE_CNT_VALID_CONFLICTS (1 << 2)
#define ESP_CACHE_CNT_VALID_LINE_FILLS (1 << 3)
#define ESP_CACHE_CNT_VALID_WRITEBACKS (1 << 4)
/** @} */
/**
* @brief Counter values for one unit.
*
* Note that the hardware "hit" and "miss" counters do not directly hold the
* number of hit and missed accesses:
* - accesses: the hit counter increments once for every access that completes,
* whether or not it had to wait for a line fill first.
* - stall_events: the miss counter increments repeatedly while an access is
* stalled on a miss, so it grows roughly with the total miss latency. This is
* only useful as a relative measure.
* - line_fills: the next-level read counter increments once per line fetched
* from the next level, so it is the true miss count.
*
* The miss ratio of a cache is therefore line_fills / accesses.
*/
typedef struct {
uint32_t valid_mask; /*!< Bitwise OR of ESP_CACHE_CNT_VALID_* flags for the fields below */
uint32_t accesses; /*!< Completed accesses (hardware hit counter) */
uint32_t stall_events; /*!< Miss stall events; grows with total miss latency,
NOT the number of missed accesses */
uint32_t conflicts; /*!< Conflicts between requesters on this cache */
uint32_t line_fills; /*!< Lines fetched from the next level (true miss count) */
uint32_t writebacks; /*!< Lines written back to the next level. Only present for data
traffic on chips with a write-back cache (PSRAM support,
see SOC_CACHE_WRITEBACK_SUPPORTED). */
} esp_cache_cnt_data_t;
/**
* @brief Number of counter units on this chip.
*
* @return Number of units; 0 if the chip has no cache access counters.
*/
size_t esp_cache_cnt_num_units(void);
/**
* @brief Get the description of a counter unit.
*
* @param unit Unit index, 0 to esp_cache_cnt_num_units() - 1
* @param[out] out Unit description
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if unit is out of range or out is NULL
*/
esp_err_t esp_cache_cnt_get_unit_info(size_t unit, esp_cache_cnt_unit_info_t *out);
/**
* @brief Clear and enable all cache access counters.
*
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
*/
esp_err_t esp_cache_cnt_start(void);
/**
* @brief Disable all cache access counters. Counter values are retained.
*
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
*/
esp_err_t esp_cache_cnt_stop(void);
/**
* @brief Reset all cache access counters to zero. Counting state is not changed.
*
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
*/
esp_err_t esp_cache_cnt_clear(void);
/**
* @brief Read the current counter values for the given unit.
*
* @param unit Unit index, 0 to esp_cache_cnt_num_units() - 1
* @param[out] out Counter values
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if unit is out of range or out is NULL
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
*/
esp_err_t esp_cache_cnt_get(size_t unit, esp_cache_cnt_data_t *out);
/**
* @brief Miss ratio (0.0 to 1.0) computed from a set of counter values.
*
* @param data Counter values obtained from esp_cache_cnt_get()
*
* @return Miss ratio; 0.0 if the unit does not provide the counters needed
* to compute it.
*/
float esp_cache_cnt_miss_ratio(const esp_cache_cnt_data_t *data);
/**
* @brief Print a table with the current values of all cache access counters.
*
* @param out Output stream; if NULL, print to stdout
*
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
*/
esp_err_t esp_cache_cnt_dump(FILE *out);
#ifdef __cplusplus
}
#endif