Skip to content

Commit f9c2a0d

Browse files
Seungwon Jeoncjb
authored andcommitted
mmc: dw_mmc: Fix PIO mode with support of highmem
Current PIO mode makes a kernel crash with CONFIG_HIGHMEM. Highmem pages have a NULL from sg_virt(sg). This patch fixes the following problem. Unable to handle kernel NULL pointer dereference at virtual address 00000000 pgd = c0004000 [00000000] *pgd=00000000 Internal error: Oops: 817 [#1] PREEMPT SMP Modules linked in: CPU: 0 Not tainted (3.0.15-01423-gdbf465f torvalds#589) PC is at dw_mci_pull_data32+0x4c/0x9c LR is at dw_mci_read_data_pio+0x54/0x1f0 pc : [<c0358824>] lr : [<c035988c>] psr: 20000193 sp : c0619d48 ip : c0619d70 fp : c0619d6c r10: 00000000 r9 : 00000002 r8 : 00001000 r7 : 00000200 r6 : 00000000 r5 : e1dd3100 r4 : 00000000 r3 : 65622023 r2 : 0000007f r1 : eeb96000 r0 : e1dd3100 Flags: nzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment xkernel Control: 10c5387d Table: 61e2004a DAC: 00000015 Process swapper (pid: 0, stack limit = 0xc06182f0) Stack: (0xc0619d48 to 0xc061a000) 9d40: e1dd3100 e1a4f000 00000000 e1dd3100 e1a4f000 00000200 9d60: c0619da4 c0619d70 c035988c c03587e4 c0619d9c e18158f4 e1dd3100 e1dd3100 9d80: 00000020 00000000 00000000 00000020 c06e8a84 00000000 c0619e04 c0619da8 9da0: c0359b24 c0359844 e18158f4 e1dd3164 e1dd3168 e1dd3150 3d02fc79 e1dd3154 9dc0: e1dd3178 00000000 00000020 00000000 e1dd3150 00000000 c10dd7e8 e1a84900 9de0: c061e7cc 00000000 00000000 0000008d c06e8a84 c061e780 c0619e4c c0619e08 9e00: c00c4738 c0359a34 3d02fc79 00000000 c0619e4c c05a1698 c05a1670 c05a165c 9e20: c04de8b0 c061e780 c061e7cc e1a84900 ffffed68 0000008d c0618000 00000000 9e40: c0619e6c c0619e50 c00c48b4 c00c46c8 c061e780 c00423ac c061e7cc ffffed68 9e60: c0619e8c c0619e70 c00c7358 c00c487c 0000008d ffffee38 c0618000 ffffed68 9e80: c0619ea4 c0619e90 c00c4258 c00c72b0 c00423ac ffffee38 c0619ecc c0619ea8 9ea0: c004241c c00c4234 ffffffff f8810000 0000006d 00000002 00000001 7fffffff 9ec0: c0619f44 c0619ed0 c0048bc0 c00423c4 220ae7a9 00000000 386f0d30 0005d3a4 9ee0: c00423ac c10dd0b8 c06f2cd8 c0618000 c0594778 c003a674 7fffffff c0619f44 9f00: 386f0d30 c0619f18 c00a6f94 c005be3c 80000013 ffffffff 386f0d30 0005d3a4 9f20: 386f0d30 0005d2d1 c10dd0a8 c10dd0b8 c06f2cd8 c0618000 c0619f74 c0619f48 9f40: c0345858 c005be00 c00a2440 c0618000 c0618000 c00410d8 c06c1944 c00410fc 9f60: c0594778 c003a674 c0619f9c c0619f78 c004a7e8 c03457b4 c0618000 c06c18f8 9f80: 00000000 c0039c70 c06c18d4 c003a674 c0619fb4 c0619fa0 c04ceafc c004a714 9fa0: c06287b4 c06c18f8 c0619ff4 c0619fb8 c0008b68 c04cea68 c0008578 00000000 9fc0: 00000000 c003a674 00000000 10c5387d c0628658 c003aa78 c062f1c4 4000406a 9fe0: 413fc090 00000000 00000000 c0619ff8 40008044 c0008858 00000000 00000000 Backtrace: [<c03587d8>] (dw_mci_pull_data32+0x0/0x9c) from [<c035988c>] (dw_mci_read_data_pio+0x54/0x1f0) r6:00000200 r5:e1a4f000 r4:e1dd3100 [<c0359838>] (dw_mci_read_data_pio+0x0/0x1f0) from [<c0359b24>] (dw_mci_interrupt+0xfc/0x4a4) [<c0359a28>] (dw_mci_interrupt+0x0/0x4a4) from [<c00c4738>] (handle_irq_event_percpu+0x7c/0x1b4) [<c00c46bc>] (handle_irq_event_percpu+0x0/0x1b4) from [<c00c48b4>] (handle_irq_event+0x44/0x64) [<c00c4870>] (handle_irq_event+0x0/0x64) from [<c00c7358>] (handle_fasteoi_irq+0xb4/0x124) r7:ffffed68 r6:c061e7cc r5:c00423ac r4:c061e780 [<c00c72a4>] (handle_fasteoi_irq+0x0/0x124) from [<c00c4258>] (generic_handle_irq+0x30/0x38) r7:ffffed68 r6:c0618000 r5:ffffee38 r4:0000008d [<c00c4228>] (generic_handle_irq+0x0/0x38) from [<c004241c>] (asm_do_IRQ+0x64/0xe0) r5:ffffee38 r4:c00423ac [<c00423b8>] (asm_do_IRQ+0x0/0xe0) from [<c0048bc0>] (__irq_svc+0x80/0x14c) Exception stack(0xc0619ed0 to 0xc0619f18) Signed-off-by: Seungwon Jeon <[email protected]> Acked-by: Will Newton <[email protected]> Cc: stable <[email protected]> Signed-off-by: Chris Ball <[email protected]>
1 parent 18ee684 commit f9c2a0d

File tree

2 files changed

+79
-71
lines changed

2 files changed

+79
-71
lines changed

drivers/mmc/host/dw_mmc.c

Lines changed: 75 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
#include <linux/ioport.h>
2323
#include <linux/module.h>
2424
#include <linux/platform_device.h>
25-
#include <linux/scatterlist.h>
2625
#include <linux/seq_file.h>
2726
#include <linux/slab.h>
2827
#include <linux/stat.h>
@@ -502,8 +501,14 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data)
502501
host->dir_status = DW_MCI_SEND_STATUS;
503502

504503
if (dw_mci_submit_data_dma(host, data)) {
504+
int flags = SG_MITER_ATOMIC;
505+
if (host->data->flags & MMC_DATA_READ)
506+
flags |= SG_MITER_TO_SG;
507+
else
508+
flags |= SG_MITER_FROM_SG;
509+
510+
sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
505511
host->sg = data->sg;
506-
host->pio_offset = 0;
507512
host->part_buf_start = 0;
508513
host->part_buf_count = 0;
509514

@@ -972,6 +977,7 @@ static void dw_mci_tasklet_func(unsigned long priv)
972977
* generates a block interrupt, hence setting
973978
* the scatter-gather pointer to NULL.
974979
*/
980+
sg_miter_stop(&host->sg_miter);
975981
host->sg = NULL;
976982
ctrl = mci_readl(host, CTRL);
977983
ctrl |= SDMMC_CTRL_FIFO_RESET;
@@ -1311,54 +1317,44 @@ static void dw_mci_pull_data(struct dw_mci *host, void *buf, int cnt)
13111317

13121318
static void dw_mci_read_data_pio(struct dw_mci *host)
13131319
{
1314-
struct scatterlist *sg = host->sg;
1315-
void *buf = sg_virt(sg);
1316-
unsigned int offset = host->pio_offset;
1320+
struct sg_mapping_iter *sg_miter = &host->sg_miter;
1321+
void *buf;
1322+
unsigned int offset;
13171323
struct mmc_data *data = host->data;
13181324
int shift = host->data_shift;
13191325
u32 status;
13201326
unsigned int nbytes = 0, len;
1327+
unsigned int remain, fcnt;
13211328

13221329
do {
1323-
len = host->part_buf_count +
1324-
(SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift);
1325-
if (offset + len <= sg->length) {
1330+
if (!sg_miter_next(sg_miter))
1331+
goto done;
1332+
1333+
host->sg = sg_miter->__sg;
1334+
buf = sg_miter->addr;
1335+
remain = sg_miter->length;
1336+
offset = 0;
1337+
1338+
do {
1339+
fcnt = (SDMMC_GET_FCNT(mci_readl(host, STATUS))
1340+
<< shift) + host->part_buf_count;
1341+
len = min(remain, fcnt);
1342+
if (!len)
1343+
break;
13261344
dw_mci_pull_data(host, (void *)(buf + offset), len);
1327-
13281345
offset += len;
13291346
nbytes += len;
1330-
1331-
if (offset == sg->length) {
1332-
flush_dcache_page(sg_page(sg));
1333-
host->sg = sg = sg_next(sg);
1334-
if (!sg)
1335-
goto done;
1336-
1337-
offset = 0;
1338-
buf = sg_virt(sg);
1339-
}
1340-
} else {
1341-
unsigned int remaining = sg->length - offset;
1342-
dw_mci_pull_data(host, (void *)(buf + offset),
1343-
remaining);
1344-
nbytes += remaining;
1345-
1346-
flush_dcache_page(sg_page(sg));
1347-
host->sg = sg = sg_next(sg);
1348-
if (!sg)
1349-
goto done;
1350-
1351-
offset = len - remaining;
1352-
buf = sg_virt(sg);
1353-
dw_mci_pull_data(host, buf, offset);
1354-
nbytes += offset;
1355-
}
1347+
remain -= len;
1348+
} while (remain);
1349+
sg_miter->consumed = offset;
13561350

13571351
status = mci_readl(host, MINTSTS);
13581352
mci_writel(host, RINTSTS, SDMMC_INT_RXDR);
13591353
if (status & DW_MCI_DATA_ERROR_FLAGS) {
13601354
host->data_status = status;
13611355
data->bytes_xfered += nbytes;
1356+
sg_miter_stop(sg_miter);
1357+
host->sg = NULL;
13621358
smp_wmb();
13631359

13641360
set_bit(EVENT_DATA_ERROR, &host->pending_events);
@@ -1367,65 +1363,66 @@ static void dw_mci_read_data_pio(struct dw_mci *host)
13671363
return;
13681364
}
13691365
} while (status & SDMMC_INT_RXDR); /*if the RXDR is ready read again*/
1370-
host->pio_offset = offset;
13711366
data->bytes_xfered += nbytes;
1367+
1368+
if (!remain) {
1369+
if (!sg_miter_next(sg_miter))
1370+
goto done;
1371+
sg_miter->consumed = 0;
1372+
}
1373+
sg_miter_stop(sg_miter);
13721374
return;
13731375

13741376
done:
13751377
data->bytes_xfered += nbytes;
1378+
sg_miter_stop(sg_miter);
1379+
host->sg = NULL;
13761380
smp_wmb();
13771381
set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
13781382
}
13791383

13801384
static void dw_mci_write_data_pio(struct dw_mci *host)
13811385
{
1382-
struct scatterlist *sg = host->sg;
1383-
void *buf = sg_virt(sg);
1384-
unsigned int offset = host->pio_offset;
1386+
struct sg_mapping_iter *sg_miter = &host->sg_miter;
1387+
void *buf;
1388+
unsigned int offset;
13851389
struct mmc_data *data = host->data;
13861390
int shift = host->data_shift;
13871391
u32 status;
13881392
unsigned int nbytes = 0, len;
1393+
unsigned int fifo_depth = host->fifo_depth;
1394+
unsigned int remain, fcnt;
13891395

13901396
do {
1391-
len = ((host->fifo_depth -
1392-
SDMMC_GET_FCNT(mci_readl(host, STATUS))) << shift)
1393-
- host->part_buf_count;
1394-
if (offset + len <= sg->length) {
1397+
if (!sg_miter_next(sg_miter))
1398+
goto done;
1399+
1400+
host->sg = sg_miter->__sg;
1401+
buf = sg_miter->addr;
1402+
remain = sg_miter->length;
1403+
offset = 0;
1404+
1405+
do {
1406+
fcnt = ((fifo_depth -
1407+
SDMMC_GET_FCNT(mci_readl(host, STATUS)))
1408+
<< shift) - host->part_buf_count;
1409+
len = min(remain, fcnt);
1410+
if (!len)
1411+
break;
13951412
host->push_data(host, (void *)(buf + offset), len);
1396-
13971413
offset += len;
13981414
nbytes += len;
1399-
if (offset == sg->length) {
1400-
host->sg = sg = sg_next(sg);
1401-
if (!sg)
1402-
goto done;
1403-
1404-
offset = 0;
1405-
buf = sg_virt(sg);
1406-
}
1407-
} else {
1408-
unsigned int remaining = sg->length - offset;
1409-
1410-
host->push_data(host, (void *)(buf + offset),
1411-
remaining);
1412-
nbytes += remaining;
1413-
1414-
host->sg = sg = sg_next(sg);
1415-
if (!sg)
1416-
goto done;
1417-
1418-
offset = len - remaining;
1419-
buf = sg_virt(sg);
1420-
host->push_data(host, (void *)buf, offset);
1421-
nbytes += offset;
1422-
}
1415+
remain -= len;
1416+
} while (remain);
1417+
sg_miter->consumed = offset;
14231418

14241419
status = mci_readl(host, MINTSTS);
14251420
mci_writel(host, RINTSTS, SDMMC_INT_TXDR);
14261421
if (status & DW_MCI_DATA_ERROR_FLAGS) {
14271422
host->data_status = status;
14281423
data->bytes_xfered += nbytes;
1424+
sg_miter_stop(sg_miter);
1425+
host->sg = NULL;
14291426

14301427
smp_wmb();
14311428

@@ -1435,12 +1432,20 @@ static void dw_mci_write_data_pio(struct dw_mci *host)
14351432
return;
14361433
}
14371434
} while (status & SDMMC_INT_TXDR); /* if TXDR write again */
1438-
host->pio_offset = offset;
14391435
data->bytes_xfered += nbytes;
1436+
1437+
if (!remain) {
1438+
if (!sg_miter_next(sg_miter))
1439+
goto done;
1440+
sg_miter->consumed = 0;
1441+
}
1442+
sg_miter_stop(sg_miter);
14401443
return;
14411444

14421445
done:
14431446
data->bytes_xfered += nbytes;
1447+
sg_miter_stop(sg_miter);
1448+
host->sg = NULL;
14441449
smp_wmb();
14451450
set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
14461451
}
@@ -1643,6 +1648,7 @@ static void dw_mci_work_routine_card(struct work_struct *work)
16431648
* block interrupt, hence setting the
16441649
* scatter-gather pointer to NULL.
16451650
*/
1651+
sg_miter_stop(&host->sg_miter);
16461652
host->sg = NULL;
16471653

16481654
ctrl = mci_readl(host, CTRL);

include/linux/mmc/dw_mmc.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#ifndef LINUX_MMC_DW_MMC_H
1515
#define LINUX_MMC_DW_MMC_H
1616

17+
#include <linux/scatterlist.h>
18+
1719
#define MAX_MCI_SLOTS 2
1820

1921
enum dw_mci_state {
@@ -40,7 +42,7 @@ struct mmc_data;
4042
* @lock: Spinlock protecting the queue and associated data.
4143
* @regs: Pointer to MMIO registers.
4244
* @sg: Scatterlist entry currently being processed by PIO code, if any.
43-
* @pio_offset: Offset into the current scatterlist entry.
45+
* @sg_miter: PIO mapping scatterlist iterator.
4446
* @cur_slot: The slot which is currently using the controller.
4547
* @mrq: The request currently being processed on @cur_slot,
4648
* or NULL if the controller is idle.
@@ -115,7 +117,7 @@ struct dw_mci {
115117
void __iomem *regs;
116118

117119
struct scatterlist *sg;
118-
unsigned int pio_offset;
120+
struct sg_mapping_iter sg_miter;
119121

120122
struct dw_mci_slot *cur_slot;
121123
struct mmc_request *mrq;

0 commit comments

Comments
 (0)