aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/mmc/mmcsd.c
diff options
context:
space:
mode:
authorAlexander Motin <mav@FreeBSD.org>2008-10-18 16:17:04 +0000
committerAlexander Motin <mav@FreeBSD.org>2008-10-18 16:17:04 +0000
commit3906d42d6318812d6cc09e5a004eb5edc21820f8 (patch)
tree5383942abf1ff4badcefd5fb68cef35ad34a25e6 /sys/dev/mmc/mmcsd.c
parent1b9f62a0442bf6a3e17a8f578c006192be6bffb8 (diff)
downloadsrc-3906d42d6318812d6cc09e5a004eb5edc21820f8.tar.gz
src-3906d42d6318812d6cc09e5a004eb5edc21820f8.zip
Implement BIO_DELETE command with MMC and SD erase commands.
Erase operation gives card's logic information about unused areas to help it implement wear-leveling with lower overhead comparing to usual writing. Erase is much faster then write and does not depends on data bus speed. Also as result of hitting in-card write logic optimizations I have measured up to 50% performance boost on writing undersized blocks into preerased areas. At the same time there are strict limitations on size and allignment of erase operations. We can erase only blocks aligned to the erase sector size and with size multiple of it. Different cards has different erase sector size which usually varies from 64KB to 4MB. SD cards actually allow to erase smaller blocks, but it is much more expensive as it is implemented via read-erase-write sequence and so not sutable for the BIO_DELETE purposes. Reviewed by: imp@
Notes
Notes: svn path=/head/; revision=184033
Diffstat (limited to 'sys/dev/mmc/mmcsd.c')
-rw-r--r--sys/dev/mmc/mmcsd.c210
1 files changed, 155 insertions, 55 deletions
diff --git a/sys/dev/mmc/mmcsd.c b/sys/dev/mmc/mmcsd.c
index d0ea6a866bea..44ba5d271a82 100644
--- a/sys/dev/mmc/mmcsd.c
+++ b/sys/dev/mmc/mmcsd.c
@@ -133,10 +133,11 @@ mmcsd_attach(device_t dev)
// d->d_dump = mmcsd_dump; Need polling mmc layer
d->d_name = "mmcsd";
d->d_drv1 = sc;
- d->d_maxsize = MAXPHYS; /* Maybe ask bridge? */
+ d->d_maxsize = 4*1024*1024; /* Maximum defined SD card AU size. */
d->d_sectorsize = mmc_get_sector_size(dev);
d->d_mediasize = mmc_get_media_size(dev) * d->d_sectorsize;
d->d_unit = device_get_unit(dev);
+ d->d_flags = DISKFLAG_CANDELETE;
/*
* Display in most natural units. There's no cards < 1MB.
* The SD standard goes to 2GiB, but the data format supports
@@ -216,6 +217,151 @@ mmcsd_strategy(struct bio *bp)
MMCSD_UNLOCK(sc);
}
+static daddr_t
+mmcsd_rw(struct mmcsd_softc *sc, struct bio *bp)
+{
+ daddr_t block, end;
+ struct mmc_command cmd;
+ struct mmc_command stop;
+ struct mmc_request req;
+ struct mmc_data data;
+ device_t dev = sc->dev;
+ int sz = sc->disk->d_sectorsize;
+
+ block = bp->bio_pblkno;
+ end = bp->bio_pblkno + (bp->bio_bcount / sz);
+ while (block < end) {
+ char *vaddr = bp->bio_data +
+ (block - bp->bio_pblkno) * sz;
+ int numblocks;
+#ifdef MULTI_BLOCK
+ numblocks = end - block;
+#else
+ numblocks = 1;
+#endif
+ memset(&req, 0, sizeof(req));
+ memset(&cmd, 0, sizeof(cmd));
+ memset(&stop, 0, sizeof(stop));
+ req.cmd = &cmd;
+ cmd.data = &data;
+ if (bp->bio_cmd == BIO_READ) {
+ if (numblocks > 1)
+ cmd.opcode = MMC_READ_MULTIPLE_BLOCK;
+ else
+ cmd.opcode = MMC_READ_SINGLE_BLOCK;
+ } else {
+ if (numblocks > 1)
+ cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
+ else
+ cmd.opcode = MMC_WRITE_BLOCK;
+ }
+ cmd.arg = block;
+ if (!mmc_get_high_cap(dev))
+ cmd.arg <<= 9;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+ data.data = vaddr;
+ data.mrq = &req;
+ if (bp->bio_cmd == BIO_READ)
+ data.flags = MMC_DATA_READ;
+ else
+ data.flags = MMC_DATA_WRITE;
+ data.len = numblocks * sz;
+ if (numblocks > 1) {
+ data.flags |= MMC_DATA_MULTI;
+ stop.opcode = MMC_STOP_TRANSMISSION;
+ stop.arg = 0;
+ stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
+ req.stop = &stop;
+ }
+// printf("Len %d %lld-%lld flags %#x sz %d\n",
+// (int)data.len, (long long)block, (long long)end, data.flags, sz);
+ MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev,
+ &req);
+ if (req.cmd->error != MMC_ERR_NONE)
+ break;
+ block += numblocks;
+ }
+ return (block);
+}
+
+static daddr_t
+mmcsd_delete(struct mmcsd_softc *sc, struct bio *bp)
+{
+ daddr_t block, end, start, stop;
+ struct mmc_command cmd;
+ struct mmc_request req;
+ device_t dev = sc->dev;
+ int sz = sc->disk->d_sectorsize;
+ int erase_sector;
+
+ block = bp->bio_pblkno;
+ end = bp->bio_pblkno + (bp->bio_bcount / sz);
+
+ /* Safe round to the erase sector boundaries. */
+ erase_sector = mmc_get_erase_sector(dev);
+ start = block + erase_sector - 1; /* Round up. */
+ start -= start % erase_sector;
+ stop = end; /* Round down. */
+ stop -= end % erase_sector;
+
+ /* We can't erase areas smaller then sector. */
+ if (start >= stop)
+ return (end);
+
+ /* Set erase start position. */
+ memset(&req, 0, sizeof(req));
+ memset(&cmd, 0, sizeof(cmd));
+ req.cmd = &cmd;
+ if (mmc_get_card_type(dev) == mode_sd)
+ cmd.opcode = SD_ERASE_WR_BLK_START;
+ else
+ cmd.opcode = MMC_ERASE_GROUP_START;
+ cmd.arg = start;
+ if (!mmc_get_high_cap(dev))
+ cmd.arg <<= 9;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev,
+ &req);
+ if (req.cmd->error != MMC_ERR_NONE) {
+ printf("erase err1: %d\n", req.cmd->error);
+ return (block);
+ }
+ /* Set erase stop position. */
+ memset(&req, 0, sizeof(req));
+ memset(&cmd, 0, sizeof(cmd));
+ req.cmd = &cmd;
+ if (mmc_get_card_type(dev) == mode_sd)
+ cmd.opcode = SD_ERASE_WR_BLK_END;
+ else
+ cmd.opcode = MMC_ERASE_GROUP_END;
+ cmd.arg = stop;
+ if (!mmc_get_high_cap(dev))
+ cmd.arg <<= 9;
+ cmd.arg--;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev,
+ &req);
+ if (req.cmd->error != MMC_ERR_NONE) {
+ printf("erase err2: %d\n", req.cmd->error);
+ return (block);
+ }
+ /* Erase range. */
+ memset(&req, 0, sizeof(req));
+ memset(&cmd, 0, sizeof(cmd));
+ req.cmd = &cmd;
+ cmd.opcode = MMC_ERASE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1B | MMC_CMD_AC;
+ MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev,
+ &req);
+ if (req.cmd->error != MMC_ERR_NONE) {
+ printf("erase err3 %d\n", req.cmd->error);
+ return (block);
+ }
+
+ return (end);
+}
+
static void
mmcsd_task(void *arg)
{
@@ -223,10 +369,6 @@ mmcsd_task(void *arg)
struct bio *bp;
int sz;
daddr_t block, end;
- struct mmc_command cmd;
- struct mmc_command stop;
- struct mmc_request req;
- struct mmc_data data;
device_t dev;
dev = sc->dev;
@@ -242,7 +384,6 @@ mmcsd_task(void *arg)
MMCSD_UNLOCK(sc);
if (!sc->running)
break;
-// printf("mmc_task: request %p for block %ju\n", bp, bp->bio_pblkno);
if (bp->bio_cmd != BIO_READ && mmc_get_read_only(dev)) {
bp->bio_error = EROFS;
bp->bio_resid = bp->bio_bcount;
@@ -252,56 +393,15 @@ mmcsd_task(void *arg)
}
MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev);
sz = sc->disk->d_sectorsize;
+ block = bp->bio_pblkno;
end = bp->bio_pblkno + (bp->bio_bcount / sz);
- for (block = bp->bio_pblkno; block < end;) {
- char *vaddr = bp->bio_data + (block - bp->bio_pblkno) * sz;
- int numblocks;
-#ifdef MULTI_BLOCK
- numblocks = end - block;
-#else
- numblocks = 1;
-#endif
- memset(&req, 0, sizeof(req));
- memset(&cmd, 0, sizeof(cmd));
- memset(&stop, 0, sizeof(stop));
- req.cmd = &cmd;
- cmd.data = &data;
- if (bp->bio_cmd == BIO_READ) {
- if (numblocks > 1)
- cmd.opcode = MMC_READ_MULTIPLE_BLOCK;
- else
- cmd.opcode = MMC_READ_SINGLE_BLOCK;
- } else {
- if (numblocks > 1)
- cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK;
- else
- cmd.opcode = MMC_WRITE_BLOCK;
- }
- cmd.arg = block;
- if (!mmc_get_high_cap(dev))
- cmd.arg <<= 9;
- cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
- data.data = vaddr;
- data.mrq = &req;
- if (bp->bio_cmd == BIO_READ)
- data.flags = MMC_DATA_READ;
- else
- data.flags = MMC_DATA_WRITE;
- data.len = numblocks * sz;
- if (numblocks > 1) {
- data.flags |= MMC_DATA_MULTI;
- stop.opcode = MMC_STOP_TRANSMISSION;
- stop.arg = 0;
- stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
- req.stop = &stop;
- }
-// printf("Len %d %lld-%lld flags %#x sz %d\n",
-// (int)data.len, (long long)block, (long long)end, data.flags, sz);
- MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev,
- &req);
- if (req.cmd->error != MMC_ERR_NONE)
- break;
- block += numblocks;
+ if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE) {
+ block = mmcsd_rw(sc, bp);
+ } else if (bp->bio_cmd == BIO_DELETE) {
+ block = mmcsd_delete(sc, bp);
+ } else {
+ /* UNSUPPORTED COMMAND */
+ block = bp->bio_pblkno;
}
MMCBUS_RELEASE_BUS(device_get_parent(dev), dev);
if (block < end) {