mmc: msm_sdcc: Use sg_miter API for PIO
Use sgl mapping iterator to process sgls in PIO mode instead
of pointer arithmetic. This prevents possible page faults
caused by accessing kmapped memory when sge buffer is larger
than PAGE_SIZE or straddles several pages. The buffer returned
by kmap_atomic() is only one PAGE_SIZE large, and an sge buffer
larger than a PAGE_SIZE must be processed in PAGE_SIZEd chunks.
Change-Id: I59f130ca2963b5327a4ce7d3016cc558dcb87218
Signed-off-by: Oluwafemi Adeyemi <aadeyemi@codeaurora.org>
diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c
index 3ae639e..1ecf2f3 100644
--- a/drivers/mmc/host/msm_sdcc.c
+++ b/drivers/mmc/host/msm_sdcc.c
@@ -1042,8 +1042,6 @@
host->curr.got_dataend = 0;
host->curr.got_auto_prog_done = 0;
- memset(&host->pio, 0, sizeof(host->pio));
-
datactrl = MCI_DPSM_ENABLE | (data->blksz << 4);
if (host->curr.wait_for_auto_prog_done)
@@ -1078,17 +1076,21 @@
/* Is data transfer in PIO mode required? */
if (!(datactrl & MCI_DPSM_DMAENABLE)) {
- host->pio.sg = data->sg;
- host->pio.sg_len = data->sg_len;
- host->pio.sg_off = 0;
+ unsigned int sg_miter_flags = SG_MITER_ATOMIC;
if (data->flags & MMC_DATA_READ) {
+ sg_miter_flags |= SG_MITER_TO_SG;
pio_irqmask = MCI_RXFIFOHALFFULLMASK;
if (host->curr.xfer_remain < MCI_FIFOSIZE)
pio_irqmask |= MCI_RXDATAAVLBLMASK;
- } else
+ } else {
+ sg_miter_flags |= SG_MITER_FROM_SG;
pio_irqmask = MCI_TXFIFOHALFEMPTYMASK |
MCI_TXFIFOEMPTYMASK;
+ }
+
+ sg_miter_start(&host->sg_miter, data->sg, data->sg_len,
+ sg_miter_flags);
}
if (data->flags & MMC_DATA_READ)
@@ -1254,6 +1256,7 @@
struct msmsdcc_host *host = dev_id;
void __iomem *base = host->base;
uint32_t status;
+ unsigned long flags;
spin_lock(&host->lock);
@@ -1268,9 +1271,10 @@
#if IRQ_DEBUG
msmsdcc_print_status(host, "irq1-r", status);
#endif
+ local_irq_save(flags);
- do {
- unsigned long flags;
+ while (sg_miter_next(&host->sg_miter)) {
+
unsigned int remain, len;
char *buffer;
@@ -1278,12 +1282,8 @@
| MCI_RXDATAAVLBL)))
break;
- /* Map the current scatter buffer */
- local_irq_save(flags);
- buffer = kmap_atomic(sg_page(host->pio.sg),
- KM_BIO_SRC_IRQ) + host->pio.sg->offset;
- buffer += host->pio.sg_off;
- remain = host->pio.sg->length - host->pio.sg_off;
+ buffer = host->sg_miter.addr;
+ remain = host->sg_miter.length;
len = 0;
if (status & MCI_RXACTIVE)
@@ -1294,11 +1294,7 @@
if (len > remain)
len = remain;
- /* Unmap the buffer */
- kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
- local_irq_restore(flags);
-
- host->pio.sg_off += len;
+ host->sg_miter.consumed = len;
host->curr.xfer_remain -= len;
host->curr.data_xfered += len;
remain -= len;
@@ -1306,20 +1302,11 @@
if (remain) /* Done with this page? */
break; /* Nope */
- if (status & MCI_RXACTIVE && host->curr.user_pages)
- flush_dcache_page(sg_page(host->pio.sg));
-
- if (!--host->pio.sg_len) {
- memset(&host->pio, 0, sizeof(host->pio));
- break;
- }
-
- /* Advance to next sg */
- host->pio.sg++;
- host->pio.sg_off = 0;
-
status = readl_relaxed(base + MMCISTATUS);
- } while (1);
+ }
+
+ sg_miter_stop(&host->sg_miter);
+ local_irq_restore(flags);
if (status & MCI_RXACTIVE && host->curr.xfer_remain < MCI_FIFOSIZE) {
writel_relaxed((readl_relaxed(host->base + MMCIMASK0) &
diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h
index ee7a3e5..319d721 100644
--- a/drivers/mmc/host/msm_sdcc.h
+++ b/drivers/mmc/host/msm_sdcc.h
@@ -361,7 +361,7 @@
struct msmsdcc_sps_data sps;
bool is_dma_mode;
bool is_sps_mode;
- struct msmsdcc_pio_data pio;
+ struct sg_mapping_iter sg_miter;
#ifdef CONFIG_HAS_EARLYSUSPEND
struct early_suspend early_suspend;