From patchwork Tue Apr 14 09:11:53 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Juergen Borleis X-Patchwork-Id: 6214451 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 80B0EBF4A6 for ; Tue, 14 Apr 2015 09:19:28 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 2CEF2201EC for ; Tue, 14 Apr 2015 09:19:27 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id C0716200CF for ; Tue, 14 Apr 2015 09:19:25 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Yhwxh-0005r8-4z; Tue, 14 Apr 2015 09:16:53 +0000 Received: from metis.ext.pengutronix.de ([2001:6f8:1178:4:290:27ff:fe1d:cc33]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1YhwtN-0001eu-9U for linux-arm-kernel@lists.infradead.org; Tue, 14 Apr 2015 09:12:35 +0000 Received: from dude.hi.pengutronix.de ([2001:67c:670:100:1d::7]) by metis.ext.pengutronix.de with esmtp (Exim 4.80) (envelope-from ) id 1Yhwsy-0004fT-3G; Tue, 14 Apr 2015 11:12:00 +0200 Received: from jbe by dude.hi.pengutronix.de with local (Exim 4.84) (envelope-from ) id 1Yhwsx-0005br-HA; Tue, 14 Apr 2015 11:11:59 +0200 From: Juergen Borleis To: linux-kernel@vger.kernel.org Subject: [PATCH 2/5] RTC/i.MX/DryIce: add the unit recovery code Date: Tue, 14 Apr 2015 11:11:53 +0200 Message-Id: <1429002716-19821-3-git-send-email-jbe@pengutronix.de> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1429002716-19821-1-git-send-email-jbe@pengutronix.de> References: <1429002716-19821-1-git-send-email-jbe@pengutronix.de> X-SA-Exim-Connect-IP: 2001:67c:670:100:1d::7 X-SA-Exim-Mail-From: jbe@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-arm-kernel@lists.infradead.org X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150414_021225_820476_409A9C7C X-CRM114-Status: GOOD ( 25.28 ) X-Spam-Score: -0.0 (/) Cc: linux-arm-kernel@lists.infradead.org, Alessandro Zummo , Robert Schwebel , kernel@pengutronix.de, rtc-linux@googlegroups.com X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 Signed-off-by: Robert Schwebel [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);