Skip to content

Commit 591c946

Browse files
fancerbp3tk0v
authored andcommitted
EDAC/synopsys: Fix ECC status and IRQ control race condition
The race condition around the ECCCLR register access happens in the IRQ disable method called in the device remove() procedure and in the ECC IRQ handler: 1. Enable IRQ: a. ECCCLR = EN_CE | EN_UE 2. Disable IRQ: a. ECCCLR = 0 3. IRQ handler: a. ECCCLR = CLR_CE | CLR_CE_CNT | CLR_CE | CLR_CE_CNT b. ECCCLR = 0 c. ECCCLR = EN_CE | EN_UE So if the IRQ disabling procedure is called concurrently with the IRQ handler method the IRQ might be actually left enabled due to the statement 3c. The root cause of the problem is that ECCCLR register (which since v3.10a has been called as ECCCTL) has intermixed ECC status data clear flags and the IRQ enable/disable flags. Thus the IRQ disabling (clear EN flags) and handling (write 1 to clear ECC status data) procedures must be serialised around the ECCCTL register modification to prevent the race. So fix the problem described above by adding the spin-lock around the ECCCLR modifications and preventing the IRQ-handler from modifying the IRQs enable flags (there is no point in disabling the IRQ and then re-enabling it again within a single IRQ handler call, see the statements 3a/3b and 3c above). Fixes: f7824de ("EDAC/synopsys: Add support for version 3 of the Synopsys EDAC DDR") Signed-off-by: Serge Semin <[email protected]> Signed-off-by: Borislav Petkov (AMD) <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent dd5a440 commit 591c946

File tree

1 file changed

+37
-13
lines changed

1 file changed

+37
-13
lines changed

drivers/edac/synopsys_edac.c

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <linux/edac.h>
1010
#include <linux/module.h>
1111
#include <linux/platform_device.h>
12+
#include <linux/spinlock.h>
1213
#include <linux/interrupt.h>
1314
#include <linux/of.h>
1415

@@ -299,6 +300,7 @@ struct synps_ecc_status {
299300
/**
300301
* struct synps_edac_priv - DDR memory controller private instance data.
301302
* @baseaddr: Base address of the DDR controller.
303+
* @reglock: Concurrent CSRs access lock.
302304
* @message: Buffer for framing the event specific info.
303305
* @stat: ECC status information.
304306
* @p_data: Platform data.
@@ -313,6 +315,7 @@ struct synps_ecc_status {
313315
*/
314316
struct synps_edac_priv {
315317
void __iomem *baseaddr;
318+
spinlock_t reglock;
316319
char message[SYNPS_EDAC_MSG_SIZE];
317320
struct synps_ecc_status stat;
318321
const struct synps_platform_data *p_data;
@@ -408,7 +411,8 @@ static int zynq_get_error_info(struct synps_edac_priv *priv)
408411
static int zynqmp_get_error_info(struct synps_edac_priv *priv)
409412
{
410413
struct synps_ecc_status *p;
411-
u32 regval, clearval = 0;
414+
u32 regval, clearval;
415+
unsigned long flags;
412416
void __iomem *base;
413417

414418
base = priv->baseaddr;
@@ -452,10 +456,14 @@ static int zynqmp_get_error_info(struct synps_edac_priv *priv)
452456
p->ueinfo.blknr = (regval & ECC_CEADDR1_BLKNR_MASK);
453457
p->ueinfo.data = readl(base + ECC_UESYND0_OFST);
454458
out:
455-
clearval = ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT;
456-
clearval |= ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT;
459+
spin_lock_irqsave(&priv->reglock, flags);
460+
461+
clearval = readl(base + ECC_CLR_OFST) |
462+
ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT |
463+
ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT;
457464
writel(clearval, base + ECC_CLR_OFST);
458-
writel(0x0, base + ECC_CLR_OFST);
465+
466+
spin_unlock_irqrestore(&priv->reglock, flags);
459467

460468
return 0;
461469
}
@@ -515,24 +523,41 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p)
515523

516524
static void enable_intr(struct synps_edac_priv *priv)
517525
{
526+
unsigned long flags;
527+
518528
/* Enable UE/CE Interrupts */
519-
if (priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)
520-
writel(DDR_UE_MASK | DDR_CE_MASK,
521-
priv->baseaddr + ECC_CLR_OFST);
522-
else
529+
if (!(priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)) {
523530
writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK,
524531
priv->baseaddr + DDR_QOS_IRQ_EN_OFST);
525532

533+
return;
534+
}
535+
536+
spin_lock_irqsave(&priv->reglock, flags);
537+
538+
writel(DDR_UE_MASK | DDR_CE_MASK,
539+
priv->baseaddr + ECC_CLR_OFST);
540+
541+
spin_unlock_irqrestore(&priv->reglock, flags);
526542
}
527543

528544
static void disable_intr(struct synps_edac_priv *priv)
529545
{
546+
unsigned long flags;
547+
530548
/* Disable UE/CE Interrupts */
531-
if (priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)
532-
writel(0x0, priv->baseaddr + ECC_CLR_OFST);
533-
else
549+
if (!(priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)) {
534550
writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK,
535551
priv->baseaddr + DDR_QOS_IRQ_DB_OFST);
552+
553+
return;
554+
}
555+
556+
spin_lock_irqsave(&priv->reglock, flags);
557+
558+
writel(0, priv->baseaddr + ECC_CLR_OFST);
559+
560+
spin_unlock_irqrestore(&priv->reglock, flags);
536561
}
537562

538563
/**
@@ -576,8 +601,6 @@ static irqreturn_t intr_handler(int irq, void *dev_id)
576601
/* v3.0 of the controller does not have this register */
577602
if (!(priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR))
578603
writel(regval, priv->baseaddr + DDR_QOS_IRQ_STAT_OFST);
579-
else
580-
enable_intr(priv);
581604

582605
return IRQ_HANDLED;
583606
}
@@ -1357,6 +1380,7 @@ static int mc_probe(struct platform_device *pdev)
13571380
priv = mci->pvt_info;
13581381
priv->baseaddr = baseaddr;
13591382
priv->p_data = p_data;
1383+
spin_lock_init(&priv->reglock);
13601384

13611385
mc_init(mci, pdev);
13621386

0 commit comments

Comments
 (0)