Commit ea9aadc0 authored by Dave Jiang's avatar Dave Jiang Committed by Vinod Koul
Browse files

dmaengine: idxd: fix wq cleanup of WQCFG registers



A pre-release silicon erratum workaround where wq reset does not clear
WQCFG registers was leaked into upstream code. Use wq reset command
instead of blasting the MMIO region. This also address an issue where
we clobber registers in future devices.

Fixes: da32b28c ("dmaengine: idxd: cleanup workqueue config after disabling")
Reported-by: default avatarShreenivaas Devarajan <shreenivaas.devarajan@intel.com>
Signed-off-by: default avatarDave Jiang <dave.jiang@intel.com>
Link: https://lore.kernel.org/r/161824330020.881560.16375921906426627033.stgit@djiang5-desk3.ch.intel.com


Signed-off-by: default avatarVinod Koul <vkoul@kernel.org>
parent 6df0e6c5
Loading
Loading
Loading
Loading
+24 −11
Original line number Diff line number Diff line
@@ -282,6 +282,22 @@ void idxd_wq_drain(struct idxd_wq *wq)
	idxd_cmd_exec(idxd, IDXD_CMD_DRAIN_WQ, operand, NULL);
}

void idxd_wq_reset(struct idxd_wq *wq)
{
	struct idxd_device *idxd = wq->idxd;
	struct device *dev = &idxd->pdev->dev;
	u32 operand;

	if (wq->state != IDXD_WQ_ENABLED) {
		dev_dbg(dev, "WQ %d in wrong state: %d\n", wq->id, wq->state);
		return;
	}

	operand = BIT(wq->id % 16) | ((wq->id / 16) << 16);
	idxd_cmd_exec(idxd, IDXD_CMD_RESET_WQ, operand, NULL);
	wq->state = IDXD_WQ_DISABLED;
}

int idxd_wq_map_portal(struct idxd_wq *wq)
{
	struct idxd_device *idxd = wq->idxd;
@@ -363,8 +379,6 @@ int idxd_wq_disable_pasid(struct idxd_wq *wq)
void idxd_wq_disable_cleanup(struct idxd_wq *wq)
{
	struct idxd_device *idxd = wq->idxd;
	struct device *dev = &idxd->pdev->dev;
	int i, wq_offset;

	lockdep_assert_held(&idxd->dev_lock);
	memset(wq->wqcfg, 0, idxd->wqcfg_size);
@@ -376,14 +390,6 @@ void idxd_wq_disable_cleanup(struct idxd_wq *wq)
	wq->ats_dis = 0;
	clear_bit(WQ_FLAG_DEDICATED, &wq->flags);
	memset(wq->name, 0, WQ_NAME_SIZE);

	for (i = 0; i < WQCFG_STRIDES(idxd); i++) {
		wq_offset = WQCFG_OFFSET(idxd, wq->id, i);
		iowrite32(0, idxd->reg_base + wq_offset);
		dev_dbg(dev, "WQ[%d][%d][%#x]: %#x\n",
			wq->id, i, wq_offset,
			ioread32(idxd->reg_base + wq_offset));
	}
}

/* Device control bits */
@@ -672,7 +678,14 @@ static int idxd_wq_config_write(struct idxd_wq *wq)
	if (!wq->group)
		return 0;

	memset(wq->wqcfg, 0, idxd->wqcfg_size);
	/*
	 * Instead of memset the entire shadow copy of WQCFG, copy from the hardware after
	 * wq reset. This will copy back the sticky values that are present on some devices.
	 */
	for (i = 0; i < WQCFG_STRIDES(idxd); i++) {
		wq_offset = WQCFG_OFFSET(idxd, wq->id, i);
		wq->wqcfg->bits[i] = ioread32(idxd->reg_base + wq_offset);
	}

	/* byte 0-3 */
	wq->wqcfg->wq_size = wq->size;
+1 −0
Original line number Diff line number Diff line
@@ -343,6 +343,7 @@ void idxd_wq_free_resources(struct idxd_wq *wq);
int idxd_wq_enable(struct idxd_wq *wq);
int idxd_wq_disable(struct idxd_wq *wq);
void idxd_wq_drain(struct idxd_wq *wq);
void idxd_wq_reset(struct idxd_wq *wq);
int idxd_wq_map_portal(struct idxd_wq *wq);
void idxd_wq_unmap_portal(struct idxd_wq *wq);
void idxd_wq_disable_cleanup(struct idxd_wq *wq);
+2 −7
Original line number Diff line number Diff line
@@ -275,7 +275,6 @@ static void disable_wq(struct idxd_wq *wq)
{
	struct idxd_device *idxd = wq->idxd;
	struct device *dev = &idxd->pdev->dev;
	int rc;

	mutex_lock(&wq->wq_lock);
	dev_dbg(dev, "%s removing WQ %s\n", __func__, dev_name(&wq->conf_dev));
@@ -296,16 +295,12 @@ static void disable_wq(struct idxd_wq *wq)
	idxd_wq_unmap_portal(wq);

	idxd_wq_drain(wq);
	rc = idxd_wq_disable(wq);
	idxd_wq_reset(wq);

	idxd_wq_free_resources(wq);
	wq->client_count = 0;
	mutex_unlock(&wq->wq_lock);

	if (rc < 0)
		dev_warn(dev, "Failed to disable %s: %d\n",
			 dev_name(&wq->conf_dev), rc);
	else
	dev_info(dev, "wq %s disabled\n", dev_name(&wq->conf_dev));
}