static int arm_cmn_discover()

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), &reg,
		cmn->multi_dtm ? ", multi-DTM" : "");

	return 0;
}