From e58e6f2adfb65fb960cdc41289a5186b4370fd1e Mon Sep 17 00:00:00 2001
From: Aseda Aboagye <aaboagye@google.com>
Date: Tue, 15 Jun 2021 23:11:41 -0700
Subject: soc/intel/common/block/gpio: Add `gpio_lock_pad()`

This commit adds a method for locking a GPIO pad configuration and its
TX state.  When the configuration is locked, the following registers
become Read-Only and software writes to these registers have no effect.

  Pad Configuration registers
  GPI_NMI_EN
  GPI_SMI_EN
  GPI_GPE_EN

Note that this is only effective if the pad is owned by the host (set in
the PAD_OWN register).

Intel platforms that wish to leverage this function need to define the
PADCFGLOCK offset for their platform.

BUG=b:191189275
BRANCH=None
TEST=With some other code, call gpio_lock_pad() against a pad and verify
that the pad configuration is locked and the state of the pad cannot be
changed from the OS.

Signed-off-by: Aseda Aboagye <aaboagye@google.com>
Change-Id: Id3c0da2f6942099c0289ca1e33a33c176f49d380
Reviewed-on: https://review.coreboot.org/c/coreboot/+/55557
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Furquan Shaikh <furquan@google.com>
Reviewed-by: Karthik Ramasubramanian <kramasub@google.com>
---
 src/soc/intel/common/block/gpio/gpio.c             | 79 ++++++++++++++++++++++
 .../intel/common/block/include/intelblocks/gpio.h  | 33 +++++++++
 2 files changed, 112 insertions(+)

(limited to 'src/soc')

diff --git a/src/soc/intel/common/block/gpio/gpio.c b/src/soc/intel/common/block/gpio/gpio.c
index 8f1c3a5601..d4a312bf9a 100644
--- a/src/soc/intel/common/block/gpio/gpio.c
+++ b/src/soc/intel/common/block/gpio/gpio.c
@@ -8,6 +8,7 @@
 #include <intelblocks/gpio.h>
 #include <gpio.h>
 #include <intelblocks/itss.h>
+#include <intelblocks/p2sb.h>
 #include <intelblocks/pcr.h>
 #include <soc/pm.h>
 #include <stdlib.h>
@@ -446,6 +447,84 @@ int gpio_get(gpio_t gpio_num)
 	return !!(reg & PAD_CFG0_RX_STATE);
 }
 
+int gpio_lock_pad(const gpio_t pad, enum gpio_lock_action action)
+{
+	const struct pad_community *comm = gpio_get_community(pad);
+	size_t rel_pad;
+	uint16_t offset;
+	uint32_t data;
+	uint8_t response;
+	int status;
+
+	/*
+	 * FSP-S will unlock all the GPIO pads and hide the P2SB device.  With
+	 * the device hidden, we will not be able to send the sideband interface
+	 * message to lock the GPIO configuration. Therefore, we need to unhide
+	 * the P2SB device which can only be done in SMM requiring that this
+	 * function is called from SMM.
+	 */
+	if (!ENV_SMM) {
+		printk(BIOS_ERR, "%s: Error: must be called from SMM!\n", __func__);
+		return -1;
+	}
+
+	if (!(action & GPIO_LOCK_FULL)) {
+		printk(BIOS_ERR, "%s: Error: no action specified!\n", __func__);
+		return -1;
+	}
+
+	rel_pad = relative_pad_in_comm(comm, pad);
+	offset = comm->pad_cfg_lock_offset;
+	if (!offset) {
+		printk(BIOS_ERR, "%s: Error: offset is not defined!\n", __func__);
+		return -1;
+	}
+	offset += gpio_group_index_scaled(comm, rel_pad, 2 * sizeof(uint32_t));
+
+	/* We must use the sideband interface in order to lock the pad. */
+	struct pcr_sbi_msg msg = {
+		.pid = comm->port,
+		.offset = offset,
+		.opcode = GPIO_LOCK_UNLOCK,
+		.is_posted = false,
+		.fast_byte_enable = 0xF,
+		.bar = 0,
+		.fid = 0,
+	};
+
+	p2sb_unhide();
+
+	data = gpio_bitmask_within_group(comm, rel_pad);
+
+	if (action & GPIO_LOCK_CONFIG) {
+		printk(BIOS_INFO, "%s: Locking pad %d configuration\n",
+		       __func__, pad);
+		status = pcr_execute_sideband_msg(&msg, &data, &response);
+		if (status || response) {
+			printk(BIOS_ERR, "%s: error status=%x response=%x\n", __func__, status,
+			       response);
+			p2sb_hide();
+			return status == -1 ? -1 : response;
+		}
+	}
+
+	if (action & GPIO_LOCK_TX) {
+		printk(BIOS_INFO, "%s: Locking pad %d TX state\n", __func__,
+		       pad);
+		msg.offset = msg.offset + 4;
+		status = pcr_execute_sideband_msg(&msg, &data, &response);
+		if (status || response) {
+			printk(BIOS_ERR, "%s: error status=%x response=%x\n", __func__, status,
+			       response);
+			p2sb_hide();
+			return status == -1 ? -1 : response;
+		}
+	}
+
+	p2sb_hide();
+	return 0;
+}
+
 void gpio_set(gpio_t gpio_num, int value)
 {
 	const struct pad_community *comm = gpio_get_community(gpio_num);
diff --git a/src/soc/intel/common/block/include/intelblocks/gpio.h b/src/soc/intel/common/block/include/intelblocks/gpio.h
index 86a36d5e6b..8496db32c5 100644
--- a/src/soc/intel/common/block/include/intelblocks/gpio.h
+++ b/src/soc/intel/common/block/include/intelblocks/gpio.h
@@ -121,6 +121,7 @@ struct pad_community {
 	uint16_t	gpi_nmi_sts_reg_0; /* offset to GPI NMI STS Reg 0 */
 	uint16_t	gpi_nmi_en_reg_0; /* offset to GPI NMI EN Reg 0 */
 	uint16_t	pad_cfg_base; /* offset to first PAD_GFG_DW0 Reg */
+	uint16_t	pad_cfg_lock_offset; /* offset to first PADCFGLOCK Reg */
 	uint8_t		gpi_status_offset;  /* specifies offset in struct
 						gpi_status */
 	uint8_t		port;	/* PCR Port ID */
@@ -197,6 +198,38 @@ void gpio_configure_pads_with_override(const struct pad_config *base_cfg,
  */
 void *gpio_dwx_address(const gpio_t pad);
 
+enum gpio_lock_action {
+	GPIO_LOCK_CONFIG = 0x1,
+	GPIO_LOCK_TX	 = 0x2,
+	GPIO_LOCK_FULL	 = GPIO_LOCK_CONFIG | GPIO_LOCK_TX,
+};
+
+/*
+ * Lock a GPIO's configuration.
+ *
+ * The caller may specify if they wish to only lock the pad configuration, only
+ * the TX state, or both.  When the configuration is locked, the following
+ * registers become Read-Only and software writes to these registers have no
+ * effect.
+ *
+ *	Pad Configuration registers,
+ *	GPI_NMI_EN,
+ *	GPI_SMI_EN,
+ *	GPI_GPE_EN
+ *
+ * Note that this is only effective if the pad is owned by the host and this
+ * function may only be called in SMM.
+ *
+ * @param pad: GPIO pad number
+ * @param action: Which register to lock.
+ * @return 0 if successful,
+ * 1 - unsuccessful
+ * 2 - powered down
+ * 3 - multi-cast mixed
+ * -1 - sideband message failed or other error
+ */
+int gpio_lock_pad(const gpio_t pad, enum gpio_lock_action action);
+
 /*
  * Returns the pmc_gpe to gpio_gpe mapping table
  *
-- 
cgit v1.2.3