Commit dd141b16 authored by Dillon Varone's avatar Dillon Varone Committed by Alex Deucher
Browse files

drm/amd/display: Fix race in dmub_srv_wait_for_pending



[WHY]
If commands are being submitted to DMCUB while concurrently waiting for
pending commands to complete, rptr and wptr may never match again, and
reported command count will not update.

[HOW]
Modify dmub_srv_wait_for_pending to constantly check wptr and rptr
match, and update inbox status whenever a message is sent to avoid the
race and determine message completion or idle as quickly as possible.

Reviewed-by: default avatarNicholas Kazlauskas <nicholas.kazlauskas@amd.com>
Signed-off-by: default avatarDillon Varone <dillon.varone@amd.com>
Signed-off-by: default avatarRay Wu <ray.wu@amd.com>
Tested-by: default avatarDaniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: default avatarAlex Deucher <alexander.deucher@amd.com>
parent 7ac37f0d
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -247,6 +247,9 @@ bool dc_dmub_srv_cmd_list_queue_execute(struct dc_dmub_srv *dc_dmub_srv,
		} else {
			res = dc_dmub_srv_fb_cmd_list_queue_execute(dc_dmub_srv, count, cmd_list);
		}

		if (res)
			res = dmub_srv_update_inbox_status(dc_dmub_srv->dmub) == DMUB_STATUS_OK;
	}

	return res;
+14 −0
Original line number Diff line number Diff line
@@ -445,6 +445,8 @@ struct dmub_srv_hw_funcs {

	uint32_t (*emul_get_inbox1_rptr)(struct dmub_srv *dmub);

	uint32_t (*emul_get_inbox1_wptr)(struct dmub_srv *dmub);

	void (*emul_set_inbox1_wptr)(struct dmub_srv *dmub, uint32_t wptr_offset);

	bool (*is_supported)(struct dmub_srv *dmub);
@@ -1053,4 +1055,16 @@ enum dmub_status dmub_srv_wait_for_inbox_free(struct dmub_srv *dmub,
		uint32_t timeout_us,
		uint32_t num_free_required);

/**
 * dmub_srv_update_inbox_status() - Updates pending status for inbox & reg inbox0
 * @dmub: the dmub service
 *
 * Return:
 *   DMUB_STATUS_OK - success
 *   DMUB_STATUS_TIMEOUT - wait for buffer to flush timed out
 *   DMUB_STATUS_HW_FAILURE - issue with HW programming
 *   DMUB_STATUS_INVALID - unspecified error
 */
enum dmub_status dmub_srv_update_inbox_status(struct dmub_srv *dmub);

#endif /* _DMUB_SRV_H_ */
+31 −27
Original line number Diff line number Diff line
@@ -952,10 +952,8 @@ enum dmub_status dmub_srv_wait_for_pending(struct dmub_srv *dmub,
			!dmub->hw_funcs.get_inbox1_wptr)
		return DMUB_STATUS_INVALID;

	/* take a snapshot of the required mailbox state */
	scratch_inbox1.rb.wrpt = dmub->hw_funcs.get_inbox1_wptr(dmub);

	for (i = 0; i <= timeout_us; i += polling_interval_us) {
			scratch_inbox1.rb.wrpt = dmub->hw_funcs.get_inbox1_wptr(dmub);
			scratch_inbox1.rb.rptr = dmub->hw_funcs.get_inbox1_rptr(dmub);

		scratch_reg_inbox0.is_pending = scratch_reg_inbox0.is_pending &&
@@ -978,30 +976,6 @@ enum dmub_status dmub_srv_wait_for_pending(struct dmub_srv *dmub,
	return DMUB_STATUS_TIMEOUT;
}

static enum dmub_status dmub_srv_update_inbox_status(struct dmub_srv *dmub)
{
	uint32_t rptr;

	/* update inbox1 state */
		rptr = dmub->hw_funcs.get_inbox1_rptr(dmub);

	if (rptr > dmub->inbox1.rb.capacity)
		return DMUB_STATUS_HW_FAILURE;

	if (dmub->inbox1.rb.rptr > rptr) {
		/* rb wrapped */
		dmub->inbox1.num_reported += (rptr + dmub->inbox1.rb.capacity - dmub->inbox1.rb.rptr) / DMUB_RB_CMD_SIZE;
	} else {
		dmub->inbox1.num_reported += (rptr - dmub->inbox1.rb.rptr) / DMUB_RB_CMD_SIZE;
	}
	dmub->inbox1.rb.rptr = rptr;

	/* update reg_inbox0 */
	dmub_srv_update_reg_inbox0_status(dmub);

	return DMUB_STATUS_OK;
}

enum dmub_status dmub_srv_wait_for_idle(struct dmub_srv *dmub,
					uint32_t timeout_us)
{
@@ -1353,3 +1327,33 @@ enum dmub_status dmub_srv_wait_for_inbox_free(struct dmub_srv *dmub,

	return DMUB_STATUS_TIMEOUT;
}

enum dmub_status dmub_srv_update_inbox_status(struct dmub_srv *dmub)
{
	uint32_t rptr;

	if (!dmub->hw_init)
		return DMUB_STATUS_INVALID;

	if (dmub->power_state != DMUB_POWER_STATE_D0)
		return DMUB_STATUS_POWER_STATE_D3;

	/* update inbox1 state */
	rptr = dmub->hw_funcs.get_inbox1_rptr(dmub);

	if (rptr > dmub->inbox1.rb.capacity)
		return DMUB_STATUS_HW_FAILURE;

	if (dmub->inbox1.rb.rptr > rptr) {
		/* rb wrapped */
		dmub->inbox1.num_reported += (rptr + dmub->inbox1.rb.capacity - dmub->inbox1.rb.rptr) / DMUB_RB_CMD_SIZE;
	} else {
		dmub->inbox1.num_reported += (rptr - dmub->inbox1.rb.rptr) / DMUB_RB_CMD_SIZE;
	}
	dmub->inbox1.rb.rptr = rptr;

	/* update reg_inbox0 */
	dmub_srv_update_reg_inbox0_status(dmub);

	return DMUB_STATUS_OK;
}