[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 <[email protected]>
Cc: [email protected]
---
 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


--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to