[PATCH 04/15] net: hbl_cn: QP state machine

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add a common QP state machine which handles the moving for a QP from one
state to another including performing necessary checks, draining
in-flight transactions, invalidating caches and error reporting.

Signed-off-by: Omer Shpigelman <oshpigelman@xxxxxxxxx>
Co-developed-by: Abhilash K V <kvabhilash@xxxxxxxxx>
Signed-off-by: Abhilash K V <kvabhilash@xxxxxxxxx>
Co-developed-by: Andrey Agranovich <aagranovich@xxxxxxxxx>
Signed-off-by: Andrey Agranovich <aagranovich@xxxxxxxxx>
Co-developed-by: Bharat Jauhari <bjauhari@xxxxxxxxx>
Signed-off-by: Bharat Jauhari <bjauhari@xxxxxxxxx>
Co-developed-by: David Meriin <dmeriin@xxxxxxxxx>
Signed-off-by: David Meriin <dmeriin@xxxxxxxxx>
Co-developed-by: Sagiv Ozeri <sozeri@xxxxxxxxx>
Signed-off-by: Sagiv Ozeri <sozeri@xxxxxxxxx>
Co-developed-by: Zvika Yehudai <zyehudai@xxxxxxxxx>
Signed-off-by: Zvika Yehudai <zyehudai@xxxxxxxxx>
---
 .../ethernet/intel/hbl_cn/common/hbl_cn_qp.c  | 480 +++++++++++++++++-
 1 file changed, 479 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/intel/hbl_cn/common/hbl_cn_qp.c b/drivers/net/ethernet/intel/hbl_cn/common/hbl_cn_qp.c
index 9ddc23bf8194..26ebdf448193 100644
--- a/drivers/net/ethernet/intel/hbl_cn/common/hbl_cn_qp.c
+++ b/drivers/net/ethernet/intel/hbl_cn/common/hbl_cn_qp.c
@@ -6,8 +6,486 @@
 
 #include "hbl_cn.h"
 
+#define OP_RETRY_COUNT		4
+#define OPC_SETTLE_RETRY_COUNT	20
+
+/* The following table represents the (valid) operations that can be performed on
+ * a QP in order to move it from one state to another
+ * For example: a QP in RTR state can be moved to RTS state using the CN_QP_OP_RTR_2RTS
+ * operation.
+ */
+static const enum hbl_cn_qp_state_op qp_valid_state_op[CN_QP_NUM_STATE][CN_QP_NUM_STATE] = {
+	[CN_QP_STATE_RESET] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_INIT]	= CN_QP_OP_RST_2INIT,
+		[CN_QP_STATE_SQD]	= CN_QP_OP_NOP,
+		[CN_QP_STATE_QPD]	= CN_QP_OP_NOP,
+	},
+	[CN_QP_STATE_INIT] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_ERR]	= CN_QP_OP_2ERR,
+		[CN_QP_STATE_INIT]	= CN_QP_OP_NOP,
+		[CN_QP_STATE_RTR]	= CN_QP_OP_INIT_2RTR,
+		[CN_QP_STATE_SQD]	= CN_QP_OP_NOP,
+		[CN_QP_STATE_QPD]	= CN_QP_OP_NOP,
+	},
+	[CN_QP_STATE_RTR] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_ERR]	= CN_QP_OP_2ERR,
+		[CN_QP_STATE_RTR]	= CN_QP_OP_RTR_2RTR,
+		[CN_QP_STATE_RTS]	= CN_QP_OP_RTR_2RTS,
+		[CN_QP_STATE_SQD]	= CN_QP_OP_NOP,
+		[CN_QP_STATE_QPD]	= CN_QP_OP_RTR_2QPD,
+	},
+	[CN_QP_STATE_RTS] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_ERR]	= CN_QP_OP_2ERR,
+		[CN_QP_STATE_RTS]	= CN_QP_OP_RTS_2RTS,
+		[CN_QP_STATE_SQD]	= CN_QP_OP_RTS_2SQD,
+		[CN_QP_STATE_QPD]	= CN_QP_OP_RTS_2QPD,
+		[CN_QP_STATE_SQERR]	= CN_QP_OP_RTS_2SQERR,
+	},
+	[CN_QP_STATE_SQD] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_ERR]	= CN_QP_OP_2ERR,
+		[CN_QP_STATE_SQD]	= CN_QP_OP_SQD_2SQD,
+		[CN_QP_STATE_RTS]	= CN_QP_OP_SQD_2RTS,
+		[CN_QP_STATE_QPD]	= CN_QP_OP_SQD_2QPD,
+		[CN_QP_STATE_SQERR]	= CN_QP_OP_SQD_2SQ_ERR,
+	},
+	[CN_QP_STATE_QPD] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_ERR]	= CN_QP_OP_2ERR,
+		[CN_QP_STATE_SQD]	= CN_QP_OP_NOP,
+		[CN_QP_STATE_QPD]	= CN_QP_OP_NOP,
+		[CN_QP_STATE_RTR]	= CN_QP_OP_QPD_2RTR,
+	},
+	[CN_QP_STATE_SQERR] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_ERR]	= CN_QP_OP_2ERR,
+		[CN_QP_STATE_SQD]	= CN_QP_OP_SQ_ERR_2SQD,
+		[CN_QP_STATE_SQERR]	= CN_QP_OP_NOP,
+	},
+	[CN_QP_STATE_ERR] = {
+		[CN_QP_STATE_RESET]	= CN_QP_OP_2RESET,
+		[CN_QP_STATE_ERR]	= CN_QP_OP_2ERR,
+	}
+};
+
+static char *cn_qp_state_2name(enum hbl_cn_qp_state state)
+{
+	static char *arr[CN_QP_NUM_STATE] = {
+						"Reset",
+						"Init",
+						"RTR",
+						"RTS",
+						"SQD",
+						"QPD",
+						"SQERR",
+						"ERR",
+	};
+
+	return arr[state];
+}
+
+static inline int wait_for_qpc_idle(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp, bool is_req)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_asic_port_funcs *port_funcs;
+	struct hbl_cn_qpc_attr qpc_attr;
+	int i, rc;
+
+	if (!hdev->qp_wait_for_idle)
+		return 0;
+
+	port_funcs = hdev->asic_funcs->port_funcs;
+
+	for (i = 0; i < OPC_SETTLE_RETRY_COUNT; i++) {
+		rc = port_funcs->qpc_query(cn_port, qp->qp_id, is_req, &qpc_attr);
+
+		if (rc && (rc != -EBUSY && rc != -ETIMEDOUT))
+			return rc;
+
+		if (!(rc || qpc_attr.in_work))
+			return 0;
+
+		/* Release lock while we wait before retry.
+		 * Note, we can assert that we are already locked.
+		 */
+		port_funcs->cfg_unlock(cn_port);
+
+		msleep(20);
+
+		port_funcs->cfg_lock(cn_port);
+	}
+
+	rc = port_funcs->qpc_query(cn_port, qp->qp_id, is_req, &qpc_attr);
+
+	if (rc && (rc != -EBUSY && rc != -ETIMEDOUT))
+		return rc;
+
+	if (rc || qpc_attr.in_work)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int cn_qp_op_reset(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_asic_funcs *asic_funcs;
+	int rc, rc1;
+
+	asic_funcs = hdev->asic_funcs;
+
+	/* clear the QPCs */
+	rc = asic_funcs->port_funcs->qpc_clear(cn_port, qp, false);
+	if (rc && hbl_cn_comp_device_operational(hdev))
+		/* Device might not respond during reset if the reset was due to error */
+		dev_err(hdev->dev, "Port %d QP %d: Failed to clear responder QPC\n",
+			qp->port, qp->qp_id);
+	else
+		qp->is_res = false;
+
+	rc1 = asic_funcs->port_funcs->qpc_clear(cn_port, qp, true);
+	if (rc1) {
+		rc = rc1;
+		if (hbl_cn_comp_device_operational(hdev))
+			/* Device might not respond during reset if the reset was due to error */
+			dev_err(hdev->dev, "Port %d QP %d: Failed to clear requestor QPC\n",
+				qp->port, qp->qp_id);
+	} else {
+		qp->is_req = false;
+	}
+
+	/* wait for REQ idle, RES idle is already done in cn_qp_op_2qpd */
+	rc = wait_for_qpc_idle(cn_port, qp, true);
+	if (rc) {
+		dev_err(hdev->dev, "Port %d QP %d, Requestor QPC is not idle (rc %d)\n",
+			cn_port->port, qp->qp_id, rc);
+		return rc;
+	}
+
+	qp->curr_state = CN_QP_STATE_RESET;
+
+	return rc;
+}
+
+static int cn_qp_op_reset_2init(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp)
+{
+	if (ZERO_OR_NULL_PTR(qp))
+		return -EINVAL;
+
+	qp->curr_state = CN_QP_STATE_INIT;
+
+	return 0;
+}
+
+static int cn_qp_op_2rts(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp,
+			 struct hbl_cni_req_conn_ctx_in *in)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_asic_funcs *asic_funcs;
+	int rc;
+
+	asic_funcs = hdev->asic_funcs;
+
+	rc = asic_funcs->set_req_qp_ctx(hdev, in, qp);
+	if (rc)
+		return rc;
+
+	qp->curr_state = CN_QP_STATE_RTS;
+	qp->is_req = true;
+
+	return 0;
+}
+
+static int cn_qp_op_2rtr(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp,
+			 struct hbl_cni_res_conn_ctx_in *in)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_asic_funcs *asic_funcs;
+	int rc;
+
+	asic_funcs = hdev->asic_funcs;
+
+	rc = asic_funcs->set_res_qp_ctx(hdev, in, qp);
+	if (rc)
+		return rc;
+
+	qp->curr_state = CN_QP_STATE_RTR;
+	qp->is_res = true;
+
+	return 0;
+}
+
+static inline int cn_qp_invalidate_qpc(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp,
+				       bool is_req)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_asic_funcs *asic_funcs;
+	int i, rc;
+
+	asic_funcs = hdev->asic_funcs;
+
+	for (i = 0; i < OP_RETRY_COUNT; i++) {
+		rc = asic_funcs->port_funcs->qpc_invalidate(cn_port, qp, is_req);
+		if (!rc)
+			break;
+
+		usleep_range(100, 200);
+	}
+
+	return rc;
+}
+
+static int cn_qp_invalidate(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp, bool is_req,
+			    bool wait_for_idle)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	int rc;
+
+	rc = cn_qp_invalidate_qpc(cn_port, qp, is_req);
+	if (rc) {
+		if (hbl_cn_comp_device_operational(hdev))
+			dev_err(hdev->dev, "Port %d QP %d, failed to invalidate %s QPC (rc %d)\n",
+				cn_port->port, qp->qp_id, is_req ? "Requester" : "Responder", rc);
+		return rc;
+	}
+
+	if (!wait_for_idle || is_req)
+		return 0;
+
+	/* check for QP idle in case of responder only */
+	rc = wait_for_qpc_idle(cn_port, qp, false);
+	if (rc) {
+		dev_err(hdev->dev, "Port %d QP %d, Responder QPC is not idle (rc %d)\n",
+			cn_port->port, qp->qp_id, rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+/* Drain the Requester */
+static int cn_qp_op_rts_2sqd(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp, void *attr)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_qpc_drain_attr *drain = attr;
+	int rc = 0;
+
+	switch (qp->curr_state) {
+	case CN_QP_STATE_RTS:
+		cn_qp_invalidate(cn_port, qp, true, drain->wait_for_idle);
+		if (drain->wait_for_idle)
+			ssleep(hdev->qp_drain_time);
+
+		break;
+	default:
+		rc = -EOPNOTSUPP;
+		break;
+	}
+
+	if (!rc)
+		qp->curr_state = CN_QP_STATE_SQD;
+
+	return rc;
+}
+
+/* Re-drain the Requester. This function is called without holding the cfg lock so it must not
+ * access the HW or do anything other than just sleeping.
+ */
+static int cn_qp_op_sqd_2sqd(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp, void *attr)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_qpc_drain_attr *drain = attr;
+
+	/* no need to invalidate the QP as it was already invalidated just extend the wait time */
+	if (drain->wait_for_idle)
+		ssleep(hdev->qp_drain_time);
+
+	return 0;
+}
+
+/* Drain the QP (Requester and Responder) */
+static int cn_qp_op_2qpd(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp, void *attr)
+{
+	struct hbl_cn_qpc_drain_attr *drain = attr;
+	int rc = 0;
+
+	switch (qp->curr_state) {
+	case CN_QP_STATE_RTR:
+		/* In RTR only the Resp is working */
+		cn_qp_invalidate(cn_port, qp, false, drain->wait_for_idle);
+		break;
+	case CN_QP_STATE_RTS:
+		/* In RTS both the Resp and Req are working */
+		cn_qp_op_rts_2sqd(cn_port, qp, attr);
+		cn_qp_invalidate(cn_port, qp, false, drain->wait_for_idle);
+		break;
+	case CN_QP_STATE_SQD:
+		/* In SQD only the Resp is working */
+		cn_qp_invalidate(cn_port, qp, false, drain->wait_for_idle);
+		break;
+	case CN_QP_STATE_QPD:
+		break;
+	default:
+		rc = -EOPNOTSUPP;
+		break;
+	}
+
+	if (!rc)
+		qp->curr_state = CN_QP_STATE_QPD;
+
+	return rc;
+}
+
+static int cn_qp_op_2reset(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp,
+			   const struct hbl_cn_qpc_reset_attr *attr)
+{
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_asic_funcs *asic_funcs;
+	struct hbl_cn_qpc_drain_attr drain;
+
+	asic_funcs = hdev->asic_funcs;
+
+	/* brute-force reset when reset mode is Hard */
+	if (attr->reset_mode == CN_QP_RESET_MODE_HARD && qp->curr_state != CN_QP_STATE_RESET) {
+		/* invalidate */
+		asic_funcs->port_funcs->qpc_invalidate(cn_port, qp, true);
+		asic_funcs->port_funcs->qpc_invalidate(cn_port, qp, false);
+
+		/* wait for HW digest the invalidation */
+		usleep_range(100, 150);
+
+		cn_qp_op_reset(cn_port, qp);
+		return 0;
+	}
+
+	if (attr->reset_mode == CN_QP_RESET_MODE_GRACEFUL)
+		drain.wait_for_idle = true;
+	else
+		drain.wait_for_idle = false;
+
+	switch (qp->curr_state) {
+	case CN_QP_STATE_RESET:
+		break;
+	case CN_QP_STATE_INIT:
+		cn_qp_op_reset(cn_port, qp);
+		break;
+	case CN_QP_STATE_RTR:
+	case CN_QP_STATE_RTS:
+	case CN_QP_STATE_SQD:
+		cn_qp_op_2qpd(cn_port, qp, &drain);
+		cn_qp_op_reset(cn_port, qp);
+		break;
+	case CN_QP_STATE_QPD:
+		cn_qp_op_reset(cn_port, qp);
+		break;
+	case CN_QP_STATE_SQERR:
+	case CN_QP_STATE_ERR:
+		cn_qp_op_reset(cn_port, qp);
+		break;
+	default:
+		dev_err(hdev->dev, "Port %d QP %d: Unknown state %d, moving to RESET state\n",
+			qp->port, qp->qp_id, qp->curr_state);
+		cn_qp_op_reset(cn_port, qp);
+		break;
+	}
+
+	return 0;
+}
+
+/* QP state handling routines  */
 int hbl_cn_qp_modify(struct hbl_cn_port *cn_port, struct hbl_cn_qp *qp,
 		     enum hbl_cn_qp_state new_state, void *params)
 {
-	return 0;
+	enum hbl_cn_qp_state prev_state = qp->curr_state;
+	struct hbl_cn_device *hdev = cn_port->hdev;
+	struct hbl_cn_asic_port_funcs *port_funcs;
+	enum hbl_cn_qp_state_op op;
+	int rc;
+
+	port_funcs = hdev->asic_funcs->port_funcs;
+
+	/* only SQD->SQD transition can be executed without holding the configuration lock */
+	if (prev_state != CN_QP_STATE_SQD || new_state != CN_QP_STATE_SQD) {
+		if (!port_funcs->cfg_is_locked(cn_port)) {
+			dev_err(hdev->dev,
+				"Configuration lock must be held while moving Port %u QP %u from state %s to %s\n",
+				qp->port, qp->qp_id, cn_qp_state_2name(prev_state),
+				cn_qp_state_2name(new_state));
+			return -EACCES;
+		}
+	}
+
+	if (qp->curr_state >= CN_QP_NUM_STATE || new_state >= CN_QP_NUM_STATE ||
+	    qp_valid_state_op[qp->curr_state][new_state] == CN_QP_OP_INVAL) {
+		dev_err(hdev->dev,
+			"Invalid QP state transition, Port %u QP %u from state %s to %s\n",
+			qp->port, qp->qp_id, cn_qp_state_2name(prev_state),
+			cn_qp_state_2name(new_state));
+		return -EINVAL;
+	}
+
+	if (new_state >= CN_QP_NUM_STATE) {
+		dev_err(hdev->dev, "Invalid QP state %d\n", new_state);
+		return -EINVAL;
+	}
+
+	/* get the operation needed for this state transition */
+	op = qp_valid_state_op[qp->curr_state][new_state];
+
+	switch (op) {
+	case CN_QP_OP_2RESET:
+		rc = cn_qp_op_2reset(cn_port, qp, params);
+		break;
+	case CN_QP_OP_RST_2INIT:
+		rc = cn_qp_op_reset_2init(cn_port, qp);
+		break;
+	case CN_QP_OP_INIT_2RTR:
+		rc = cn_qp_op_2rtr(cn_port, qp, params);
+		break;
+	case CN_QP_OP_RTR_2RTR:
+		rc = cn_qp_op_2rtr(cn_port, qp, params);
+		break;
+	case CN_QP_OP_RTR_2QPD:
+		rc = cn_qp_op_2qpd(cn_port, qp, params);
+		break;
+	case CN_QP_OP_RTR_2RTS:
+		rc = cn_qp_op_2rts(cn_port, qp, params);
+		break;
+	case CN_QP_OP_RTS_2RTS:
+		rc = cn_qp_op_2rts(cn_port, qp, params);
+		break;
+	case CN_QP_OP_RTS_2SQD:
+		rc = cn_qp_op_rts_2sqd(cn_port, qp, params);
+		break;
+	case CN_QP_OP_SQD_2SQD:
+		rc = cn_qp_op_sqd_2sqd(cn_port, qp, params);
+		break;
+	case CN_QP_OP_RTS_2QPD:
+		rc = cn_qp_op_2qpd(cn_port, qp, params);
+		break;
+	case CN_QP_OP_SQD_2QPD:
+		rc = cn_qp_op_2qpd(cn_port, qp, params);
+		break;
+	case CN_QP_OP_INVAL:
+		rc = -EINVAL;
+		break;
+	case CN_QP_OP_NOP:
+		rc = 0;
+		break;
+	default:
+		rc = -EOPNOTSUPP;
+		break;
+	}
+
+	if (rc)
+		dev_err(hdev->dev,
+			"Errors detected while moving Port %u QP %u from state %s to %s, (rc %d)\n",
+			qp->port, qp->qp_id, cn_qp_state_2name(prev_state),
+			cn_qp_state_2name(new_state), rc);
+
+	return rc;
 }
-- 
2.34.1





[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Photo]     [Yosemite News]     [Yosemite Photos]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux