[PATCH bugfix] mtd: gpmi: serialize all the dma operations

Huang Shijie b32955 at freescale.com
Wed Nov 6 03:53:27 EST 2013


[1] The gpmi uses the nand_command_lp to issue the commands to NAND chips.
    It will issue a DMA operation when it handles a NAND_CMD_NONE control
    command. So when we read a page(NAND_CMD_READ0) from the NAND, we may send
    two DMA operations back-to-back.

    If we do not serialize the two DMA operations, we will meet a bug when

    1.1) we enable CONFIG_DMA_API_DEBUG, CONFIG_DMADEVICES_DEBUG,
         and CONFIG_DEBUG_SG.

    1.2) Use the following commands in an UART console and a SSH console:
         cmd 1: while true;do dd if=/dev/mtd0 of=/dev/null;done
         cmd 1: while true;do dd if=/dev/mmcblk0 of=/dev/null;done

    The kernel log shows below:
    -----------------------------------------------------------------
    kernel BUG at lib/scatterlist.c:28!
    Unable to handle kernel NULL pointer dereference at virtual address 00000000
      .........................
    [<80044a0c>] (__bug+0x18/0x24) from [<80249b74>] (sg_next+0x48/0x4c)
    [<80249b74>] (sg_next+0x48/0x4c) from [<80255398>] (debug_dma_unmap_sg+0x170/0x1a4)
    [<80255398>] (debug_dma_unmap_sg+0x170/0x1a4) from [<8004af58>] (dma_unmap_sg+0x14/0x6c)
    [<8004af58>] (dma_unmap_sg+0x14/0x6c) from [<8027e594>] (mxs_dma_tasklet+0x18/0x1c)
    [<8027e594>] (mxs_dma_tasklet+0x18/0x1c) from [<8007d444>] (tasklet_action+0x114/0x164)
    -----------------------------------------------------------------

    1.3) Assume the two DMA operations is X (first) and Y (second).
         The root cause of the bug:
         X's tasklet mxs_dma_tasklet trid to unmap the scatterlist, while Y is
         trying to set up a new DMA operation with the _SAME_ scatterlist in
         another ARM core.

[2] This patch adds a wait queue and two helpers gpmi_enter_dma/gpmi_exit_dma to
    serialize all the DMA operations.

Signed-off-by: Huang Shijie <b32955 at freescale.com>
Cc: stable at vger.kernel.org
---
 drivers/mtd/nand/gpmi-nand/gpmi-nand.c |   24 ++++++++++++++++++++++++
 drivers/mtd/nand/gpmi-nand/gpmi-nand.h |    2 ++
 2 files changed, 26 insertions(+), 0 deletions(-)

diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
index 71df69e..b849b92 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.c
@@ -392,6 +392,20 @@ void prepare_data_dma(struct gpmi_nand_data *this, enum dma_data_direction dr)
 	}
 }
 
+static void gpmi_enter_dma(struct gpmi_nand_data *this)
+{
+	/* Wait until the previous DMA is finished. */
+	wait_event(this->dma_wait, !this->dma_is_working);
+
+	this->dma_is_working = true;
+}
+
+static void gpmi_exit_dma(struct gpmi_nand_data *this)
+{
+	this->dma_is_working = false;
+	wake_up(&this->dma_wait);
+}
+
 /* This will be called after the DMA operation is finished. */
 static void dma_irq_callback(void *param)
 {
@@ -424,6 +438,7 @@ static void dma_irq_callback(void *param)
 	default:
 		pr_err("in wrong DMA operation.\n");
 	}
+	gpmi_exit_dma(this);
 }
 
 int start_dma_without_bch_irq(struct gpmi_nand_data *this,
@@ -906,6 +921,8 @@ static void gpmi_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl)
 	if (!this->command_length)
 		return;
 
+	gpmi_enter_dma(this);
+
 	ret = gpmi_send_command(this);
 	if (ret)
 		pr_err("Chip: %u, Error %d\n", this->current_chip, ret);
@@ -943,6 +960,8 @@ static void gpmi_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
 	this->upper_buf	= buf;
 	this->upper_len	= len;
 
+	gpmi_enter_dma(this);
+
 	gpmi_read_data(this);
 }
 
@@ -955,6 +974,8 @@ static void gpmi_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
 	this->upper_buf	= (uint8_t *)buf;
 	this->upper_len	= len;
 
+	gpmi_enter_dma(this);
+
 	gpmi_send_data(this);
 }
 
@@ -1031,6 +1052,7 @@ static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int           ret;
 
 	pr_debug("page number is : %d\n", page);
+	gpmi_enter_dma(this);
 	ret = read_page_prepare(this, buf, mtd->writesize,
 					this->payload_virt, this->payload_phys,
 					nfc_geo->payload_size,
@@ -1107,6 +1129,7 @@ static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
 	int        ret;
 
 	pr_debug("ecc write page.\n");
+	gpmi_enter_dma(this);
 	if (this->swap_block_mark) {
 		/*
 		 * If control arrives here, we're doing block mark swapping.
@@ -1745,6 +1768,7 @@ static int gpmi_nand_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, this);
 	this->pdev  = pdev;
 	this->dev   = &pdev->dev;
+	init_waitqueue_head(&this->dma_wait);
 
 	ret = acquire_resources(this);
 	if (ret)
diff --git a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h
index a7685e3..9597615 100644
--- a/drivers/mtd/nand/gpmi-nand/gpmi-nand.h
+++ b/drivers/mtd/nand/gpmi-nand/gpmi-nand.h
@@ -160,6 +160,8 @@ struct gpmi_nand_data {
 
 	/* for DMA operations */
 	bool			direct_dma_map_ok;
+	bool			dma_is_working;
+	wait_queue_head_t	dma_wait;
 
 	struct scatterlist	cmd_sgl;
 	char			*cmd_buffer;
-- 
1.7.2.rc3





More information about the linux-arm-kernel mailing list