2015-04-27 14:00:04

by Juergen Borleis

[permalink] [raw]
Subject: [PATCHv2] RTC/i.MX/DryICE: add recovery routines to the driver

The built-in RTC unit on some i.MX SoCs isn't an RTC only. It is also a tamper
monitor unit which can keep some (secret) keys. When it does its tamper
detection job and a security violation is detected, the whole DryICE unit
including the real-time counter locks completely. In this state the whole unit
is completely useless. The only way to bring it out of this locked state is a
power cylce with a POR (most of the case) or additionally a battery power
cycle which includes the loss of the secret keys.
At the next boot time some flags signals the security violation and a specific
register access sequence must be done to finaly bring this unit into life
again. Until this is done, there is no way to use it again as an RTC.

But also without any enabled tamper detection sometimes this unit tends to
lock. And in this case the same steps must be done to bring it into life
again.

The current implementation of the DryIce driver isn't able to unlock the
device successfully in the case it is locked somehow. Only a full power cycle
including *battery power* can help in this case.

The attached change set adds the required routines to be able to unlock the
DryIce unit in the case the driver detects a locked unit. This includes
unlocking it if it is locked by accident or malfunction and not by a real
security violation.

The last patch of this series is for reference only and should not be part
of the kernel. It just adds some code to force a locked DryIce unit to check
if the new routines are able to unlock it again. This code was required
because I had no hardware which really uses the tamper detection features of
this unit.

This is the 2nd version of the patch series. Hopefully I addressed all comments
from Alexandre.

In this version I added a new patch which replaces all __raw* register functions
as recommended by Alexandre.

Comments are welcome.

jbe


2015-04-27 14:01:10

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 1/6] RTC/i.MX/DryIce: avoid the __raw* register access functions

Be independent of the endianness of the kernel.

Signed-off-by: Juergen Borleis <[email protected]>
---
drivers/rtc/rtc-imxdi.c | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
index c666eab..0c2a064 100644
--- a/drivers/rtc/rtc-imxdi.c
+++ b/drivers/rtc/rtc-imxdi.c
@@ -137,7 +137,7 @@ static void di_int_enable(struct imxdi_dev *imxdi, u32 intr)
unsigned long flags;

spin_lock_irqsave(&imxdi->irq_lock, flags);
- __raw_writel(__raw_readl(imxdi->ioaddr + DIER) | intr,
+ writel(readl(imxdi->ioaddr + DIER) | intr,
imxdi->ioaddr + DIER);
spin_unlock_irqrestore(&imxdi->irq_lock, flags);
}
@@ -150,7 +150,7 @@ static void di_int_disable(struct imxdi_dev *imxdi, u32 intr)
unsigned long flags;

spin_lock_irqsave(&imxdi->irq_lock, flags);
- __raw_writel(__raw_readl(imxdi->ioaddr + DIER) & ~intr,
+ writel(readl(imxdi->ioaddr + DIER) & ~intr,
imxdi->ioaddr + DIER);
spin_unlock_irqrestore(&imxdi->irq_lock, flags);
}
@@ -169,11 +169,11 @@ static void clear_write_error(struct imxdi_dev *imxdi)
dev_warn(&imxdi->pdev->dev, "WARNING: Register write error!\n");

/* clear the write error flag */
- __raw_writel(DSR_WEF, imxdi->ioaddr + DSR);
+ writel(DSR_WEF, imxdi->ioaddr + DSR);

/* wait for it to take effect */
for (cnt = 0; cnt < 1000; cnt++) {
- if ((__raw_readl(imxdi->ioaddr + DSR) & DSR_WEF) == 0)
+ if ((readl(imxdi->ioaddr + DSR) & DSR_WEF) == 0)
return;
udelay(10);
}
@@ -201,7 +201,7 @@ static int di_write_wait(struct imxdi_dev *imxdi, u32 val, int reg)
imxdi->dsr = 0;

/* do the register write */
- __raw_writel(val, imxdi->ioaddr + reg);
+ writel(val, imxdi->ioaddr + reg);

/* wait for the write to finish */
ret = wait_event_interruptible_timeout(imxdi->write_wait,
@@ -235,7 +235,7 @@ static int dryice_rtc_read_time(struct device *dev, struct rtc_time *tm)
struct imxdi_dev *imxdi = dev_get_drvdata(dev);
unsigned long now;

- now = __raw_readl(imxdi->ioaddr + DTCMR);
+ now = readl(imxdi->ioaddr + DTCMR);
rtc_time_to_tm(now, tm);

return 0;
@@ -280,17 +280,17 @@ static int dryice_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
struct imxdi_dev *imxdi = dev_get_drvdata(dev);
u32 dcamr;

- dcamr = __raw_readl(imxdi->ioaddr + DCAMR);
+ dcamr = readl(imxdi->ioaddr + DCAMR);
rtc_time_to_tm(dcamr, &alarm->time);

/* alarm is enabled if the interrupt is enabled */
- alarm->enabled = (__raw_readl(imxdi->ioaddr + DIER) & DIER_CAIE) != 0;
+ alarm->enabled = (readl(imxdi->ioaddr + DIER) & DIER_CAIE) != 0;

/* don't allow the DSR read to mess up DSR_WCF */
mutex_lock(&imxdi->write_mutex);

/* alarm is pending if the alarm flag is set */
- alarm->pending = (__raw_readl(imxdi->ioaddr + DSR) & DSR_CAF) != 0;
+ alarm->pending = (readl(imxdi->ioaddr + DSR) & DSR_CAF) != 0;

mutex_unlock(&imxdi->write_mutex);

@@ -312,7 +312,7 @@ static int dryice_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
return rc;

/* don't allow setting alarm in the past */
- now = __raw_readl(imxdi->ioaddr + DTCMR);
+ now = readl(imxdi->ioaddr + DTCMR);
if (alarm_time < now)
return -EINVAL;

@@ -346,7 +346,7 @@ static irqreturn_t dryice_norm_irq(int irq, void *dev_id)
u32 dsr, dier;
irqreturn_t rc = IRQ_NONE;

- dier = __raw_readl(imxdi->ioaddr + DIER);
+ dier = readl(imxdi->ioaddr + DIER);

/* handle write complete and write error cases */
if (dier & DIER_WCIE) {
@@ -357,7 +357,7 @@ static irqreturn_t dryice_norm_irq(int irq, void *dev_id)
return rc;

/* DSR_WCF clears itself on DSR read */
- dsr = __raw_readl(imxdi->ioaddr + DSR);
+ dsr = readl(imxdi->ioaddr + DSR);
if (dsr & (DSR_WCF | DSR_WEF)) {
/* mask the interrupt */
di_int_disable(imxdi, DIER_WCIE);
@@ -373,7 +373,7 @@ static irqreturn_t dryice_norm_irq(int irq, void *dev_id)
/* handle the alarm case */
if (dier & DIER_CAIE) {
/* DSR_WCF clears itself on DSR read */
- dsr = __raw_readl(imxdi->ioaddr + DSR);
+ dsr = readl(imxdi->ioaddr + DSR);
if (dsr & DSR_CAF) {
/* mask the interrupt */
di_int_disable(imxdi, DIER_CAIE);
@@ -446,7 +446,7 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
*/

/* mask all interrupts */
- __raw_writel(0, imxdi->ioaddr + DIER);
+ writel(0, imxdi->ioaddr + DIER);

rc = devm_request_irq(&pdev->dev, imxdi->irq, dryice_norm_irq,
IRQF_SHARED, pdev->name, imxdi);
@@ -456,7 +456,7 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
}

/* put dryice into valid state */
- if (__raw_readl(imxdi->ioaddr + DSR) & DSR_NVF) {
+ if (readl(imxdi->ioaddr + DSR) & DSR_NVF) {
rc = di_write_wait(imxdi, DSR_NVF | DSR_SVF, DSR);
if (rc)
goto err;
@@ -471,23 +471,23 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
goto err;

/* clear alarm flag */
- if (__raw_readl(imxdi->ioaddr + DSR) & DSR_CAF) {
+ if (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) {
+ if (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)) {
+ if (!(readl(imxdi->ioaddr + DCR) & DCR_TCE)) {
rc = di_write_wait(imxdi,
- __raw_readl(imxdi->ioaddr + DCR) | DCR_TCE,
+ readl(imxdi->ioaddr + DCR) | DCR_TCE,
DCR);
if (rc)
goto err;
@@ -516,7 +516,7 @@ static int __exit dryice_rtc_remove(struct platform_device *pdev)
flush_work(&imxdi->work);

/* mask all interrupts */
- __raw_writel(0, imxdi->ioaddr + DIER);
+ writel(0, imxdi->ioaddr + DIER);

clk_disable_unprepare(imxdi->clk);

--
2.1.4

2015-04-27 14:00:07

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 2/6] RTC/i.MX/DryIce: add some background info about the states the machine can be in

Signed-off-by: Juergen Borleis <[email protected]>
Signed-off-by: Robert Schwebel <[email protected]>
[rsc: got NDA clearance from Freescale]
---
drivers/rtc/rtc-imxdi.c | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)

diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
index 0c2a064..6db8d1c 100644
--- a/drivers/rtc/rtc-imxdi.c
+++ b/drivers/rtc/rtc-imxdi.c
@@ -129,6 +129,49 @@ struct imxdi_dev {
struct work_struct work;
};

+/* Some background:
+ *
+ * The DryIce unit is a complex security/tamper monitor device. To be able do
+ * its job in a useful manner it runs a bigger statemachine to bring it into
+ * security/tamper failure state and once again to bring it out of this state.
+ *
+ * This unit can be in one of three states:
+ *
+ * - "NON-VALID STATE"
+ * always after the battery power was removed
+ * - "FAILURE STATE"
+ * if one of the enabled security events has happened
+ * - "VALID STATE"
+ * if the unit works as expected
+ *
+ * Everything stops when the unit enters the failure state including the RTC
+ * counter (to be able to detect the time the security event happened).
+ *
+ * The following events (when enabled) let the DryIce unit enter the failure
+ * state:
+ *
+ * - wire-mesh-tamper detect
+ * - external tamper B detect
+ * - external tamper A detect
+ * - temperature tamper detect
+ * - clock tamper detect
+ * - voltage tamper detect
+ * - RTC counter overflow
+ * - monotonic counter overflow
+ * - external boot
+ *
+ * If we find the DryIce unit in "FAILURE STATE" and the TDCHL cleared, we
+ * can only detect this state. In this case the unit is completely locked and
+ * must force a second "SYSTEM POR" to bring the DryIce into the
+ * "NON-VALID STATE" + "FAILURE STATE" where a recovery is possible.
+ * If the TDCHL is set in the "FAILURE STATE" we are out of luck. In this case
+ * a battery power cycle is required.
+ *
+ * In the "NON-VALID STATE" + "FAILURE STATE" we can clear the "FAILURE STATE"
+ * and recover the DryIce unit. By clearing the "NON-VALID STATE" as the last
+ * task, we bring back this unit into life.
+ */
+
/*
* enable a dryice interrupt
*/
--
2.1.4

2015-04-27 14:01:35

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 3/6] RTC/i.MX/DryIce: add the unit recovery code

This code is required 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 <[email protected]>
Signed-off-by: Robert Schwebel <[email protected]>
[rsc: got NDA clearance from Freescale]
---
drivers/rtc/rtc-imxdi.c | 317 ++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 279 insertions(+), 38 deletions(-)

diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
index 6db8d1c..f0c8319 100644
--- a/drivers/rtc/rtc-imxdi.c
+++ b/drivers/rtc/rtc-imxdi.c
@@ -173,6 +173,281 @@ struct imxdi_dev {
*/

/*
+ * Do a write into the unit without interrupt support.
+ * We do not need to check the WEF here, because the only reason this kind of
+ * write error can happen is if we write to the unit twice within the 122 us
+ * interval. This cannot happen, since we are using this function only while
+ * setting up the unit.
+ */
+static void di_write_busy_wait(const struct imxdi_dev *imxdi, u32 val,
+ unsigned reg)
+{
+ /* do the register write */
+ writel(val, imxdi->ioaddr + reg);
+
+ /*
+ * now it takes four 32,768 kHz clock cycles to take
+ * the change into effect = 122 us
+ */
+ usleep_range(130, 200);
+}
+
+static void di_report_tamper_info(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dtcr;
+
+ dtcr = 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, "%sVoltage Tamper Event\n",
+ dtcr & DTCR_VTE ? "" : "Spurious ");
+
+ if (dsr & DSR_CTD)
+ dev_emerg(&imxdi->pdev->dev, "%s32768 Hz Clock Tamper Event\n",
+ dtcr & DTCR_CTE ? "" : "Spurious ");
+
+ if (dsr & DSR_TTD)
+ dev_emerg(&imxdi->pdev->dev, "%sTemperature Tamper Event\n",
+ dtcr & DTCR_TTE ? "" : "Spurious ");
+
+ if (dsr & DSR_SAD)
+ dev_emerg(&imxdi->pdev->dev,
+ "%sSecure Controller Alarm Event\n",
+ dtcr & DTCR_SAIE ? "" : "Spurious ");
+
+ if (dsr & DSR_EBD)
+ dev_emerg(&imxdi->pdev->dev, "%sExternal Boot Tamper Event\n",
+ dtcr & DTCR_EBE ? "" : "Spurious ");
+
+ if (dsr & DSR_ETAD)
+ dev_emerg(&imxdi->pdev->dev, "%sExternal Tamper A Event\n",
+ dtcr & DTCR_ETAE ? "" : "Spurious ");
+
+ if (dsr & DSR_ETBD)
+ dev_emerg(&imxdi->pdev->dev, "%sExternal Tamper B Event\n",
+ dtcr & DTCR_ETBE ? "" : "Spurious ");
+
+ if (dsr & DSR_WTD)
+ dev_emerg(&imxdi->pdev->dev, "%sWire-mesh Tamper Event\n",
+ dtcr & DTCR_WTE ? "" : "Spurious ");
+
+ if (dsr & DSR_MCO)
+ dev_emerg(&imxdi->pdev->dev,
+ "%sMonotonic-counter Overflow Event\n",
+ dtcr & DTCR_MOE ? "" : "Spurious ");
+
+ if (dsr & DSR_TCO)
+ dev_emerg(&imxdi->pdev->dev, "%sTimer-counter Overflow Event\n",
+ dtcr & DTCR_TOE ? "" : "Spurious ");
+}
+
+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/RTC 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 = 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 = 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 = 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 = 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 = 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 resort */
+ 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 = 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
*/
static void di_int_enable(struct imxdi_dev *imxdi, u32 intr)
@@ -491,6 +766,10 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
/* mask all interrupts */
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 +777,6 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
goto err;
}

- /* put dryice into valid state */
- if (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 (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 (readl(imxdi->ioaddr + DTCMR) == 0) {
- rc = di_write_wait(imxdi, 0, DTCMR);
- if (rc)
- goto err;
- }
-
- /* start keeping time */
- if (!(readl(imxdi->ioaddr + DCR) & DCR_TCE)) {
- rc = di_write_wait(imxdi,
- 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

2015-04-27 14:00:09

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 4/6] RTC/i.MX/DryIce: monitor a security violation at runtime

Maybe the unit enters the hardware related state at runtime and not at
system boot time (after a power cycle).

Signed-off-by: Juergen Borleis <[email protected]>
Signed-off-by: Robert Schwebel <[email protected]>
[rsc: got NDA clearance from Freescale]
---
drivers/rtc/rtc-imxdi.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
index f0c8319..8890a62 100644
--- a/drivers/rtc/rtc-imxdi.c
+++ b/drivers/rtc/rtc-imxdi.c
@@ -665,6 +665,25 @@ static irqreturn_t dryice_norm_irq(int irq, void *dev_id)
irqreturn_t rc = IRQ_NONE;

dier = readl(imxdi->ioaddr + DIER);
+ dsr = readl(imxdi->ioaddr + DSR);
+
+ /* handle the security violation event */
+ if (dier & DIER_SVIE) {
+ if (dsr & DSR_SVF) {
+ /*
+ * Disable the interrupt when this kind of event has
+ * happened.
+ * There cannot be more than one event of this type,
+ * because it needs a complex state change
+ * including a main power cycle to get again out of
+ * this state.
+ */
+ di_int_disable(imxdi, DIER_SVIE);
+ /* report the violation */
+ di_report_tamper_info(imxdi, dsr);
+ rc = IRQ_HANDLED;
+ }
+ }

/* handle write complete and write error cases */
if (dier & DIER_WCIE) {
@@ -675,7 +694,6 @@ static irqreturn_t dryice_norm_irq(int irq, void *dev_id)
return rc;

/* DSR_WCF clears itself on DSR read */
- dsr = readl(imxdi->ioaddr + DSR);
if (dsr & (DSR_WCF | DSR_WEF)) {
/* mask the interrupt */
di_int_disable(imxdi, DIER_WCIE);
@@ -691,7 +709,6 @@ static irqreturn_t dryice_norm_irq(int irq, void *dev_id)
/* handle the alarm case */
if (dier & DIER_CAIE) {
/* DSR_WCF clears itself on DSR read */
- dsr = readl(imxdi->ioaddr + DSR);
if (dsr & DSR_CAF) {
/* mask the interrupt */
di_int_disable(imxdi, DIER_CAIE);
--
2.1.4

2015-04-27 14:00:12

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 5/6] RTC/i.MX/DryIce: when locked, do not fail silently

If the DryICE unit is locked it is impossibe to set the time. Provide an
error message for this case.

Signed-off-by: Juergen Borleis <[email protected]>
Signed-off-by: Robert Schwebel <[email protected]>
[rsc: got NDA clearance from Freescale]
---
drivers/rtc/rtc-imxdi.c | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
index 8890a62..46bf014 100644
--- a/drivers/rtc/rtc-imxdi.c
+++ b/drivers/rtc/rtc-imxdi.c
@@ -566,14 +566,35 @@ static int dryice_rtc_read_time(struct device *dev, struct rtc_time *tm)
static int dryice_rtc_set_mmss(struct device *dev, unsigned long secs)
{
struct imxdi_dev *imxdi = dev_get_drvdata(dev);
+ u32 dcr, dsr;
int rc;

+ dcr = readl(imxdi->ioaddr + DCR);
+ dsr = readl(imxdi->ioaddr + DSR);
+
+ if (!(dcr & DCR_TCE) || (dsr & DSR_SVF)) {
+ if (dcr & DCR_TCHL) {
+ /* we are even more out of luck */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -EPERM;
+ }
+ if ((dcr & DCR_TCSL) || (dsr & DSR_SVF)) {
+ /* we are out of luck for now */
+ di_what_is_to_be_done(imxdi, "main");
+ return -EPERM;
+ }
+ }
+
/* zero the fractional part first */
rc = di_write_wait(imxdi, 0, DTCLR);
- if (rc == 0)
- rc = di_write_wait(imxdi, secs, DTCMR);
+ if (rc != 0)
+ return rc;

- return rc;
+ rc = di_write_wait(imxdi, secs, DTCMR);
+ if (rc != 0)
+ return rc;
+
+ return di_write_wait(imxdi, readl(imxdi->ioaddr + DCR) | DCR_TCE, DCR);
}

static int dryice_rtc_alarm_irq_enable(struct device *dev,
--
2.1.4

2015-04-27 14:01:13

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 6/6] RTC/i.MX/DryIce: prepare to force a security violation

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 <[email protected]>
Signed-off-by: Robert Schwebel <[email protected]>
[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 46bf014..be4487c 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>
@@ -275,6 +278,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 = readl(imxdi->ioaddr + DCR);
+ u32 dtcr = 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 */
@@ -292,6 +358,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
@@ -299,7 +366,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 = readl(imxdi->ioaddr + DTCMR);
if (sec != 0)
dev_warn(&imxdi->pdev->dev,
@@ -556,6 +623,10 @@ static int dryice_rtc_read_time(struct device *dev, struct rtc_time *tm)
now = 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;
}

@@ -823,6 +894,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

2015-05-03 15:12:17

by Alexandre Belloni

[permalink] [raw]
Subject: Re: [rtc-linux] [PATCHv2] RTC/i.MX/DryICE: add recovery routines to the driver

Hi,

On 27/04/2015 at 15:59:46 +0200, Juergen Borleis wrote :
> The built-in RTC unit on some i.MX SoCs isn't an RTC only. It is also a tamper
> monitor unit which can keep some (secret) keys. When it does its tamper
> detection job and a security violation is detected, the whole DryICE unit
> including the real-time counter locks completely. In this state the whole unit
> is completely useless. The only way to bring it out of this locked state is a
> power cylce with a POR (most of the case) or additionally a battery power
> cycle which includes the loss of the secret keys.
> At the next boot time some flags signals the security violation and a specific
> register access sequence must be done to finaly bring this unit into life
> again. Until this is done, there is no way to use it again as an RTC.
>
> But also without any enabled tamper detection sometimes this unit tends to
> lock. And in this case the same steps must be done to bring it into life
> again.
>
> The current implementation of the DryIce driver isn't able to unlock the
> device successfully in the case it is locked somehow. Only a full power cycle
> including *battery power* can help in this case.
>
> The attached change set adds the required routines to be able to unlock the
> DryIce unit in the case the driver detects a locked unit. This includes
> unlocking it if it is locked by accident or malfunction and not by a real
> security violation.
>
> The last patch of this series is for reference only and should not be part
> of the kernel. It just adds some code to force a locked DryIce unit to check
> if the new routines are able to unlock it again. This code was required
> because I had no hardware which really uses the tamper detection features of
> this unit.
>
> This is the 2nd version of the patch series. Hopefully I addressed all comments
> from Alexandre.
>
> In this version I added a new patch which replaces all __raw* register functions
> as recommended by Alexandre.
>
> Comments are welcome.
>

I've applied 1-5 after fixing a few parenthesis alignments you missed.
I've also reworked the commit subject prefix to the more concise "rtc:
imdi:" and you forgot the commit message in patch 2, you can check it
here:
https://github.com/alexandrebelloni/linux/commit/eff76de33878687dc1877f40ac2cc34794f499e0

Tell me if you have any objection.

BTW, I guess your email address has been recycled as patchwork recognize
it has belonging to Juergen Beisert ;)


--
Alexandre Belloni, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com

2015-05-03 15:14:31

by Marc Kleine-Budde

[permalink] [raw]
Subject: Re: [rtc-linux] [PATCHv2] RTC/i.MX/DryICE: add recovery routines to the driver

On 05/03/2015 05:12 PM, Alexandre Belloni wrote:
> BTW, I guess your email address has been recycled as patchwork recognize
> it has belonging to Juergen Beisert ;)

No, Jürgen just got a new name, but stuck to his old email address.

Marc

--
Pengutronix e.K. | Marc Kleine-Budde |
Industrial Linux Solutions | Phone: +49-231-2826-924 |
Vertretung West/Dortmund | Fax: +49-5121-206917-5555 |
Amtsgericht Hildesheim, HRA 2686 | http://www.pengutronix.de |


Attachments:
signature.asc (801.00 B)
OpenPGP digital signature

2015-05-03 15:38:32

by Alexandre Belloni

[permalink] [raw]
Subject: Re: [rtc-linux] [PATCHv2] RTC/i.MX/DryICE: add recovery routines to the driver

On 03/05/2015 at 17:13:54 +0200, Marc Kleine-Budde wrote :
> On 05/03/2015 05:12 PM, Alexandre Belloni wrote:
> > BTW, I guess your email address has been recycled as patchwork recognize
> > it has belonging to Juergen Beisert ;)
>
> No, J?rgen just got a new name, but stuck to his old email address.
>

Ok, thanks for the explanation and sorry for the confusion.


--
Alexandre Belloni, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com