* [PATCH 2/5] RTC/i.MX/DryIce: add the unit recovery code
2015-04-14 9:08 [PATCH 1/5] RTC/i.MX/DryIce: add some background info about the states the machine can be in Juergen Borleis
@ 2015-04-14 9:08 ` Juergen Borleis
2015-04-14 9:08 ` [PATCH 3/5] RTC/i.MX/DryIce: monitor a security violation at runtime Juergen Borleis
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Juergen Borleis @ 2015-04-14 9:08 UTC (permalink / raw)
To: linux-arm-kernel
This code is requiered to recover the unit from a security violation.
Hopefully this code can recover the unit from a hardware related invalid
state as well.
Signed-off-by: Juergen Borleis <jbe@pengutronix.de>
Signed-off-by: Robert Schwebel <rsc@pengutronix.de>
[rsc: got NDA clearance from Freescale]
---
drivers/rtc/rtc-imxdi.c | 333 ++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 295 insertions(+), 38 deletions(-)
diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
index 8750477..f8abf2b 100644
--- a/drivers/rtc/rtc-imxdi.c
+++ b/drivers/rtc/rtc-imxdi.c
@@ -172,6 +172,296 @@ struct imxdi_dev {
* task, we bring back this unit into life.
*/
+/* do a write into the unit without interrupt support */
+static void di_write_busy_wait(const struct imxdi_dev *imxdi, u32 val,
+ unsigned reg)
+{
+ /* do the register write */
+ __raw_writel(val, imxdi->ioaddr + reg);
+
+ /*
+ * now it takes four 32,768 kHz clock cycles to take
+ * the change into effect = 122 us
+ */
+ udelay(200);
+}
+
+static void di_report_tamper_info(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dtcr;
+
+ dtcr = __raw_readl(imxdi->ioaddr + DTCR);
+
+ dev_emerg(&imxdi->pdev->dev, "!! DryIce tamper event detected !!\n");
+ /* the following flags force a transition into the "FAILURE STATE" */
+ if (dsr & DSR_VTD) {
+ dev_emerg(&imxdi->pdev->dev, "!! Voltage Tamper event\n");
+ if (!(dtcr & DTCR_VTE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But Voltage Tamper detection wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_CTD) {
+ dev_emerg(&imxdi->pdev->dev, "!! 32768 Hz Clock Tamper Event\n");
+ if (!(dtcr & DTCR_CTE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But Clock Tamper detection wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_TTD) {
+ dev_emerg(&imxdi->pdev->dev, "!! Temperature Tamper Event\n");
+ if (!(dtcr & DTCR_TTE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But Temperature Tamper detection wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_SAD) {
+ dev_emerg(&imxdi->pdev->dev, "!! Secure Controller Alarm Event\n");
+ if (!(dtcr & DTCR_SAIE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But Secure Controller Alarm detection wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_EBD) {
+ dev_emerg(&imxdi->pdev->dev, "!! External Boot Tamper Event\n");
+ if (!(dtcr & DTCR_EBE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But External Boot detection wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_ETAD) {
+ dev_emerg(&imxdi->pdev->dev, "!! External Tamper A Event\n");
+ if (!(dtcr & DTCR_ETAE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But External Tamper A wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_ETBD) {
+ dev_emerg(&imxdi->pdev->dev, "!! External Tamper B Event\n");
+ if (!(dtcr & DTCR_ETBE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But External Tamper B wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_WTD) {
+ dev_emerg(&imxdi->pdev->dev, "!! Wire-mesh Tamper Event\n");
+ if (!(dtcr & DTCR_WTE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But wire-mesh Tamper detection wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_MCO) {
+ dev_emerg(&imxdi->pdev->dev, "!! Monotonic-counter Overflow Event\n");
+ if (!(dtcr & DTCR_MOE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But Monotonic-counter Overflow detection wasn't enabled. False positive?\n");
+ }
+ if (dsr & DSR_TCO) {
+ dev_emerg(&imxdi->pdev->dev, "!! Timer-counter Overflow Event\n");
+ if (!(dtcr & DTCR_TOE))
+ dev_emerg(&imxdi->pdev->dev,
+ "!! But Timer-counter Overflow detection wasn't enabled. False positive?\n");
+ }
+}
+
+static void di_what_is_to_be_done(struct imxdi_dev *imxdi,
+ const char *power_supply)
+{
+ dev_emerg(&imxdi->pdev->dev, " Please cycle the %s power supply in order to get the DryIce unit working again\n",
+ power_supply);
+}
+
+static int di_handle_failure_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dcr;
+
+ dev_dbg(&imxdi->pdev->dev, "DSR register reports: %08X\n", dsr);
+
+ /* report the cause */
+ di_report_tamper_info(imxdi, dsr);
+
+ dcr = __raw_readl(imxdi->ioaddr + DCR);
+
+ if (dcr & DCR_FSHL) {
+ /* we are out of luck */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+ /*
+ * with the next SYSTEM POR we will transit from the "FAILURE STATE"
+ * into the "NON-VALID STATE" + "FAILURE STATE"
+ */
+ di_what_is_to_be_done(imxdi, "main");
+
+ return -ENODEV;
+}
+
+static int di_handle_valid_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ /* initialize alarm */
+ di_write_busy_wait(imxdi, DCAMR_UNSET, DCAMR);
+ di_write_busy_wait(imxdi, 0, DCALR);
+
+ /* clear alarm flag */
+ if (dsr & DSR_CAF)
+ di_write_busy_wait(imxdi, DSR_CAF, DSR);
+
+ return 0;
+}
+
+static int di_handle_invalid_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dcr, sec;
+
+ /*
+ * lets disable all sources which can force the DryIce unit into
+ * the "FAILURE STATE" for now
+ */
+ di_write_busy_wait(imxdi, 0x00000000, DTCR);
+ /* and lets protect them at runtime from any change */
+ di_write_busy_wait(imxdi, DCR_TDCSL, DCR);
+
+ sec = __raw_readl(imxdi->ioaddr + DTCMR);
+ if (sec != 0)
+ dev_warn(&imxdi->pdev->dev,
+ "The security violation has happend at %u seconds\n",
+ sec);
+ /*
+ * the timer cannot be set/modified if
+ * - the TCHL or TCSL bit is set in DCR
+ */
+ dcr = __raw_readl(imxdi->ioaddr + DCR);
+ if (!(dcr & DCR_TCE)) {
+ if (dcr & DCR_TCHL) {
+ /* we are out of luck */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+ if (dcr & DCR_TCSL) {
+ di_what_is_to_be_done(imxdi, "main");
+ return -ENODEV;
+ }
+
+ }
+ /*
+ * - the timer counter stops/is stopped if
+ * - its overflow flag is set (TCO in DSR)
+ * -> clear overflow bit to make it count again
+ * - NVF is set in DSR
+ * -> clear non-valid bit to make it count again
+ * - its TCE (DCR) is cleared
+ * -> set TCE to make it count
+ * - it was never set before
+ * -> write a time into it (required again if the NVF was set)
+ */
+ /* state handled */
+ di_write_busy_wait(imxdi, DSR_NVF, DSR);
+ /* clear overflow flag */
+ di_write_busy_wait(imxdi, DSR_TCO, DSR);
+ /* enable the counter */
+ di_write_busy_wait(imxdi, dcr | DCR_TCE, DCR);
+ /* set and trigger it to make it count */
+ di_write_busy_wait(imxdi, sec, DTCMR);
+
+ /* now prepare for the valid state */
+ return di_handle_valid_state(imxdi, __raw_readl(imxdi->ioaddr + DSR));
+}
+
+static int di_handle_invalid_and_failure_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dcr;
+
+ /*
+ * now we must first remove the tamper sources in order to get the
+ * device out of the "FAILURE STATE"
+ * To disable any of the following sources we need to modify the DTCR
+ */
+ if (dsr & (DSR_WTD | DSR_ETBD | DSR_ETAD | DSR_EBD | DSR_SAD |
+ DSR_TTD | DSR_CTD | DSR_VTD | DSR_MCO | DSR_TCO)) {
+ dcr = __raw_readl(imxdi->ioaddr + DCR);
+ if (dcr & DCR_TDCHL) {
+ /*
+ * the tamper register is locked. We cannot disable the
+ * tamper detection. The TDCHL can only be reset by a
+ * DRYICE POR, but we cannot force a DRYICE POR in
+ * softwere because we are still in "FAILURE STATE".
+ * We need a DRYICE POR via battery power cycling....
+ */
+ /*
+ * out of luck!
+ * we cannot disable them without a DRYICE POR
+ */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+ if (dcr & DCR_TDCSL) {
+ /* a soft lock can be removed by a SYSTEM POR */
+ di_what_is_to_be_done(imxdi, "main");
+ return -ENODEV;
+ }
+ }
+
+ /* disable all sources */
+ di_write_busy_wait(imxdi, 0x00000000, DTCR);
+
+ /* clear the status bits now */
+ di_write_busy_wait(imxdi, dsr & (DSR_WTD | DSR_ETBD | DSR_ETAD |
+ DSR_EBD | DSR_SAD | DSR_TTD | DSR_CTD | DSR_VTD |
+ DSR_MCO | DSR_TCO), DSR);
+
+ dsr = __raw_readl(imxdi->ioaddr + DSR);
+ if ((dsr & ~(DSR_NVF | DSR_SVF | DSR_WBF | DSR_WNF |
+ DSR_WCF | DSR_WEF)) != 0)
+ dev_warn(&imxdi->pdev->dev,
+ "There are still some sources of pain in DSR: %08x!\n",
+ dsr & ~(DSR_NVF | DSR_SVF | DSR_WBF | DSR_WNF |
+ DSR_WCF | DSR_WEF));
+
+ /*
+ * now we are trying to clear the "Security-violation flag" to
+ * get the DryIce out of this state
+ */
+ di_write_busy_wait(imxdi, DSR_SVF, DSR);
+
+ /* success? */
+ dsr = __raw_readl(imxdi->ioaddr + DSR);
+ if (dsr & DSR_SVF) {
+ dev_crit(&imxdi->pdev->dev,
+ "Cannot clear the security violation flag. We are ending up in an endless loop!\n");
+ /* last resourt */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+
+ /*
+ * now we have left the "FAILURE STATE" and ending up in the
+ * "NON-VALID STATE" time to recover everything
+ */
+ return di_handle_invalid_state(imxdi, dsr);
+}
+
+static int di_handle_state(struct imxdi_dev *imxdi)
+{
+ int rc;
+ u32 dsr;
+
+ dsr = __raw_readl(imxdi->ioaddr + DSR);
+
+ switch (dsr & (DSR_NVF | DSR_SVF)) {
+ case DSR_NVF:
+ dev_warn(&imxdi->pdev->dev, "Invalid stated unit detected\n");
+ rc = di_handle_invalid_state(imxdi, dsr);
+ break;
+ case DSR_SVF:
+ dev_warn(&imxdi->pdev->dev, "Failure stated unit detected\n");
+ rc = di_handle_failure_state(imxdi, dsr);
+ break;
+ case DSR_NVF | DSR_SVF:
+ dev_warn(&imxdi->pdev->dev,
+ "Failure+Invalid stated unit detected\n");
+ rc = di_handle_invalid_and_failure_state(imxdi, dsr);
+ break;
+ default:
+ dev_notice(&imxdi->pdev->dev, "Unlocked unit detected\n");
+ rc = di_handle_valid_state(imxdi, dsr);
+ }
+
+ return rc;
+}
+
+
/*
* enable a dryice interrupt
*/
@@ -491,6 +781,11 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
/* mask all interrupts */
__raw_writel(0, imxdi->ioaddr + DIER);
+ rc = di_handle_state(imxdi);
+ if (rc != 0)
+ goto err;
+
+
rc = devm_request_irq(&pdev->dev, imxdi->irq, dryice_norm_irq,
IRQF_SHARED, pdev->name, imxdi);
if (rc) {
@@ -498,44 +793,6 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
goto err;
}
- /* put dryice into valid state */
- if (__raw_readl(imxdi->ioaddr + DSR) & DSR_NVF) {
- rc = di_write_wait(imxdi, DSR_NVF | DSR_SVF, DSR);
- if (rc)
- goto err;
- }
-
- /* initialize alarm */
- rc = di_write_wait(imxdi, DCAMR_UNSET, DCAMR);
- if (rc)
- goto err;
- rc = di_write_wait(imxdi, 0, DCALR);
- if (rc)
- goto err;
-
- /* clear alarm flag */
- if (__raw_readl(imxdi->ioaddr + DSR) & DSR_CAF) {
- rc = di_write_wait(imxdi, DSR_CAF, DSR);
- if (rc)
- goto err;
- }
-
- /* the timer won't count if it has never been written to */
- if (__raw_readl(imxdi->ioaddr + DTCMR) == 0) {
- rc = di_write_wait(imxdi, 0, DTCMR);
- if (rc)
- goto err;
- }
-
- /* start keeping time */
- if (!(__raw_readl(imxdi->ioaddr + DCR) & DCR_TCE)) {
- rc = di_write_wait(imxdi,
- __raw_readl(imxdi->ioaddr + DCR) | DCR_TCE,
- DCR);
- if (rc)
- goto err;
- }
-
platform_set_drvdata(pdev, imxdi);
imxdi->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
&dryice_rtc_ops, THIS_MODULE);
--
2.1.4
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 5/5] RTC/i.MX/DryIce: prepare to force a security violation
2015-04-14 9:08 [PATCH 1/5] RTC/i.MX/DryIce: add some background info about the states the machine can be in Juergen Borleis
` (2 preceding siblings ...)
2015-04-14 9:08 ` [PATCH 4/5] RTC/i.MX/DryIce: when locked, do not fail silently Juergen Borleis
@ 2015-04-14 9:08 ` Juergen Borleis
2015-04-14 9:38 ` [PATCH 1/5] RTC/i.MX/DryIce: add some background info about the states the machine can be in Juergen Borleis
4 siblings, 0 replies; 7+ messages in thread
From: Juergen Borleis @ 2015-04-14 9:08 UTC (permalink / raw)
To: linux-arm-kernel
In order to test the new driver we need some mechanism to force a transition
into the security violation state. Two DryIce internal timers can be used
for this purpose. Both have an overflow feature which forces this transition
and can be triggered automatically (timer) or manually (monotonic via reading
the RTC time).
Note: this change is intended for development only to test the driver's
recovery capabilities. It is useless for regular use of the DryIce unit.
Signed-off-by: Juergen Borleis <jbe@pengutronix.de>
Signed-off-by: Robert Schwebel <rsc@pengutronix.de>
[rsc: got NDA clearance from Freescale]
---
drivers/rtc/rtc-imxdi.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 75 insertions(+), 1 deletion(-)
diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
index de1a2e4..cbc1ebe 100644
--- a/drivers/rtc/rtc-imxdi.c
+++ b/drivers/rtc/rtc-imxdi.c
@@ -29,6 +29,9 @@
* not supported by the hardware.
*/
+#undef FORCE_VIOLATION
+# define USE_TIMER_VIOLATION
+
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/delay.h>
@@ -288,6 +291,69 @@ static int di_handle_failure_state(struct imxdi_dev *imxdi, u32 dsr)
return -ENODEV;
}
+/*
+ * Two types of security violations we can force:
+ *
+ * - regular timer counter overflow:
+ * - set it up to 0xfffffff0
+ * - enable its counting
+ * - set TCSL bit to prevent any further change
+ * - let the overflow happen which forces a security violation
+ *
+ * - monotonic counter overflow:
+ * - set it up to 0xfffffffc
+ * - enable its counting (MCE = 1)
+ * - set MCSL bit to prevent any further change
+ * - write 4 times to the monotonic counter register
+ */
+static void di_prepare_security_violation(struct imxdi_dev *imxdi)
+{
+ u32 dcr = __raw_readl(imxdi->ioaddr + DCR);
+ u32 dtcr = __raw_readl(imxdi->ioaddr + DTCR);
+
+#ifndef USE_TIMER_VIOLATION /* monotonic counter variant */
+
+ /* clear the MCO flag, otherwhise it cannot be programmed again */
+ di_write_busy_wait(imxdi, DSR_MCO, DSR);
+
+ /* stop monotonic-counter to be able to set its absolute value */
+ dcr &= ~DCR_MCE;
+ di_write_busy_wait(imxdi, dcr, DCR);
+
+ /* set a new value close to its overflow */
+ di_write_busy_wait(imxdi, 0xfffffff8, DMCR);
+
+ /* enable monotonic-counter to increment on each write */
+ dcr |= DCR_MCE;
+ di_write_busy_wait(imxdi, dcr, DCR);
+
+ /* lock this setting */
+ dcr |= DCR_MCSL;
+ di_write_busy_wait(imxdi, dcr, DCR);
+
+ /* let this overflow force the transition into the failure state */
+ di_write_busy_wait(imxdi, dtcr | DTCR_MOE, DTCR);
+#else /* timer counter variant */
+ /* clear the TCO flag, otherwhise it cannot be programmed again */
+ di_write_busy_wait(imxdi, DSR_TCO, DSR);
+
+ /* set a new value close to its overflow (16 seconds) */
+ di_write_busy_wait(imxdi, 0x00000000, DTCLR);
+ di_write_busy_wait(imxdi, 0xfffffff0, DTCMR);
+
+ /* enable timer-counter to increment on each write */
+ dcr |= DCR_TCE;
+ di_write_busy_wait(imxdi, dcr, DCR);
+
+ /* lock this setting */
+ dcr |= DCR_TCSL;
+ di_write_busy_wait(imxdi, dcr, DCR);
+
+ /* let this overflow force the transition into the failure state */
+ di_write_busy_wait(imxdi, dtcr | DTCR_TOE, DTCR);
+#endif
+}
+
static int di_handle_valid_state(struct imxdi_dev *imxdi, u32 dsr)
{
/* initialize alarm */
@@ -305,6 +371,7 @@ static int di_handle_invalid_state(struct imxdi_dev *imxdi, u32 dsr)
{
u32 dcr, sec;
+#ifndef FORCE_VIOLATION
/*
* lets disable all sources which can force the DryIce unit into
* the "FAILURE STATE" for now
@@ -312,7 +379,7 @@ static int di_handle_invalid_state(struct imxdi_dev *imxdi, u32 dsr)
di_write_busy_wait(imxdi, 0x00000000, DTCR);
/* and lets protect them at runtime from any change */
di_write_busy_wait(imxdi, DCR_TDCSL, DCR);
-
+#endif
sec = __raw_readl(imxdi->ioaddr + DTCMR);
if (sec != 0)
dev_warn(&imxdi->pdev->dev,
@@ -571,6 +638,10 @@ static int dryice_rtc_read_time(struct device *dev, struct rtc_time *tm)
now = __raw_readl(imxdi->ioaddr + DTCMR);
rtc_time_to_tm(now, tm);
+#if defined(FORCE_VIOLATION) && !defined(USE_TIMER_VIOLATION)
+ /* don't use interrupts here */
+ di_write_busy_wait(imxdi, 0, DMCR);
+#endif
return 0;
}
@@ -840,6 +911,9 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
goto err;
}
+#ifdef FORCE_VIOLATION
+ di_prepare_security_violation(imxdi);
+#endif
return 0;
err:
--
2.1.4
^ permalink raw reply related [flat|nested] 7+ messages in thread