162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <cxl.h>
562306a36Sopenharmony_ci#include "core.h"
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define CREATE_TRACE_POINTS
862306a36Sopenharmony_ci#include "trace.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistatic bool cxl_is_hpa_in_range(u64 hpa, struct cxl_region *cxlr, int pos)
1162306a36Sopenharmony_ci{
1262306a36Sopenharmony_ci	struct cxl_region_params *p = &cxlr->params;
1362306a36Sopenharmony_ci	int gran = p->interleave_granularity;
1462306a36Sopenharmony_ci	int ways = p->interleave_ways;
1562306a36Sopenharmony_ci	u64 offset;
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci	/* Is the hpa within this region at all */
1862306a36Sopenharmony_ci	if (hpa < p->res->start || hpa > p->res->end) {
1962306a36Sopenharmony_ci		dev_dbg(&cxlr->dev,
2062306a36Sopenharmony_ci			"Addr trans fail: hpa 0x%llx not in region\n", hpa);
2162306a36Sopenharmony_ci		return false;
2262306a36Sopenharmony_ci	}
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	/* Is the hpa in an expected chunk for its pos(-ition) */
2562306a36Sopenharmony_ci	offset = hpa - p->res->start;
2662306a36Sopenharmony_ci	offset = do_div(offset, gran * ways);
2762306a36Sopenharmony_ci	if ((offset >= pos * gran) && (offset < (pos + 1) * gran))
2862306a36Sopenharmony_ci		return true;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	dev_dbg(&cxlr->dev,
3162306a36Sopenharmony_ci		"Addr trans fail: hpa 0x%llx not in expected chunk\n", hpa);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	return false;
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic u64 cxl_dpa_to_hpa(u64 dpa,  struct cxl_region *cxlr,
3762306a36Sopenharmony_ci			  struct cxl_endpoint_decoder *cxled)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	u64 dpa_offset, hpa_offset, bits_upper, mask_upper, hpa;
4062306a36Sopenharmony_ci	struct cxl_region_params *p = &cxlr->params;
4162306a36Sopenharmony_ci	int pos = cxled->pos;
4262306a36Sopenharmony_ci	u16 eig = 0;
4362306a36Sopenharmony_ci	u8 eiw = 0;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	ways_to_eiw(p->interleave_ways, &eiw);
4662306a36Sopenharmony_ci	granularity_to_eig(p->interleave_granularity, &eig);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	/*
4962306a36Sopenharmony_ci	 * The device position in the region interleave set was removed
5062306a36Sopenharmony_ci	 * from the offset at HPA->DPA translation. To reconstruct the
5162306a36Sopenharmony_ci	 * HPA, place the 'pos' in the offset.
5262306a36Sopenharmony_ci	 *
5362306a36Sopenharmony_ci	 * The placement of 'pos' in the HPA is determined by interleave
5462306a36Sopenharmony_ci	 * ways and granularity and is defined in the CXL Spec 3.0 Section
5562306a36Sopenharmony_ci	 * 8.2.4.19.13 Implementation Note: Device Decode Logic
5662306a36Sopenharmony_ci	 */
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	/* Remove the dpa base */
5962306a36Sopenharmony_ci	dpa_offset = dpa - cxl_dpa_resource_start(cxled);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	mask_upper = GENMASK_ULL(51, eig + 8);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (eiw < 8) {
6462306a36Sopenharmony_ci		hpa_offset = (dpa_offset & mask_upper) << eiw;
6562306a36Sopenharmony_ci		hpa_offset |= pos << (eig + 8);
6662306a36Sopenharmony_ci	} else {
6762306a36Sopenharmony_ci		bits_upper = (dpa_offset & mask_upper) >> (eig + 8);
6862306a36Sopenharmony_ci		bits_upper = bits_upper * 3;
6962306a36Sopenharmony_ci		hpa_offset = ((bits_upper << (eiw - 8)) + pos) << (eig + 8);
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* The lower bits remain unchanged */
7362306a36Sopenharmony_ci	hpa_offset |= dpa_offset & GENMASK_ULL(eig + 7, 0);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	/* Apply the hpa_offset to the region base address */
7662306a36Sopenharmony_ci	hpa = hpa_offset + p->res->start;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (!cxl_is_hpa_in_range(hpa, cxlr, cxled->pos))
7962306a36Sopenharmony_ci		return ULLONG_MAX;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return hpa;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ciu64 cxl_trace_hpa(struct cxl_region *cxlr, struct cxl_memdev *cxlmd,
8562306a36Sopenharmony_ci		  u64 dpa)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct cxl_region_params *p = &cxlr->params;
8862306a36Sopenharmony_ci	struct cxl_endpoint_decoder *cxled = NULL;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	for (int i = 0; i <  p->nr_targets; i++) {
9162306a36Sopenharmony_ci		cxled = p->targets[i];
9262306a36Sopenharmony_ci		if (cxlmd == cxled_to_memdev(cxled))
9362306a36Sopenharmony_ci			break;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci	if (!cxled || cxlmd != cxled_to_memdev(cxled))
9662306a36Sopenharmony_ci		return ULLONG_MAX;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	return cxl_dpa_to_hpa(dpa, cxlr, cxled);
9962306a36Sopenharmony_ci}
100