/* * Copyright(c) 2023 Huawei * * The CXL 3.0 specification includes a standard Performance Monitoring Unit, * called the CXL PMU, or CPMU. In order to allow a high degree of * implementation flexibility the specification provides a wide range of * options all of which are self describing. * * Details in CXL rev 3.0 section 8.2.7 CPMU Register Interface
*/
/* * All CPMU counters are discoverable via the Event Capabilities Registers. * Each Event Capability register contains a VID / GroupID. * A counter may then count any combination (by summing) of events in * that group which are in the Supported Events Bitmask. * However, there are some complexities to the scheme. * - Fixed function counters refer to an Event Capabilities register. * That event capability register is not then used for Configurable * counters.
*/ staticint cxl_pmu_parse_caps(struct device *dev, struct cxl_pmu_info *info)
{ unsignedlong fixed_counter_event_cap_bm = 0; void __iomem *base = info->base; bool freeze_for_enable;
u64 val, eval; int i;
val = readq(base + CXL_PMU_CAP_REG);
freeze_for_enable = FIELD_GET(CXL_PMU_CAP_WRITEABLE_WHEN_FROZEN, val) &&
FIELD_GET(CXL_PMU_CAP_FREEZE, val); if (!freeze_for_enable) {
dev_err(dev, "Counters not writable while frozen\n"); return -ENODEV;
}
/* First handle fixed function counters; note if configurable counters found */ for (i = 0; i < info->num_counters; i++) { struct cxl_pmu_ev_cap *pmu_ev;
u32 events_msk;
u8 group_idx;
val = readq(base + CXL_PMU_COUNTER_CFG_REG(i));
if (FIELD_GET(CXL_PMU_COUNTER_CFG_TYPE_MSK, val) ==
CXL_PMU_COUNTER_CFG_TYPE_CONFIGURABLE) {
set_bit(i, info->conf_counter_bm);
}
if (FIELD_GET(CXL_PMU_COUNTER_CFG_TYPE_MSK, val) !=
CXL_PMU_COUNTER_CFG_TYPE_FIXED_FUN) continue;
/* In this case we know which fields are const */
group_idx = FIELD_GET(CXL_PMU_COUNTER_CFG_EVENT_GRP_ID_IDX_MSK, val);
events_msk = FIELD_GET(CXL_PMU_COUNTER_CFG_EVENTS_MSK, val);
eval = readq(base + CXL_PMU_EVENT_CAP_REG(group_idx));
pmu_ev = devm_kzalloc(dev, sizeof(*pmu_ev), GFP_KERNEL); if (!pmu_ev) return -ENOMEM;
pmu_ev->vid = FIELD_GET(CXL_PMU_EVENT_CAP_VENDOR_ID_MSK, eval);
pmu_ev->gid = FIELD_GET(CXL_PMU_EVENT_CAP_GROUP_ID_MSK, eval); /* For a fixed purpose counter use the events mask from the counter CFG */
pmu_ev->msk = events_msk;
pmu_ev->counter_idx = i; /* This list add is never unwound as all entries deleted on remove */
list_add(&pmu_ev->node, &info->event_caps_fixed); /* * Configurable counters must not use an Event Capability registers that * is in use for a Fixed counter
*/
set_bit(group_idx, &fixed_counter_event_cap_bm);
}
if (!bitmap_empty(info->conf_counter_bm, CXL_PMU_MAX_COUNTERS)) { struct cxl_pmu_ev_cap *pmu_ev; int j; /* Walk event capabilities unused by fixed counters */
for_each_clear_bit(j, &fixed_counter_event_cap_bm,
info->num_event_capabilities) {
pmu_ev = devm_kzalloc(dev, sizeof(*pmu_ev), GFP_KERNEL); if (!pmu_ev) return -ENOMEM;
/* * Filter capability at the CPMU level, so hide the attributes if the particular * filter is not supported.
*/ if (!info->filter_hdm &&
(attr == cxl_pmu_format_attr[cxl_pmu_hdm_filter_en_attr] ||
attr == cxl_pmu_format_attr[cxl_pmu_hdm_attr])) return0;
/* * CPMU specification allows for 8 filters, each with a 32 bit value... * So we need to find 8x32bits to store it in. * As the value used for disable is 0xffff_ffff, a separate enable switch * is needed.
*/
/* If counter_idx == NULL, don't try to allocate a counter. */ staticint cxl_pmu_get_event_idx(struct perf_event *event, int *counter_idx, int *event_idx)
{ struct cxl_pmu_info *info = pmu_to_cxl_pmu_info(event->pmu);
DECLARE_BITMAP(configurable_and_free, CXL_PMU_MAX_COUNTERS); struct cxl_pmu_ev_cap *pmu_ev;
u32 mask;
u16 gid, vid; int i;
vid = cxl_pmu_config_get_vid(event);
gid = cxl_pmu_config_get_gid(event);
mask = cxl_pmu_config_get_mask(event);
pmu_ev = cxl_pmu_find_fixed_counter_ev_cap(info, vid, gid, mask); if (!IS_ERR(pmu_ev)) { if (!counter_idx) return0; if (!test_bit(pmu_ev->counter_idx, info->used_counter_bm)) {
*counter_idx = pmu_ev->counter_idx; return0;
} /* Fixed counter is in use, but maybe a configurable one? */
}
pmu_ev = cxl_pmu_find_config_counter_ev_cap(info, vid, gid, mask); if (!IS_ERR(pmu_ev)) { if (!counter_idx) return0;
/* Top level type sanity check - is this a Hardware Event being requested */ if (event->attr.type != event->pmu->type) return -ENOENT;
if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) return -EOPNOTSUPP; /* TODO: Validation of any filter */
/* * Verify that it is possible to count what was requested. Either must * be a fixed counter that is a precise match or a configurable counter * where this is a subset.
*/
rc = cxl_pmu_get_event_idx(event, NULL, NULL); if (rc < 0) return rc;
/* * Whilst bits above number of counters are RsvdZ * they are unlikely to be repurposed given * number of counters is allowed to be 64 leaving * no reserved bits. Hence this is only slightly * naughty.
*/
writeq(GENMASK_ULL(63, 0), base + CXL_PMU_FREEZE_REG);
}
/* * All paths to here should either set these flags directly or * call cxl_pmu_event_stop() which will ensure the correct state.
*/ if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED))) return;
/* * Currently only hdm filter control is implemented, this code will * want generalizing when more filters are added.
*/ if (info->filter_hdm) { if (cxl_pmu_config1_hdm_filter_en(event))
cfg = cxl_pmu_config2_get_hdm_decoder(event); else
cfg = GENMASK(31, 0); /* No filtering if 0xFFFF_FFFF */
writeq(cfg, base + CXL_PMU_FILTER_CFG_REG(hwc->idx, 0));
}
/* Fixed purpose counters have next two fields RO */ if (test_bit(hwc->idx, info->conf_counter_bm)) {
cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EVENT_GRP_ID_IDX_MSK,
hwc->event_base);
cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EVENTS_MSK,
cxl_pmu_config_get_mask(event));
}
cfg &= ~CXL_PMU_COUNTER_CFG_THRESHOLD_MSK; /* * For events that generate only 1 count per clock the CXL 3.0 spec * states the threshold shall be set to 1 but if set to 0 it will * count the raw value anwyay? * There is no definition of what events will count multiple per cycle * and hence to which non 1 values of threshold can apply. * (CXL 3.0 8.2.7.2.1 Counter Configuration - threshold field definition)
*/
cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_THRESHOLD_MSK,
cxl_pmu_config1_get_threshold(event));
writeq(cfg, base + CXL_PMU_COUNTER_CFG_REG(hwc->idx));
local64_set(&hwc->prev_count, 0);
writeq(0, base + CXL_PMU_COUNTER_REG(hwc->idx));
do {
prev_cnt = local64_read(&hwc->prev_count);
new_cnt = cxl_pmu_read_counter(event);
} while (local64_cmpxchg(&hwc->prev_count, prev_cnt, new_cnt) != prev_cnt);
/* * If we know an overflow occur then take that into account. * Note counter is not reset as that would lose events
*/
delta = (new_cnt - prev_cnt) & GENMASK_ULL(info->counter_width - 1, 0); if (overflow && delta < GENMASK_ULL(info->counter_width - 1, 0))
delta += (1UL << info->counter_width);
info->on_cpu = cpu; /* * CPU HP lock is held so we should be guaranteed that the CPU hasn't yet * gone away again.
*/
WARN_ON(irq_set_affinity(info->irq, cpumask_of(cpu)));
info->on_cpu = -1;
target = cpumask_any_but(cpu_online_mask, cpu); if (target >= nr_cpu_ids) {
dev_err(info->pmu.dev, "Unable to find a suitable CPU\n"); return0;
}
perf_pmu_migrate_context(&info->pmu, cpu, target);
info->on_cpu = target; /* * CPU HP lock is held so we should be guaranteed that this CPU hasn't yet * gone away.
*/
WARN_ON(irq_set_affinity(info->irq, cpumask_of(target)));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.