[Skiboot] [RFC UNTESTED PATCH] ast-sf-ctrl: Try to harden vs. BMC collisions & reboots

Benjamin Herrenschmidt benh at kernel.crashing.org
Fri Jun 3 10:20:31 AEST 2016


We try to detect collisions and reboots via changes to the control
register. When they happen, we offline ourselves for 2mn and restore
all settings when done.

Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
---
 hw/ast-bmc/ast-sf-ctrl.c | 172 +++++++++++++++++++++++++++++++++++++----------
 include/timebase.h       |  11 +++
 libflash/errors.h        |   1 +
 libflash/libflash.c      |   2 +-
 4 files changed, 148 insertions(+), 38 deletions(-)

diff --git a/hw/ast-bmc/ast-sf-ctrl.c b/hw/ast-bmc/ast-sf-ctrl.c
index bf42d32..df85934 100644
--- a/hw/ast-bmc/ast-sf-ctrl.c
+++ b/hw/ast-bmc/ast-sf-ctrl.c
@@ -20,6 +20,12 @@
 #include <stdio.h>
 #include <string.h>
 
+#ifdef __SKIBOOT__
+#include <timebase.h>
+/* 2 minutes delay when something bad detected with BMC */
+#define OFFLINE_DELAY	secs_to_tb(2 * 60)
+#endif
+
 #include <libflash/libflash.h>
 #include <libflash/libflash-priv.h>
 
@@ -54,6 +60,9 @@ struct ast_sf_ctrl {
 	/* Current 4b mode */
 	bool			mode_4b;
 
+	/* offline target for when BMC reboots */
+	uint64_t		offline_target;
+
 	/* Callbacks */
 	struct spi_flash_ctrl	ops;
 };
@@ -68,8 +77,114 @@ static const uint32_t ast_ct_hclk_divs[] = {
 	0xd, /* HCLK/5 */
 };
 
+static int ast_sf_set_4b(struct spi_flash_ctrl *ctrl, bool enable)
+{
+	struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+	uint32_t ce_ctrl = 0;
+
+	if (ct->type == AST_SF_TYPE_BMC && ct->ops.finfo->size > 0x1000000)
+		ce_ctrl = ast_ahb_readl(BMC_SPI_FCTL_CE_CTRL);
+	else if (ct->type != AST_SF_TYPE_PNOR)
+		return enable ? FLASH_ERR_4B_NOT_SUPPORTED : 0;
+
+	/*
+	 * We update the "old" value as well since when quitting
+	 * we don't restore the mode of the flash itself so we need
+	 * to leave the controller in a compatible setup
+	 */
+	if (enable) {
+		ct->ctl_val |= 0x2000;
+		ct->ctl_read_val |= 0x2000;
+		ce_ctrl |= 0x1;
+	} else {
+		ct->ctl_val &= ~0x2000;
+		ct->ctl_read_val &= ~0x2000;
+		ce_ctrl &= ~0x1;
+	}
+	ct->mode_4b = enable;
+
+	/* Update read mode */
+	ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+
+	if (ce_ctrl && ct->type == AST_SF_TYPE_BMC)
+		ast_ahb_writel(ce_ctrl, BMC_SPI_FCTL_CE_CTRL);
+
+	return 0;
+}
+
+static bool ast_sf_check_bmc(struct ast_sf_ctrl *ct)
+{
+#ifdef __SKIBOOT__
+	uint32_t ctrl;
+
+	/* If BMC is marked offline, just return */
+	if (time_before(ct->offline_target))
+		return false;
+
+	/* Read control register */
+	ctrl = ast_ahb_readl(ct->ctl_reg);
+
+	/* If we didn't have an offline target, check the BMC state */
+	/* We had an offline target, it's now expired, check if the BMC
+	 * is responding and restore registers.
+	 */
+	if (!ct->offline_target) {
+		/* Expected value, we are good */
+		if (((ct->ctl_reg ^ ctrl) & ~0x7) == 0)
+			return true;
+
+		/* Unexpected value, we mark ourselves offline for a couple
+		 * of minutes as the BMC might be rebooting
+		 */
+		prlog(PR_WARNING, "AST: Flash controller settings changed, "
+		      "BMC rebooted ?\n");
+		prlog(PR_WARNING, "AST: Flash access off for a while...\n");
+		prlog(PR_WARNING, "AST: ctrl reg was: 0x%08x\n", ctrl);
+		ct->offline_target = mftb() + OFFLINE_DELAY;
+
+		/* We also whack the register to clear CE# since a collision
+		 * did occur
+		 */
+		ast_ahb_writel(ctrl | 7, ct->ctl_reg);
+		ast_ahb_writel(ctrl & ~7, ct->ctl_reg);
+		return false;
+	}
+
+	/* We had an offline target and we passed it, check if the BMC
+	 * is responding (also clear CE# just in case)
+	 */
+	ast_ahb_writel(ct->ctl_val | 7, ct->ctl_reg);
+	ctrl = ast_ahb_readl(ct->ctl_reg);
+	if (((ct->ctl_reg ^ ctrl) & ~0x7) != 0) {
+		prlog(PR_WARNING, "AST: Flash controller still not"
+		      " responding, waiting longer...\n");
+		ct->offline_target = mftb() + OFFLINE_DELAY;
+		return false;
+	}
+
+	/* Ok, we are good now, reconfigure all */
+	ct->offline_target = 0;
+
+	/* Restore timing calibration */
+	ast_ahb_writel(ct->fread_timing_val, ct->fread_timing_reg);
+
+	/* Update read mode */
+	ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+
+	/* Restore 4b mode */
+	ast_sf_set_4b(&ct->ops, ct->mode_4b);
+#endif /* __SKIBOOT__ */
+
+	return true;
+
+}
+
 static int ast_sf_start_cmd(struct ast_sf_ctrl *ct, uint8_t cmd)
 {
+	/* Check for a BMC reboot having lost settings */
+	if (!ast_sf_check_bmc(ct))
+		return FLASH_ERR_OFFLINE;
+
 	/* Switch to user mode, CE# dropped */
 	ast_ahb_writel(ct->ctl_val | 7, ct->ctl_reg);
 
@@ -80,13 +195,21 @@ static int ast_sf_start_cmd(struct ast_sf_ctrl *ct, uint8_t cmd)
 	return ast_copy_to_ahb(ct->flash, &cmd, 1);
 }
 
-static void ast_sf_end_cmd(struct ast_sf_ctrl *ct)
+static bool ast_sf_end_cmd(struct ast_sf_ctrl *ct)
 {
+	bool bmc_lost;
+
+	/* Check for a BMC reboot having lost settings */
+	if (!ast_sf_check_bmc(ct))
+		return FLASH_ERR_OFFLINE;
+
 	/* clear CE# */
 	ast_ahb_writel(ct->ctl_val | 7, ct->ctl_reg);
 
 	/* Switch back to read mode */
 	ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
+
+	return bmc_lost;
 }
 
 static int ast_sf_send_addr(struct ast_sf_ctrl *ct, uint32_t addr)
@@ -149,51 +272,26 @@ static int ast_sf_cmd_wr(struct spi_flash_ctrl *ctrl, uint8_t cmd,
 	return rc;
 }
 
-static int ast_sf_set_4b(struct spi_flash_ctrl *ctrl, bool enable)
-{
-	struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
-	uint32_t ce_ctrl = 0;
-
-	if (ct->type == AST_SF_TYPE_BMC && ct->ops.finfo->size > 0x1000000)
-		ce_ctrl = ast_ahb_readl(BMC_SPI_FCTL_CE_CTRL);
-	else if (ct->type != AST_SF_TYPE_PNOR)
-		return enable ? FLASH_ERR_4B_NOT_SUPPORTED : 0;
-
-	/*
-	 * We update the "old" value as well since when quitting
-	 * we don't restore the mode of the flash itself so we need
-	 * to leave the controller in a compatible setup
-	 */
-	if (enable) {
-		ct->ctl_val |= 0x2000;
-		ct->ctl_read_val |= 0x2000;
-		ce_ctrl |= 0x1;
-	} else {
-		ct->ctl_val &= ~0x2000;
-		ct->ctl_read_val &= ~0x2000;
-		ce_ctrl &= ~0x1;
-	}
-	ct->mode_4b = enable;
-
-	/* Update read mode */
-	ast_ahb_writel(ct->ctl_read_val, ct->ctl_reg);
-
-	if (ce_ctrl && ct->type == AST_SF_TYPE_BMC)
-		ast_ahb_writel(ce_ctrl, BMC_SPI_FCTL_CE_CTRL);
-
-	return 0;
-}
-
 static int ast_sf_read(struct spi_flash_ctrl *ctrl, uint32_t pos,
 		       void *buf, uint32_t len)
 {
 	struct ast_sf_ctrl *ct = container_of(ctrl, struct ast_sf_ctrl, ops);
+	int rc;
 
+	if (!ast_sf_check_bmc(ct))
+		return FLASH_ERR_OFFLINE;
 	/*
 	 * We are in read mode by default. We don't yet support fancy
 	 * things like fast read or X2 mode
 	 */
-	return ast_copy_from_ahb(buf, ct->flash + pos, len);
+	rc = ast_copy_from_ahb(buf, ct->flash + pos, len);
+	if (rc)
+		return rc;
+
+	if (!ast_sf_check_bmc(ct))
+		return FLASH_ERR_OFFLINE;
+
+	return 0;
 }
 
 static void ast_get_ahb_freq(void)
diff --git a/include/timebase.h b/include/timebase.h
index 7f5afe1..a1d8a1c 100644
--- a/include/timebase.h
+++ b/include/timebase.h
@@ -23,6 +23,7 @@
 #define __TIME_H
 
 #include <time.h>
+#include <stdbool.h>
 
 #ifndef __TEST__
 static inline unsigned long mftb(void)
@@ -51,6 +52,16 @@ static inline enum tb_cmpval tb_compare(unsigned long a,
 	return ((long)(b - a)) > 0 ? TB_ABEFOREB : TB_AAFTERB;
 }
 
+static inline bool time_after(unsigned long target)
+{
+	return tb_compare(mftb(), target) == TB_AAFTERB;
+}
+
+static inline bool time_before(unsigned long target)
+{
+	return tb_compare(mftb(), target) == TB_ABEFOREB;
+}
+
 /* Architected timebase */
 static const unsigned long tb_hz = 512000000;
 
diff --git a/libflash/errors.h b/libflash/errors.h
index 99dcfc2..3381f48 100644
--- a/libflash/errors.h
+++ b/libflash/errors.h
@@ -31,5 +31,6 @@
 #define FLASH_ERR_CTRL_TIMEOUT		13
 #define FLASH_ERR_ECC_INVALID		14
 #define FLASH_ERR_BAD_READ		15
+#define FLASH_ERR_OFFLINE		16
 
 #endif /* __LIBFLASH_ERRORS_H */
diff --git a/libflash/libflash.c b/libflash/libflash.c
index d3e594a..ec93284 100644
--- a/libflash/libflash.c
+++ b/libflash/libflash.c
@@ -286,7 +286,7 @@ int flash_erase_chip(struct flash_chip *c)
 		return FLASH_ERR_CHIP_ER_NOT_SUPPORTED;
 
 	FL_DBG("LIBFLASH: Erasing chip...\n");
-	
+
 	/* Use controller erase if supported */
 	if (ct->erase)
 		return ct->erase(ct, 0, 0xffffffff);




More information about the Skiboot mailing list