astlpc: Support the host-side binding interface

The host-side KCS interface differs to the BMC that we need to mirror
the use of the IBF/OBF bits and the location of the Tx/Rx buffers.

The device (currently restricted to the host) also needs to use a
different initialisation sequence to the bus-owner (currently restricted
to the BMC), in that it must not write to regions of the control space
that the bus-owner owns. Concretely, the device is to write its
supported version range and then send the channel-init command via the
KCS interface.

Change-Id: I715b83d82119ebbdce6ef8176ea0a9facf9bb555
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/astlpc.c b/astlpc.c
index a5a3a82..1fa1492 100644
--- a/astlpc.c
+++ b/astlpc.c
@@ -10,6 +10,7 @@
 
 #include <assert.h>
 #include <err.h>
+#include <errno.h>
 #include <inttypes.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -37,6 +38,16 @@
 
 #endif
 
+struct mctp_astlpc_buffer {
+	uint32_t offset;
+	uint32_t size;
+};
+
+struct mctp_astlpc_layout {
+	struct mctp_astlpc_buffer rx;
+	struct mctp_astlpc_buffer tx;
+};
+
 struct mctp_binding_astlpc {
 	struct mctp_binding	binding;
 
@@ -44,6 +55,9 @@
 		void			*lpc_map;
 		struct mctp_lpcmap_hdr	*lpc_hdr;
 	};
+	struct mctp_astlpc_layout layout;
+
+	uint8_t mode;
 
 	/* direct ops data */
 	struct mctp_binding_astlpc_ops	ops;
@@ -61,9 +75,13 @@
 #define binding_to_astlpc(b) \
 	container_of(b, struct mctp_binding_astlpc, binding)
 
-#define MCTP_MAGIC	0x4d435450
-#define BMC_VER_MIN	1
-#define BMC_VER_CUR	1
+/* clang-format off */
+#define ASTLPC_MCTP_MAGIC	0x4d435450
+#define ASTLPC_VER_MIN	1
+#define ASTLPC_VER_CUR	1
+
+#define ASTLPC_BODY_SIZE(sz)	((sz) - 4)
+/* clang-format on */
 
 struct mctp_lpcmap_hdr {
 	uint32_t	magic;
@@ -104,6 +122,145 @@
 	return astlpc->lpc_map != NULL;
 }
 
+static int mctp_astlpc_init_bmc(struct mctp_binding_astlpc *astlpc)
+{
+	struct mctp_lpcmap_hdr *hdr;
+	uint8_t status;
+	int rc;
+
+	/* Flip the buffers as the names are defined in terms of the host */
+	astlpc->layout.rx.offset = tx_offset;
+	astlpc->layout.rx.size = tx_size;
+	astlpc->layout.tx.offset = rx_offset;
+	astlpc->layout.tx.size = rx_size;
+
+	if (lpc_direct(astlpc))
+		hdr = astlpc->lpc_hdr;
+	else
+		hdr = astlpc->priv_hdr;
+
+	hdr->magic = htobe32(ASTLPC_MCTP_MAGIC);
+	hdr->bmc_ver_min = htobe16(ASTLPC_VER_MIN);
+	hdr->bmc_ver_cur = htobe16(ASTLPC_VER_CUR);
+
+	/* Flip the buffers back as we're now describing the host's
+	 * configuration to the host */
+	hdr->rx_offset = htobe32(astlpc->layout.tx.offset);
+	hdr->rx_size = htobe32(astlpc->layout.tx.size);
+	hdr->tx_offset = htobe32(astlpc->layout.rx.offset);
+	hdr->tx_size = htobe32(astlpc->layout.rx.size);
+
+	if (!lpc_direct(astlpc))
+		astlpc->ops.lpc_write(astlpc->ops_data, hdr, 0, sizeof(*hdr));
+
+	/* set status indicating that the BMC is now active */
+	status = KCS_STATUS_BMC_READY | KCS_STATUS_OBF;
+	/* XXX: Should we be calling mctp_astlpc_kcs_set_status() instead? */
+	rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_STATUS,
+				   status);
+	if (rc) {
+		mctp_prwarn("KCS write failed");
+	}
+
+	return rc;
+}
+
+static int mctp_binding_astlpc_start_bmc(struct mctp_binding *b)
+{
+	struct mctp_binding_astlpc *astlpc =
+		container_of(b, struct mctp_binding_astlpc, binding);
+
+	return mctp_astlpc_init_bmc(astlpc);
+}
+
+static int mctp_astlpc_init_host(struct mctp_binding_astlpc *astlpc)
+{
+	struct mctp_lpcmap_hdr *hdr;
+	uint8_t status;
+	int rc;
+
+	rc = astlpc->ops.kcs_read(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_STATUS,
+				  &status);
+	if (rc) {
+		mctp_prwarn("KCS status read failed");
+		return rc;
+	}
+
+	astlpc->kcs_status = status;
+
+	if (!(status & KCS_STATUS_BMC_READY))
+		return -EHOSTDOWN;
+
+	if (lpc_direct(astlpc)) {
+		hdr = astlpc->lpc_hdr;
+	} else {
+		hdr = astlpc->priv_hdr;
+		astlpc->ops.lpc_read(astlpc->ops_data, hdr, 0, sizeof(*hdr));
+	}
+
+	astlpc->layout.rx.offset = be32toh(hdr->rx_offset);
+	astlpc->layout.rx.size = be32toh(hdr->rx_size);
+	astlpc->layout.tx.offset = be32toh(hdr->tx_offset);
+	astlpc->layout.tx.size = be32toh(hdr->tx_size);
+
+	hdr->host_ver_min = htobe16(ASTLPC_VER_MIN);
+	if (!lpc_direct(astlpc))
+		astlpc->ops.lpc_write(astlpc->ops_data, &hdr->host_ver_min,
+				      offsetof(struct mctp_lpcmap_hdr,
+					       host_ver_min),
+				      sizeof(hdr->host_ver_min));
+
+	hdr->host_ver_cur = htobe16(ASTLPC_VER_CUR);
+	if (!lpc_direct(astlpc))
+		astlpc->ops.lpc_write(astlpc->ops_data, &hdr->host_ver_cur,
+				      offsetof(struct mctp_lpcmap_hdr,
+					       host_ver_cur),
+				      sizeof(hdr->host_ver_cur));
+
+	/* Send channel init command */
+	rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA,
+				   0x0);
+	if (rc) {
+		mctp_prwarn("KCS write failed");
+	}
+
+	return rc;
+}
+
+static int mctp_binding_astlpc_start_host(struct mctp_binding *b)
+{
+	struct mctp_binding_astlpc *astlpc =
+		container_of(b, struct mctp_binding_astlpc, binding);
+
+	return mctp_astlpc_init_host(astlpc);
+}
+
+static bool __mctp_astlpc_kcs_ready(struct mctp_binding_astlpc *astlpc,
+				    uint8_t status, bool is_write)
+{
+	bool is_bmc;
+	bool ready_state;
+	uint8_t flag;
+
+	is_bmc = (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_BMC);
+	flag = (is_bmc ^ is_write) ? KCS_STATUS_IBF : KCS_STATUS_OBF;
+	ready_state = is_write ? 0 : 1;
+
+	return !!(status & flag) == ready_state;
+}
+
+static inline bool
+mctp_astlpc_kcs_read_ready(struct mctp_binding_astlpc *astlpc, uint8_t status)
+{
+	return __mctp_astlpc_kcs_ready(astlpc, status, false);
+}
+
+static inline bool
+mctp_astlpc_kcs_write_ready(struct mctp_binding_astlpc *astlpc, uint8_t status)
+{
+	return __mctp_astlpc_kcs_ready(astlpc, status, true);
+}
+
 static int mctp_astlpc_kcs_set_status(struct mctp_binding_astlpc *astlpc,
 		uint8_t status)
 {
@@ -149,7 +306,7 @@
 			mctp_prwarn("KCS status read failed");
 			return -1;
 		}
-		if (!(status & KCS_STATUS_OBF))
+		if (mctp_astlpc_kcs_write_ready(astlpc, status))
 			break;
 		/* todo: timeout */
 	}
@@ -177,21 +334,22 @@
 	mctp_prdebug("%s: Transmitting %"PRIu32"-byte packet (%hhu, %hhu, 0x%hhx)",
 		     __func__, len, hdr->src, hdr->dest, hdr->flags_seq_tag);
 
-	if (len > rx_size - 4) {
+	if (len > ASTLPC_BODY_SIZE(astlpc->layout.tx.size)) {
 		mctp_prwarn("invalid TX len 0x%x", len);
 		return -1;
 	}
 
 	if (lpc_direct(astlpc)) {
-		*(uint32_t *)(astlpc->lpc_map + rx_offset) = htobe32(len);
-		memcpy(astlpc->lpc_map + rx_offset + 4, mctp_pktbuf_hdr(pkt),
-				len);
+		*(uint32_t *)(astlpc->lpc_map + astlpc->layout.tx.offset) =
+			htobe32(len);
+		memcpy(astlpc->lpc_map + astlpc->layout.tx.offset + 4,
+		       mctp_pktbuf_hdr(pkt), len);
 	} else {
 		uint32_t tmp = htobe32(len);
-		astlpc->ops.lpc_write(astlpc->ops_data, &tmp, rx_offset,
-				sizeof(tmp));
+		astlpc->ops.lpc_write(astlpc->ops_data, &tmp,
+				      astlpc->layout.tx.offset, sizeof(tmp));
 		astlpc->ops.lpc_write(astlpc->ops_data, mctp_pktbuf_hdr(pkt),
-				rx_offset + 4, len);
+				      astlpc->layout.tx.offset + 4, len);
 	}
 
 	mctp_binding_set_tx_enabled(b, false);
@@ -225,14 +383,14 @@
 	uint32_t len;
 
 	if (lpc_direct(astlpc)) {
-		len = *(uint32_t *)(astlpc->lpc_map + tx_offset);
+		len = *(uint32_t *)(astlpc->lpc_map + astlpc->layout.rx.offset);
 	} else {
 		astlpc->ops.lpc_read(astlpc->ops_data, &len,
-				tx_offset, sizeof(len));
+				     astlpc->layout.rx.offset, sizeof(len));
 	}
 	len = be32toh(len);
 
-	if (len > tx_size - 4) {
+	if (len > ASTLPC_BODY_SIZE(astlpc->layout.rx.size)) {
 		mctp_prwarn("invalid RX len 0x%x", len);
 		return;
 	}
@@ -249,10 +407,10 @@
 
 	if (lpc_direct(astlpc)) {
 		memcpy(mctp_pktbuf_hdr(pkt),
-				astlpc->lpc_map + tx_offset + 4, len);
+		       astlpc->lpc_map + astlpc->layout.rx.offset + 4, len);
 	} else {
 		astlpc->ops.lpc_read(astlpc->ops_data, mctp_pktbuf_hdr(pkt),
-				tx_offset + 4, len);
+				     astlpc->layout.rx.offset + 4, len);
 	}
 
 	mctp_bus_rx(&astlpc->binding, pkt);
@@ -266,6 +424,30 @@
 	mctp_binding_set_tx_enabled(&astlpc->binding, true);
 }
 
+static int mctp_astlpc_update_channel(struct mctp_binding_astlpc *astlpc,
+				      uint8_t status)
+{
+	uint8_t updated;
+	int rc = 0;
+
+	assert(astlpc->mode == MCTP_BINDING_ASTLPC_MODE_HOST);
+
+	updated = astlpc->kcs_status ^ status;
+
+	if (updated & KCS_STATUS_BMC_READY) {
+		if (!(status & KCS_STATUS_BMC_READY))
+			mctp_binding_set_tx_enabled(&astlpc->binding, false);
+	}
+
+	if (updated & KCS_STATUS_CHANNEL_ACTIVE)
+		mctp_binding_set_tx_enabled(&astlpc->binding,
+					    status & KCS_STATUS_CHANNEL_ACTIVE);
+
+	astlpc->kcs_status = status;
+
+	return rc;
+}
+
 int mctp_astlpc_poll(struct mctp_binding_astlpc *astlpc)
 {
 	uint8_t status, data;
@@ -280,7 +462,7 @@
 
 	mctp_prdebug("%s: status: 0x%hhx", __func__, status);
 
-	if (!(status & KCS_STATUS_IBF))
+	if (!mctp_astlpc_kcs_read_ready(astlpc, status))
 		return 0;
 
 	rc = astlpc->ops.kcs_read(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA,
@@ -303,70 +485,47 @@
 		mctp_astlpc_tx_complete(astlpc);
 		break;
 	case 0xff:
-		/* reserved value for dummy data writes; do nothing */
-		break;
+		/* No responsibilities for the BMC on 0xff */
+		if (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_BMC)
+			return 0;
+
+		return mctp_astlpc_update_channel(astlpc, status);
 	default:
 		mctp_prwarn("unknown message 0x%x", data);
 	}
 	return 0;
 }
 
-static int mctp_astlpc_init_bmc(struct mctp_binding_astlpc *astlpc)
-{
-	struct mctp_lpcmap_hdr *hdr;
-	uint8_t status;
-	int rc;
-
-	if (lpc_direct(astlpc))
-		hdr = astlpc->lpc_hdr;
-	else
-		hdr = astlpc->priv_hdr;
-
-	hdr->magic = htobe32(MCTP_MAGIC);
-	hdr->bmc_ver_min = htobe16(BMC_VER_MIN);
-	hdr->bmc_ver_cur = htobe16(BMC_VER_CUR);
-
-	hdr->rx_offset = htobe32(rx_offset);
-	hdr->rx_size = htobe32(rx_size);
-	hdr->tx_offset = htobe32(tx_offset);
-	hdr->tx_size = htobe32(tx_size);
-
-	if (!lpc_direct(astlpc))
-		astlpc->ops.lpc_write(astlpc->ops_data, hdr, 0, sizeof(*hdr));
-
-	/* set status indicating that the BMC is now active */
-	status = KCS_STATUS_BMC_READY | KCS_STATUS_OBF;
-	rc = astlpc->ops.kcs_write(astlpc->ops_data,
-			MCTP_ASTLPC_KCS_REG_STATUS, status);
-	if (rc) {
-		mctp_prwarn("KCS write failed");
-	}
-
-	return rc;
-}
-
-static int mctp_binding_astlpc_start(struct mctp_binding *b)
-{
-	struct mctp_binding_astlpc *astlpc = container_of(b,
-			struct mctp_binding_astlpc, binding);
-
-	return mctp_astlpc_init_bmc(astlpc);
-}
-
 /* allocate and basic initialisation */
-static struct mctp_binding_astlpc *__mctp_astlpc_init(void)
+static struct mctp_binding_astlpc *__mctp_astlpc_init(uint8_t mode,
+						      uint32_t mtu)
 {
 	struct mctp_binding_astlpc *astlpc;
 
+	assert((mode == MCTP_BINDING_ASTLPC_MODE_BMC) ||
+	       (mode == MCTP_BINDING_ASTLPC_MODE_HOST));
+
 	astlpc = __mctp_alloc(sizeof(*astlpc));
+	if (!astlpc)
+		return NULL;
+
 	memset(astlpc, 0, sizeof(*astlpc));
+	astlpc->mode = mode;
+	astlpc->lpc_map = NULL;
 	astlpc->binding.name = "astlpc";
 	astlpc->binding.version = 1;
-	astlpc->binding.tx = mctp_binding_astlpc_tx;
-	astlpc->binding.start = mctp_binding_astlpc_start;
-	astlpc->binding.pkt_size = MCTP_PACKET_SIZE(MCTP_BTU);
+	astlpc->binding.pkt_size = MCTP_PACKET_SIZE(mtu);
 	astlpc->binding.pkt_pad = 0;
-	astlpc->lpc_map = NULL;
+	astlpc->binding.tx = mctp_binding_astlpc_tx;
+	if (mode == MCTP_BINDING_ASTLPC_MODE_BMC)
+		astlpc->binding.start = mctp_binding_astlpc_start_bmc;
+	else if (mode == MCTP_BINDING_ASTLPC_MODE_HOST)
+		astlpc->binding.start = mctp_binding_astlpc_start_host;
+	else {
+		mctp_prerr("%s: Invalid mode: %d\n", __func__, mode);
+		__mctp_free(astlpc);
+		return NULL;
+	}
 
 	return astlpc;
 }
@@ -376,19 +535,32 @@
 	return &b->binding;
 }
 
-struct mctp_binding_astlpc *mctp_astlpc_init_ops(
-		const struct mctp_binding_astlpc_ops *ops,
-		void *ops_data, void *lpc_map)
+struct mctp_binding_astlpc *
+mctp_astlpc_init(uint8_t mode, uint32_t mtu, void *lpc_map,
+		 const struct mctp_binding_astlpc_ops *ops, void *ops_data)
 {
 	struct mctp_binding_astlpc *astlpc;
 
-	astlpc = __mctp_astlpc_init();
+	if (!(mode == MCTP_BINDING_ASTLPC_MODE_BMC ||
+	      mode == MCTP_BINDING_ASTLPC_MODE_HOST)) {
+		mctp_prerr("Unknown binding mode: %u", mode);
+		return NULL;
+	}
+
+	if (mtu != MCTP_BTU) {
+		mctp_prwarn("Unable to negotiate the MTU, using %u instead",
+			    MCTP_BTU);
+		mtu = MCTP_BTU;
+	}
+
+	astlpc = __mctp_astlpc_init(mode, mtu);
 	if (!astlpc)
 		return NULL;
 
 	memcpy(&astlpc->ops, ops, sizeof(astlpc->ops));
 	astlpc->ops_data = ops_data;
 	astlpc->lpc_map = lpc_map;
+	astlpc->mode = mode;
 
 	/* In indirect mode, we keep a separate buffer of header data.
 	 * We need to sync this through the lpc_read/lpc_write ops.
@@ -399,6 +571,14 @@
 	return astlpc;
 }
 
+struct mctp_binding_astlpc *
+mctp_astlpc_init_ops(const struct mctp_binding_astlpc_ops *ops, void *ops_data,
+		     void *lpc_map)
+{
+	return mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_BMC, MCTP_BTU, lpc_map,
+				ops, ops_data);
+}
+
 void mctp_astlpc_destroy(struct mctp_binding_astlpc *astlpc)
 {
 	if (astlpc->priv_hdr)
@@ -490,7 +670,11 @@
 	struct mctp_binding_astlpc *astlpc;
 	int rc;
 
-	astlpc = __mctp_astlpc_init();
+	/*
+	 * If we're doing file IO then we're very likely not running
+	 * freestanding, so lets assume that we're on the BMC side
+	 */
+	astlpc = __mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_BMC, MCTP_BTU);
 	if (!astlpc)
 		return NULL;
 
diff --git a/libmctp-astlpc.h b/libmctp-astlpc.h
index 23fe642..a3e7ffb 100644
--- a/libmctp-astlpc.h
+++ b/libmctp-astlpc.h
@@ -9,8 +9,11 @@
 
 #include <libmctp.h>
 
+#include <stdint.h>
+
 struct mctp_binding_astlpc;
 
+/* todo: Remove enum from public interfaces */
 enum mctp_binding_astlpc_kcs_reg {
 	MCTP_ASTLPC_KCS_REG_DATA = 0,
 	MCTP_ASTLPC_KCS_REG_STATUS = 1,
@@ -25,9 +28,15 @@
 	int	(*lpc_write)(void *data, void *buf, long offset, size_t len);
 };
 
-struct mctp_binding_astlpc *mctp_astlpc_init_ops(
-		const struct mctp_binding_astlpc_ops *ops,
-		void *ops_data, void *lpc_map);
+#define MCTP_BINDING_ASTLPC_MODE_BMC 0
+#define MCTP_BINDING_ASTLPC_MODE_HOST 1
+struct mctp_binding_astlpc *
+mctp_astlpc_init(uint8_t mode, uint32_t mtu, void *lpc_map,
+		 const struct mctp_binding_astlpc_ops *ops, void *ops_data);
+
+struct mctp_binding_astlpc *
+mctp_astlpc_init_ops(const struct mctp_binding_astlpc_ops *ops, void *ops_data,
+		     void *lpc_map);
 void mctp_astlpc_destroy(struct mctp_binding_astlpc *astlpc);
 
 struct mctp_binding *mctp_binding_astlpc_core(struct mctp_binding_astlpc *b);