Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7e9bbe0 authored by Juergen Borleis's avatar Juergen Borleis Committed by Alexandre Belloni
Browse files

rtc: imxdi: 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: default avatarJuergen Borleis <jbe@pengutronix.de>
Signed-off-by: default avatarRobert Schwebel <rsc@pengutronix.de>
[rsc: got NDA clearance from Freescale]
Signed-off-by: default avatarAlexandre Belloni <alexandre.belloni@free-electrons.com>
parent 3ba3fab7
Loading
Loading
Loading
Loading
+279 −38
Original line number Original line Diff line number Diff line
@@ -172,6 +172,281 @@ struct imxdi_dev {
 * task, we bring back this unit into life.
 * task, we bring back this unit into life.
 */
 */


/*
 * 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
 * enable a dryice interrupt
 */
 */
@@ -491,6 +766,10 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
	/* mask all interrupts */
	/* mask all interrupts */
	writel(0, imxdi->ioaddr + DIER);
	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,
	rc = devm_request_irq(&pdev->dev, imxdi->irq, dryice_norm_irq,
			IRQF_SHARED, pdev->name, imxdi);
			IRQF_SHARED, pdev->name, imxdi);
	if (rc) {
	if (rc) {
@@ -498,44 +777,6 @@ static int __init dryice_rtc_probe(struct platform_device *pdev)
		goto err;
		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);
	platform_set_drvdata(pdev, imxdi);
	imxdi->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
	imxdi->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
				  &dryice_rtc_ops, THIS_MODULE);
				  &dryice_rtc_ops, THIS_MODULE);