in arm-cmn.c [1646:1836]
static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
{
void __iomem *cfg_region;
struct arm_cmn_node cfg, *dn;
struct arm_cmn_dtm *dtm;
u16 child_count, child_poff;
u32 xp_offset[CMN_MAX_XPS];
u64 reg;
int i, j;
size_t sz;
arm_cmn_init_node_info(cmn, rgn_offset, &cfg);
if (cfg.type != CMN_TYPE_CFG)
return -ENODEV;
cfg_region = cmn->base + rgn_offset;
reg = readl_relaxed(cfg_region + CMN_CFGM_PERIPH_ID_2);
cmn->rev = FIELD_GET(CMN_CFGM_PID2_REVISION, reg);
reg = readq_relaxed(cfg_region + CMN_CFGM_INFO_GLOBAL);
cmn->multi_dtm = reg & CMN_INFO_MULTIPLE_DTM_EN;
cmn->rsp_vc_num = FIELD_GET(CMN_INFO_RSP_VC_NUM, reg);
cmn->dat_vc_num = FIELD_GET(CMN_INFO_DAT_VC_NUM, reg);
reg = readq_relaxed(cfg_region + CMN_CHILD_INFO);
child_count = FIELD_GET(CMN_CI_CHILD_COUNT, reg);
child_poff = FIELD_GET(CMN_CI_CHILD_PTR_OFFSET, reg);
cmn->num_xps = child_count;
cmn->num_dns = cmn->num_xps;
/* Pass 1: visit the XPs, enumerate their children */
for (i = 0; i < cmn->num_xps; i++) {
reg = readq_relaxed(cfg_region + child_poff + i * 8);
xp_offset[i] = reg & CMN_CHILD_NODE_ADDR;
reg = readq_relaxed(cmn->base + xp_offset[i] + CMN_CHILD_INFO);
cmn->num_dns += FIELD_GET(CMN_CI_CHILD_COUNT, reg);
}
/* Cheeky +1 to help terminate pointer-based iteration later */
dn = devm_kcalloc(cmn->dev, cmn->num_dns + 1, sizeof(*dn), GFP_KERNEL);
if (!dn)
return -ENOMEM;
/* Initial safe upper bound on DTMs for any possible mesh layout */
i = cmn->num_xps;
if (cmn->multi_dtm)
i += cmn->num_xps + 1;
dtm = devm_kcalloc(cmn->dev, i, sizeof(*dtm), GFP_KERNEL);
if (!dtm)
return -ENOMEM;
/* Pass 2: now we can actually populate the nodes */
cmn->dns = dn;
cmn->dtms = dtm;
for (i = 0; i < cmn->num_xps; i++) {
void __iomem *xp_region = cmn->base + xp_offset[i];
struct arm_cmn_node *xp = dn++;
unsigned int xp_ports = 0;
arm_cmn_init_node_info(cmn, xp_offset[i], xp);
/*
* Thanks to the order in which XP logical IDs seem to be
* assigned, we can handily infer the mesh X dimension by
* looking out for the XP at (0,1) without needing to know
* the exact node ID format, which we can later derive.
*/
if (xp->id == (1 << 3))
cmn->mesh_x = xp->logid;
if (cmn->model == CMN600)
xp->dtc = 0xf;
else
xp->dtc = 1 << readl_relaxed(xp_region + CMN_DTM_UNIT_INFO);
xp->dtm = dtm - cmn->dtms;
arm_cmn_init_dtm(dtm++, xp, 0);
/*
* Keeping track of connected ports will let us filter out
* unnecessary XP events easily. We can also reliably infer the
* "extra device ports" configuration for the node ID format
* from this, since in that case we will see at least one XP
* with port 2 connected, for the HN-D.
*/
if (readq_relaxed(xp_region + CMN_MXP__CONNECT_INFO_P0))
xp_ports |= BIT(0);
if (readq_relaxed(xp_region + CMN_MXP__CONNECT_INFO_P1))
xp_ports |= BIT(1);
if (readq_relaxed(xp_region + CMN_MXP__CONNECT_INFO_P2))
xp_ports |= BIT(2);
if (readq_relaxed(xp_region + CMN_MXP__CONNECT_INFO_P3))
xp_ports |= BIT(3);
if (readq_relaxed(xp_region + CMN_MXP__CONNECT_INFO_P4))
xp_ports |= BIT(4);
if (readq_relaxed(xp_region + CMN_MXP__CONNECT_INFO_P5))
xp_ports |= BIT(5);
if (cmn->multi_dtm && (xp_ports & 0xc))
arm_cmn_init_dtm(dtm++, xp, 1);
if (cmn->multi_dtm && (xp_ports & 0x30))
arm_cmn_init_dtm(dtm++, xp, 2);
cmn->ports_used |= xp_ports;
reg = readq_relaxed(xp_region + CMN_CHILD_INFO);
child_count = FIELD_GET(CMN_CI_CHILD_COUNT, reg);
child_poff = FIELD_GET(CMN_CI_CHILD_PTR_OFFSET, reg);
for (j = 0; j < child_count; j++) {
reg = readq_relaxed(xp_region + child_poff + j * 8);
/*
* Don't even try to touch anything external, since in general
* we haven't a clue how to power up arbitrary CHI requesters.
* As of CMN-600r1 these could only be RN-SAMs or CXLAs,
* neither of which have any PMU events anyway.
* (Actually, CXLAs do seem to have grown some events in r1p2,
* but they don't go to regular XP DTMs, and they depend on
* secure configuration which we can't easily deal with)
*/
if (reg & CMN_CHILD_NODE_EXTERNAL) {
dev_dbg(cmn->dev, "ignoring external node %llx\n", reg);
continue;
}
arm_cmn_init_node_info(cmn, reg & CMN_CHILD_NODE_ADDR, dn);
switch (dn->type) {
case CMN_TYPE_DTC:
cmn->num_dtcs++;
dn++;
break;
/* These guys have PMU events */
case CMN_TYPE_DVM:
case CMN_TYPE_HNI:
case CMN_TYPE_HNF:
case CMN_TYPE_SBSX:
case CMN_TYPE_RNI:
case CMN_TYPE_RND:
case CMN_TYPE_MTSX:
case CMN_TYPE_CXRA:
case CMN_TYPE_CXHA:
dn++;
break;
/* Nothing to see here */
case CMN_TYPE_MPAM_S:
case CMN_TYPE_MPAM_NS:
case CMN_TYPE_RNSAM:
case CMN_TYPE_CXLA:
break;
/* Something has gone horribly wrong */
default:
dev_err(cmn->dev, "invalid device node type: 0x%x\n", dn->type);
return -ENODEV;
}
}
}
/* Correct for any nodes we skipped */
cmn->num_dns = dn - cmn->dns;
sz = (void *)(dn + 1) - (void *)cmn->dns;
dn = devm_krealloc(cmn->dev, cmn->dns, sz, GFP_KERNEL);
if (dn)
cmn->dns = dn;
sz = (void *)dtm - (void *)cmn->dtms;
dtm = devm_krealloc(cmn->dev, cmn->dtms, sz, GFP_KERNEL);
if (dtm)
cmn->dtms = dtm;
/*
* If mesh_x wasn't set during discovery then we never saw
* an XP at (0,1), thus we must have an Nx1 configuration.
*/
if (!cmn->mesh_x)
cmn->mesh_x = cmn->num_xps;
cmn->mesh_y = cmn->num_xps / cmn->mesh_x;
/* 1x1 config plays havoc with XP event encodings */
if (cmn->num_xps == 1)
dev_warn(cmn->dev, "1x1 config not fully supported, translate XP events manually\n");
dev_dbg(cmn->dev, "model %d, periph_id_2 revision %d\n", cmn->model, cmn->rev);
reg = cmn->ports_used;
dev_dbg(cmn->dev, "mesh %dx%d, ID width %d, ports %6pbl%s\n",
cmn->mesh_x, cmn->mesh_y, arm_cmn_xyidbits(cmn), ®,
cmn->multi_dtm ? ", multi-DTM" : "");
return 0;
}