/* * Events defined in TRM for MN, HN-I and SBSX are actually watchpoints set on * their ports in XP they are connected to. For the sake of usability they are * explicitly defined here (and translated into a relevant watchpoint in * arm_ccn_pmu_event_init()) so the user can easily request them without deep * knowledge of the flit format.
*/
/* * RN-I & RN-D (RN-D = RN-I + DVM) nodes have different type ID depending * on configuration. One of them is picked to represent the whole group, * as they all share the same event types.
*/ #define CCN_EVENT_RNI(_name, _event) { .attr = CCN_EVENT_ATTR(rni_##_name), \
.type = CCN_TYPE_RNI_3P, .event = _event, }
res = sysfs_emit(buf, "type=0x%x", event->type); if (event->event)
res += sysfs_emit_at(buf, res, ",event=0x%x", event->event); if (event->def)
res += sysfs_emit_at(buf, res, ",%s", event->def); if (event->mask)
res += sysfs_emit_at(buf, res, ",mask=0x%x", event->mask);
/* Arguments required by an event */ switch (event->type) { case CCN_TYPE_CYCLES: break; case CCN_TYPE_XP:
res += sysfs_emit_at(buf, res, ",xp=?,vc=?"); if (event->event == CCN_EVENT_WATCHPOINT)
res += sysfs_emit_at(buf, res, ",port=?,dir=?,cmp_l=?,cmp_h=?,mask=?"); else
res += sysfs_emit_at(buf, res, ",bus=?");
break; case CCN_TYPE_MN:
res += sysfs_emit_at(buf, res, ",node=%d", ccn->mn_id); break; default:
res += sysfs_emit_at(buf, res, ",node=?"); break;
}
/* * Default poll period is 10ms, which is way over the top anyway, * as in the worst case scenario (an event every cycle), with 1GHz * clocked bus, the smallest, 32 bit counter will overflow in * more than 4s.
*/ staticunsignedint arm_ccn_pmu_poll_period_us = 10000;
module_param_named(pmu_poll_period_us, arm_ccn_pmu_poll_period_us, uint,
S_IRUGO | S_IWUSR);
staticint arm_ccn_pmu_alloc_bit(unsignedlong *bitmap, unsignedlong size)
{ int bit;
do {
bit = find_first_zero_bit(bitmap, size); if (bit >= size) return -EAGAIN;
} while (test_and_set_bit(bit, bitmap));
return bit;
}
/* All RN-I and RN-D nodes have identical PMUs */ staticint arm_ccn_pmu_type_eq(u32 a, u32 b)
{ if (a == b) return 1;
switch (a) { case CCN_TYPE_RNI_1P: case CCN_TYPE_RNI_2P: case CCN_TYPE_RNI_3P: case CCN_TYPE_RND_1P: case CCN_TYPE_RND_2P: case CCN_TYPE_RND_3P: switch (b) { case CCN_TYPE_RNI_1P: case CCN_TYPE_RNI_2P: case CCN_TYPE_RNI_3P: case CCN_TYPE_RND_1P: case CCN_TYPE_RND_2P: case CCN_TYPE_RND_3P: return 1;
} break;
}
/* Allocate an event source or a watchpoint */ if (type == CCN_TYPE_XP && event_id == CCN_EVENT_WATCHPOINT)
bit = arm_ccn_pmu_alloc_bit(source->xp.dt_cmp_mask,
CCN_NUM_XP_WATCHPOINTS); else
bit = arm_ccn_pmu_alloc_bit(source->pmu_events_mask,
CCN_NUM_PMU_EVENTS); if (bit < 0) {
dev_dbg(ccn->dev, "No more event sources/watchpoints on node/XP %d!\n",
node_xp);
clear_bit(hw->idx, ccn->dt.pmu_counters_mask); return -EAGAIN;
}
hw->config_base = bit;
if (event->cpu < 0) {
dev_dbg(ccn->dev, "Can't provide per-task data!\n"); return -EOPNOTSUPP;
} /* * Many perf core operations (eg. events rotation) operate on a * single CPU context. This is obvious for CPU PMUs, where one * expects the same sets of events being observed on all CPUs, * but can lead to issues for off-core PMUs, like CCN, where each * event could be theoretically assigned to a different CPU. To * mitigate this, we enforce CPU assignment to one, selected * processor (the one described in the "cpumask" attribute).
*/
event->cpu = ccn->dt.cpu;
node_xp = CCN_CONFIG_NODE(event->attr.config);
type = CCN_CONFIG_TYPE(event->attr.config);
event_id = CCN_CONFIG_EVENT(event->attr.config);
/* Validate node/xp vs topology */ switch (type) { case CCN_TYPE_MN: if (node_xp != ccn->mn_id) {
dev_dbg(ccn->dev, "Invalid MN ID %d!\n", node_xp); return -EINVAL;
} break; case CCN_TYPE_XP: if (node_xp >= ccn->num_xps) {
dev_dbg(ccn->dev, "Invalid XP ID %d!\n", node_xp); return -EINVAL;
} break; case CCN_TYPE_CYCLES: break; default: if (node_xp >= ccn->num_nodes) {
dev_dbg(ccn->dev, "Invalid node ID %d!\n", node_xp); return -EINVAL;
} if (!arm_ccn_pmu_type_eq(type, ccn->node[node_xp].type)) {
dev_dbg(ccn->dev, "Invalid type 0x%x for node %d!\n",
type, node_xp); return -EINVAL;
} break;
}
/* Validate event ID vs available for the type */ for (i = 0, valid = 0; i < ARRAY_SIZE(arm_ccn_pmu_events) && !valid;
i++) { struct arm_ccn_pmu_event *e = &arm_ccn_pmu_events[i];
u32 port = CCN_CONFIG_PORT(event->attr.config);
u32 vc = CCN_CONFIG_VC(event->attr.config);
if (!arm_ccn_pmu_type_eq(type, e->type)) continue; if (event_id != e->event) continue; if (e->num_ports && port >= e->num_ports) {
dev_dbg(ccn->dev, "Invalid port %d for node/XP %d!\n",
port, node_xp); return -EINVAL;
} if (e->num_vcs && vc >= e->num_vcs) {
dev_dbg(ccn->dev, "Invalid vc %d for node/XP %d!\n",
vc, node_xp); return -EINVAL;
}
valid = 1;
} if (!valid) {
dev_dbg(ccn->dev, "Invalid event 0x%x for node/XP %d!\n",
event_id, node_xp); return -EINVAL;
}
/* Watchpoint-based event for a node is actually set on XP */ if (event_id == CCN_EVENT_WATCHPOINT && type != CCN_TYPE_XP) {
u32 port;
type = CCN_TYPE_XP;
port = arm_ccn_node_to_xp_port(node_xp);
node_xp = arm_ccn_node_to_xp(node_xp);
/* * We must NOT create groups containing mixed PMUs, although software * events are acceptable (for example to create a CCN group * periodically read when a hrtimer aka cpu-clock leader triggers).
*/ if (event->group_leader->pmu != event->pmu &&
!is_software_event(event->group_leader)) return -EINVAL;
static u64 arm_ccn_pmu_read_counter(struct arm_ccn *ccn, int idx)
{
u64 res;
if (idx == CCN_IDX_PMU_CYCLE_COUNTER) { #ifdef readq
res = readq(ccn->dt.base + CCN_DT_PMCCNTR); #else /* 40 bit counter, can do snapshot and read in two parts */
writel(0x1, ccn->dt.base + CCN_DT_PMSR_REQ); while (!(readl(ccn->dt.base + CCN_DT_PMSR) & 0x1))
;
writel(0x1, ccn->dt.base + CCN_DT_PMSR_CLR);
res = readl(ccn->dt.base + CCN_DT_PMCCNTRSR + 4) & 0xff;
res <<= 32;
res |= readl(ccn->dt.base + CCN_DT_PMCCNTRSR); #endif
} else {
res = readl(ccn->dt.base + CCN_DT_PMEVCNT(idx));
}
/* Nothing to do for cycle counter */ if (hw->idx == CCN_IDX_PMU_CYCLE_COUNTER) return;
if (CCN_CONFIG_TYPE(event->attr.config) == CCN_TYPE_XP)
xp = &ccn->xp[CCN_CONFIG_XP(event->attr.config)]; else
xp = &ccn->xp[arm_ccn_node_to_xp(
CCN_CONFIG_NODE(event->attr.config))];
if (enable)
dt_cfg = hw->event_base; else
dt_cfg = CCN_XP_DT_CONFIG__DT_CFG__PASS_THROUGH;
spin_lock(&ccn->dt.config_lock);
val = readl(xp->base + CCN_XP_DT_CONFIG);
val &= ~(CCN_XP_DT_CONFIG__DT_CFG__MASK <<
CCN_XP_DT_CONFIG__DT_CFG__SHIFT(hw->idx));
val |= dt_cfg << CCN_XP_DT_CONFIG__DT_CFG__SHIFT(hw->idx);
writel(val, xp->base + CCN_XP_DT_CONFIG);
val = readl(source->base + CCN_XP_PMU_EVENT_SEL);
val &= ~(CCN_XP_PMU_EVENT_SEL__ID__MASK <<
CCN_XP_PMU_EVENT_SEL__ID__SHIFT(hw->config_base));
val |= id << CCN_XP_PMU_EVENT_SEL__ID__SHIFT(hw->config_base);
writel(val, source->base + CCN_XP_PMU_EVENT_SEL);
}
port = arm_ccn_node_to_xp_port(CCN_CONFIG_NODE(event->attr.config));
hw->event_base = CCN_XP_DT_CONFIG__DT_CFG__DEVICE_PMU_EVENT(port,
hw->config_base);
/* These *_event_sel regs should be identical, but let's make sure... */
BUILD_BUG_ON(CCN_HNF_PMU_EVENT_SEL != CCN_SBAS_PMU_EVENT_SEL);
BUILD_BUG_ON(CCN_SBAS_PMU_EVENT_SEL != CCN_RNI_PMU_EVENT_SEL);
BUILD_BUG_ON(CCN_HNF_PMU_EVENT_SEL__ID__SHIFT(1) !=
CCN_SBAS_PMU_EVENT_SEL__ID__SHIFT(1));
BUILD_BUG_ON(CCN_SBAS_PMU_EVENT_SEL__ID__SHIFT(1) !=
CCN_RNI_PMU_EVENT_SEL__ID__SHIFT(1));
BUILD_BUG_ON(CCN_HNF_PMU_EVENT_SEL__ID__MASK !=
CCN_SBAS_PMU_EVENT_SEL__ID__MASK);
BUILD_BUG_ON(CCN_SBAS_PMU_EVENT_SEL__ID__MASK !=
CCN_RNI_PMU_EVENT_SEL__ID__MASK); if (WARN_ON(type != CCN_TYPE_HNF && type != CCN_TYPE_SBAS &&
!arm_ccn_pmu_type_eq(type, CCN_TYPE_RNI_3P))) return;
/* Set the event id for the pre-allocated counter */
val = readl(source->base + CCN_HNF_PMU_EVENT_SEL);
val &= ~(CCN_HNF_PMU_EVENT_SEL__ID__MASK <<
CCN_HNF_PMU_EVENT_SEL__ID__SHIFT(hw->config_base));
val |= CCN_CONFIG_EVENT(event->attr.config) <<
CCN_HNF_PMU_EVENT_SEL__ID__SHIFT(hw->config_base);
writel(val, source->base + CCN_HNF_PMU_EVENT_SEL);
}
/* Cycle counter requires no setup */ if (hw->idx == CCN_IDX_PMU_CYCLE_COUNTER) return;
if (CCN_CONFIG_TYPE(event->attr.config) == CCN_TYPE_XP)
xp = CCN_CONFIG_XP(event->attr.config); else
xp = arm_ccn_node_to_xp(CCN_CONFIG_NODE(event->attr.config));
spin_lock(&ccn->dt.config_lock);
/* Set the DT bus "distance" register */
offset = (hw->idx / 4) * 4;
val = readl(ccn->dt.base + CCN_DT_ACTIVE_DSM + offset);
val &= ~(CCN_DT_ACTIVE_DSM__DSM_ID__MASK <<
CCN_DT_ACTIVE_DSM__DSM_ID__SHIFT(hw->idx % 4));
val |= xp << CCN_DT_ACTIVE_DSM__DSM_ID__SHIFT(hw->idx % 4);
writel(val, ccn->dt.base + CCN_DT_ACTIVE_DSM + offset);
if (CCN_CONFIG_TYPE(event->attr.config) == CCN_TYPE_XP) { if (CCN_CONFIG_EVENT(event->attr.config) ==
CCN_EVENT_WATCHPOINT)
arm_ccn_pmu_xp_watchpoint_config(event); else
arm_ccn_pmu_xp_event_config(event);
} else {
arm_ccn_pmu_node_event_config(event);
}
staticint arm_ccn_pmu_event_add(struct perf_event *event, int flags)
{ int err; struct hw_perf_event *hw = &event->hw; struct arm_ccn *ccn = pmu_to_arm_ccn(event->pmu);
err = arm_ccn_pmu_event_alloc(event); if (err) return err;
/* * Pin the timer, so that the overflows are handled by the chosen * event->cpu (this is the same one as presented in "cpumask" * attribute).
*/ if (!ccn->irq && arm_ccn_pmu_active_counters(ccn) == 1)
hrtimer_start(&ccn->dt.hrtimer, arm_ccn_pmu_timer_period(),
HRTIMER_MODE_REL_PINNED);
arm_ccn_pmu_event_config(event);
hw->state = PERF_HES_STOPPED;
if (flags & PERF_EF_START)
arm_ccn_pmu_event_start(event, PERF_EF_UPDATE);
/* No overflow interrupt? Have to use a timer instead. */ if (!ccn->irq) {
dev_info(ccn->dev, "No access to interrupts, using timer.\n");
hrtimer_setup(&ccn->dt.hrtimer, arm_ccn_pmu_timer_handler, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
}
/* Pick one CPU which we will use to collect data from CCN... */
ccn->dt.cpu = raw_smp_processor_id();
/* Also make sure that the overflow interrupt is handled by this CPU */ if (ccn->irq) {
err = irq_set_affinity(ccn->irq, cpumask_of(ccn->dt.cpu)); if (err) {
dev_err(ccn->dev, "Failed to set interrupt affinity!\n"); goto error_set_affinity;
}
}
/* PMU overflow is a special case */
err_or = err_sig_val[0] = readl(ccn->base + CCN_MN_ERR_SIG_VAL_63_0); if (err_or & CCN_MN_ERR_SIG_VAL_63_0__DT) {
err_or &= ~CCN_MN_ERR_SIG_VAL_63_0__DT;
res = arm_ccn_pmu_overflow_handler(&ccn->dt);
}
/* Have to read all err_sig_vals to clear them */ for (i = 1; i < ARRAY_SIZE(err_sig_val); i++) {
err_sig_val[i] = readl(ccn->base +
CCN_MN_ERR_SIG_VAL_63_0 + i * 4);
err_or |= err_sig_val[i];
} if (err_or)
res |= arm_ccn_error_handler(ccn, err_sig_val);
if (res != IRQ_NONE)
writel(CCN_MN_ERRINT_STATUS__INTREQ__DESSERT,
ccn->base + CCN_MN_ERRINT_STATUS);
return res;
}
staticint arm_ccn_probe(struct platform_device *pdev)
{ struct arm_ccn *ccn; int irq; int err;
ccn->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ccn->base)) return PTR_ERR(ccn->base);
irq = platform_get_irq(pdev, 0); if (irq < 0) return irq;
/* Check if we can use the interrupt */
writel(CCN_MN_ERRINT_STATUS__PMU_EVENTS__DISABLE,
ccn->base + CCN_MN_ERRINT_STATUS); if (readl(ccn->base + CCN_MN_ERRINT_STATUS) &
CCN_MN_ERRINT_STATUS__PMU_EVENTS__DISABLED) { /* Can set 'disable' bits, so can acknowledge interrupts */
writel(CCN_MN_ERRINT_STATUS__PMU_EVENTS__ENABLE,
ccn->base + CCN_MN_ERRINT_STATUS);
err = devm_request_irq(ccn->dev, irq, arm_ccn_irq_handler,
IRQF_NOBALANCING | IRQF_NO_THREAD,
dev_name(ccn->dev), ccn); if (err) return err;
ccn->irq = irq;
}
/* Build topology */
err = arm_ccn_for_each_valid_region(ccn, arm_ccn_get_nodes_num); if (err) return err;
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.