Contributors: 12
Author Tokens Token Proportion Commits Commit Proportion
Hung, Cruise 618 36.29% 6 18.18%
Mustapha Ghaddar 577 33.88% 7 21.21%
Meenakshikumar Somasundaram 249 14.62% 3 9.09%
Peichen Huang 138 8.10% 2 6.06%
Leo (Hanghong) Ma 30 1.76% 1 3.03%
Harry Wentland 29 1.70% 2 6.06%
Jimmy Kizito 28 1.64% 2 6.06%
Wenjing Liu 22 1.29% 6 18.18%
Nathan Chancellor 6 0.35% 1 3.03%
George Shen 3 0.18% 1 3.03%
bobzhou 2 0.12% 1 3.03%
Jerry (Fangzhi) Zuo 1 0.06% 1 3.03%
Total 1703 33


/*
 * Copyright 2022 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: AMD
 *
 */
/*********************************************************************/
// USB4 DPIA BANDWIDTH ALLOCATION LOGIC
/*********************************************************************/
#include "link_dp_dpia_bw.h"
#include "link_dpcd.h"
#include "dc_dmub_srv.h"

#define DC_LOGGER \
	link->ctx->logger

#define Kbps_TO_Gbps (1000 * 1000)

#define MST_TIME_SLOT_COUNT 64

// ------------------------------------------------------------------
// PRIVATE FUNCTIONS
// ------------------------------------------------------------------
/*
 * Always Check the following:
 *  - Is it USB4 link?
 *  - Is HPD HIGH?
 *  - Is BW Allocation Support Mode enabled on DP-Tx?
 */
static bool link_dp_is_bw_alloc_available(struct dc_link *link)
{
	return (link && link->hpd_status
		&& link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling
		&& link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dpia_bw_alloc
		&& link->dpcd_caps.usb4_dp_tun_info.driver_bw_cap.bits.driver_bw_alloc_support);
}

static void reset_bw_alloc_struct(struct dc_link *link)
{
	link->dpia_bw_alloc_config.bw_alloc_enabled = false;
	link->dpia_bw_alloc_config.link_verified_bw = 0;
	link->dpia_bw_alloc_config.link_max_bw = 0;
	link->dpia_bw_alloc_config.allocated_bw = 0;
	link->dpia_bw_alloc_config.estimated_bw = 0;
	link->dpia_bw_alloc_config.bw_granularity = 0;
	link->dpia_bw_alloc_config.dp_overhead = 0;
	link->dpia_bw_alloc_config.nrd_max_lane_count = 0;
	link->dpia_bw_alloc_config.nrd_max_link_rate = 0;
	for (int i = 0; i < MAX_SINKS_PER_LINK; i++)
		link->dpia_bw_alloc_config.remote_sink_req_bw[i] = 0;
	DC_LOG_DEBUG("reset usb4 bw alloc of link(%d)\n", link->link_index);
}

#define BW_GRANULARITY_0 4 // 0.25 Gbps
#define BW_GRANULARITY_1 2 // 0.5 Gbps
#define BW_GRANULARITY_2 1 // 1 Gbps

static uint8_t get_bw_granularity(struct dc_link *link)
{
	uint8_t bw_granularity = 0;

	core_link_read_dpcd(
			link,
			DP_BW_GRANULALITY,
			&bw_granularity,
			sizeof(uint8_t));

	switch (bw_granularity & 0x3) {
	case 0:
		bw_granularity = BW_GRANULARITY_0;
		break;
	case 1:
		bw_granularity = BW_GRANULARITY_1;
		break;
	case 2:
	default:
		bw_granularity = BW_GRANULARITY_2;
		break;
	}

	return bw_granularity;
}

static int get_estimated_bw(struct dc_link *link)
{
	uint8_t bw_estimated_bw = 0;

	core_link_read_dpcd(
			link,
			ESTIMATED_BW,
			&bw_estimated_bw,
			sizeof(uint8_t));

	return bw_estimated_bw * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
}

static int get_non_reduced_max_link_rate(struct dc_link *link)
{
	uint8_t nrd_max_link_rate = 0;

	core_link_read_dpcd(
			link,
			DP_TUNNELING_MAX_LINK_RATE,
			&nrd_max_link_rate,
			sizeof(uint8_t));

	return nrd_max_link_rate;
}

static int get_non_reduced_max_lane_count(struct dc_link *link)
{
	uint8_t nrd_max_lane_count = 0;

	core_link_read_dpcd(
			link,
			DP_TUNNELING_MAX_LANE_COUNT,
			&nrd_max_lane_count,
			sizeof(uint8_t));

	return nrd_max_lane_count;
}

/*
 * Read all New BW alloc configuration ex: estimated_bw, allocated_bw,
 * granuality, Driver_ID, CM_Group, & populate the BW allocation structs
 * for host router and dpia
 */
static void retrieve_usb4_dp_bw_allocation_info(struct dc_link *link)
{
	reset_bw_alloc_struct(link);

	/* init the known values */
	link->dpia_bw_alloc_config.bw_granularity = get_bw_granularity(link);
	link->dpia_bw_alloc_config.estimated_bw = get_estimated_bw(link);
	link->dpia_bw_alloc_config.nrd_max_link_rate = get_non_reduced_max_link_rate(link);
	link->dpia_bw_alloc_config.nrd_max_lane_count = get_non_reduced_max_lane_count(link);

	DC_LOG_DEBUG("%s: bw_granularity(%d), estimated_bw(%d)\n",
		__func__, link->dpia_bw_alloc_config.bw_granularity,
		link->dpia_bw_alloc_config.estimated_bw);
	DC_LOG_DEBUG("%s: nrd_max_link_rate(%d), nrd_max_lane_count(%d)\n",
		__func__, link->dpia_bw_alloc_config.nrd_max_link_rate,
		link->dpia_bw_alloc_config.nrd_max_lane_count);
}

/*
 * Cleanup function for when the dpia is unplugged to reset struct
 * and perform any required clean up
 *
 * @link: pointer to the dc_link struct instance
 *
 * return: none
 */
static void dpia_bw_alloc_unplug(struct dc_link *link)
{
	if (link) {
		DC_LOG_DEBUG("%s: resetting BW alloc config for link(%d)\n",
			__func__, link->link_index);
		reset_bw_alloc_struct(link);
	}
}

static void link_dpia_send_bw_alloc_request(struct dc_link *link, int req_bw)
{
	uint8_t request_reg_val;
	uint32_t temp, request_bw;

	if (link->dpia_bw_alloc_config.bw_granularity == 0) {
		DC_LOG_ERROR("%s:  Link[%d]:  bw_granularity is zero!", __func__, link->link_index);
		return;
	}

	temp = req_bw * link->dpia_bw_alloc_config.bw_granularity;
	request_reg_val = temp / Kbps_TO_Gbps;

	/* Always make sure to add more to account for floating points */
	if (temp % Kbps_TO_Gbps)
		++request_reg_val;

	request_bw = request_reg_val * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);

	if (request_bw > link->dpia_bw_alloc_config.estimated_bw) {
		DC_LOG_ERROR("%s:  Link[%d]:  Request BW (%d --> %d) > Estimated BW (%d)... Set to Estimated BW!",
				__func__, link->link_index,
				req_bw, request_bw, link->dpia_bw_alloc_config.estimated_bw);
		req_bw = link->dpia_bw_alloc_config.estimated_bw;

		temp = req_bw * link->dpia_bw_alloc_config.bw_granularity;
		request_reg_val = temp / Kbps_TO_Gbps;
		if (temp % Kbps_TO_Gbps)
			++request_reg_val;
	}

	link->dpia_bw_alloc_config.allocated_bw = request_bw;
	DC_LOG_DC("%s:  Link[%d]:  Request BW:  %d", __func__, link->link_index, request_bw);

	core_link_write_dpcd(link, REQUESTED_BW,
		&request_reg_val,
		sizeof(uint8_t));
}

// ------------------------------------------------------------------
// PUBLIC FUNCTIONS
// ------------------------------------------------------------------
bool link_dpia_enable_usb4_dp_bw_alloc_mode(struct dc_link *link)
{
	bool ret = false;
	uint8_t val;

	if (link->hpd_status) {
		val = DPTX_BW_ALLOC_MODE_ENABLE | DPTX_BW_ALLOC_UNMASK_IRQ;

		if (core_link_write_dpcd(link, DPTX_BW_ALLOCATION_MODE_CONTROL, &val, sizeof(uint8_t)) == DC_OK) {
			DC_LOG_DEBUG("%s:  link[%d] DPTX BW allocation mode enabled", __func__, link->link_index);

			retrieve_usb4_dp_bw_allocation_info(link);

			if (link->dpia_bw_alloc_config.nrd_max_link_rate && link->dpia_bw_alloc_config.nrd_max_lane_count) {
				link->reported_link_cap.link_rate = link->dpia_bw_alloc_config.nrd_max_link_rate;
				link->reported_link_cap.lane_count = link->dpia_bw_alloc_config.nrd_max_lane_count;
			}

			link->dpia_bw_alloc_config.bw_alloc_enabled = true;
			ret = true;

			if (link->dc->debug.dpia_debug.bits.enable_usb4_bw_zero_alloc_patch) {
				/*
				 * During DP tunnel creation, the CM preallocates BW
				 * and reduces the estimated BW of other DPIAs.
				 * The CM releases the preallocation only when the allocation is complete.
				 * Perform a zero allocation to make the CM release the preallocation
				 * and correctly update the estimated BW for all DPIAs per host router.
				 */
				link_dp_dpia_allocate_usb4_bandwidth_for_stream(link, 0);
			}
		} else
			DC_LOG_DEBUG("%s:  link[%d] failed to enable DPTX BW allocation mode", __func__, link->link_index);
	}

	return ret;
}

/*
 * Handle DP BW allocation status register
 *
 * @link: pointer to the dc_link struct instance
 * @status: content of DP tunneling status DPCD register
 *
 * return: none
 */
void link_dp_dpia_handle_bw_alloc_status(struct dc_link *link, uint8_t status)
{
	link->dpia_bw_alloc_config.estimated_bw = get_estimated_bw(link);

	if (status & DP_TUNNELING_BW_REQUEST_SUCCEEDED) {
		DC_LOG_DEBUG("%s: BW Allocation request succeeded on link(%d)",
				__func__, link->link_index);
	} else if (status & DP_TUNNELING_BW_REQUEST_FAILED) {
		DC_LOG_DEBUG("%s: BW Allocation request failed on link(%d)  allocated/estimated BW=%d",
				__func__, link->link_index, link->dpia_bw_alloc_config.estimated_bw);

		link_dpia_send_bw_alloc_request(link, link->dpia_bw_alloc_config.estimated_bw);
	} else if (status & DP_TUNNELING_ESTIMATED_BW_CHANGED) {
		DC_LOG_DEBUG("%s: Estimated BW changed on link(%d)  new estimated BW=%d",
				__func__, link->link_index, link->dpia_bw_alloc_config.estimated_bw);
	}

	core_link_write_dpcd(
		link, DP_TUNNELING_STATUS,
		&status, sizeof(status));
}

/*
 * Handle the DP Bandwidth allocation for DPIA
 *
 */
void dpia_handle_usb4_bandwidth_allocation_for_link(struct dc_link *link, int peak_bw)
{
	if (link && link->dpcd_caps.usb4_dp_tun_info.dp_tun_cap.bits.dp_tunneling
			&& link->dpia_bw_alloc_config.bw_alloc_enabled) {
		//1. Hot Plug
		if (link->hpd_status && peak_bw > 0) {
			// If DP over USB4 then we need to check BW allocation
			link->dpia_bw_alloc_config.link_max_bw = peak_bw;

			link_dpia_send_bw_alloc_request(link, peak_bw);
		}
		//2. Cold Unplug
		else if (!link->hpd_status)
			dpia_bw_alloc_unplug(link);
	}
}

void link_dp_dpia_allocate_usb4_bandwidth_for_stream(struct dc_link *link, int req_bw)
{
	link->dpia_bw_alloc_config.estimated_bw = get_estimated_bw(link);

	DC_LOG_DEBUG("%s: ENTER: link[%d] hpd(%d)  Allocated_BW: %d  Estimated_BW: %d  Req_BW: %d",
		__func__, link->link_index, link->hpd_status,
		link->dpia_bw_alloc_config.allocated_bw,
		link->dpia_bw_alloc_config.estimated_bw,
		req_bw);

	if (link_dp_is_bw_alloc_available(link))
		link_dpia_send_bw_alloc_request(link, req_bw);
	else
		DC_LOG_DEBUG("%s:  BW Allocation mode not available", __func__);
}

uint32_t link_dpia_get_dp_overhead(const struct dc_link *link)
{
	uint32_t link_dp_overhead = 0;

	if ((link->type == dc_connection_mst_branch) &&
				!link->dpcd_caps.channel_coding_cap.bits.DP_128b_132b_SUPPORTED) {
		/* For 8b/10b encoding: MTP is 64 time slots long, slot 0 is used for MTPH
		 * MST overhead is 1/64 of link bandwidth (excluding any overhead)
		 */
		const struct dc_link_settings *link_cap = dc_link_get_link_cap(link);

		if (link_cap) {
			uint32_t link_bw_in_kbps = (uint32_t)link_cap->link_rate *
					   (uint32_t)link_cap->lane_count *
					   LINK_RATE_REF_FREQ_IN_KHZ * 8;
			link_dp_overhead = (link_bw_in_kbps / MST_TIME_SLOT_COUNT)
						+ ((link_bw_in_kbps % MST_TIME_SLOT_COUNT) ? 1 : 0);
		}
	}

	return link_dp_overhead;
}

/*
 * Aggregates the DPIA bandwidth usage for the respective USB4 Router.
 * And then validate if the required bandwidth is within the router's capacity.
 *
 * @dc_validation_dpia_set: pointer to the dc_validation_dpia_set
 * @count: number of DPIA validation sets
 *
 * return: true if validation is succeeded
 */
bool link_dpia_validate_dp_tunnel_bandwidth(const struct dc_validation_dpia_set *dpia_link_sets, uint8_t count)
{
	uint32_t granularity_Gbps;
	const struct dc_link *link;
	uint32_t link_bw_granularity;
	uint32_t link_required_bw;
	struct usb4_router_validation_set router_sets[MAX_HOST_ROUTERS_NUM] = { 0 };
	uint8_t i;
	bool is_success = true;
	uint8_t router_count = 0;

	if ((dpia_link_sets == NULL) || (count == 0))
		return is_success;

	// Iterate through each DP tunneling link (DPIA).
	// Aggregate its bandwidth requirements onto the respective USB4 router.
	for (i = 0; i < count; i++) {
		link = dpia_link_sets[i].link;
		link_required_bw = dpia_link_sets[i].required_bw;
		const struct dc_tunnel_settings *dp_tunnel_settings = dpia_link_sets[i].tunnel_settings;

		if ((link == NULL) || (dp_tunnel_settings == NULL) || dp_tunnel_settings->bw_granularity == 0)
			break;

		if (link->type == dc_connection_mst_branch)
			link_required_bw += link_dpia_get_dp_overhead(link);

		granularity_Gbps = (Kbps_TO_Gbps / dp_tunnel_settings->bw_granularity);
		link_bw_granularity = (link_required_bw / granularity_Gbps) * granularity_Gbps +
				((link_required_bw % granularity_Gbps) ? granularity_Gbps : 0);

		// Find or add the USB4 router associated with the current DPIA link
		for (uint8_t j = 0; j < MAX_HOST_ROUTERS_NUM; j++) {
			if (router_sets[j].is_valid == false) {
				router_sets[j].is_valid = true;
				router_sets[j].cm_id = dp_tunnel_settings->cm_id;
				router_count++;
			}

			if (router_sets[j].cm_id == dp_tunnel_settings->cm_id) {
				uint32_t remaining_bw =
					dp_tunnel_settings->estimated_bw - dp_tunnel_settings->allocated_bw;

				router_sets[j].allocated_bw += dp_tunnel_settings->allocated_bw;

				if (remaining_bw > router_sets[j].remaining_bw)
					router_sets[j].remaining_bw = remaining_bw;

				// Get the max estimated BW within the same CM_ID
				if (dp_tunnel_settings->estimated_bw > router_sets[j].estimated_bw)
					router_sets[j].estimated_bw = dp_tunnel_settings->estimated_bw;

				router_sets[j].required_bw += link_bw_granularity;
				router_sets[j].dpia_count++;
				break;
			}
		}
	}

	// Validate bandwidth for each unique router found.
	for (i = 0; i < router_count; i++) {
		uint32_t total_bw = 0;

		if (router_sets[i].is_valid == false)
			break;

		// Determine the total available bandwidth for the current router based on aggregated data
		if ((router_sets[i].dpia_count == 1) || (router_sets[i].allocated_bw == 0))
			total_bw = router_sets[i].estimated_bw;
		else
			total_bw = router_sets[i].allocated_bw + router_sets[i].remaining_bw;

		if (router_sets[i].required_bw > total_bw) {
			is_success = false;
			break;
		}
	}

	return is_success;
}