aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2021-03-22 18:50:25 +0000
committerPeter Maydell <peter.maydell@linaro.org>2021-03-22 18:50:25 +0000
commit5ca634afcf83215a9a54ca6e66032325b5ffb5f6 (patch)
treee779695412b37e07ec7c9ecec023779bedb6aa75
parentc95bd5ff1660883d15ad6e0005e4c8571604f51a (diff)
parentcffb446e8fd19a14e1634c7a3a8b07be3f01d5c9 (diff)
Merge remote-tracking branch 'remotes/philmd/tags/sdmmc-20210322' into staging
SD/MMC patches queue - Fix build error when DEBUG_SD is on - Perform SD ERASE operation - SDHCI ADMA heap buffer overflow (CVE-2020-17380, CVE-2020-25085, CVE-2021-3409) # gpg: Signature made Mon 22 Mar 2021 17:13:43 GMT # gpg: using RSA key FAABE75E12917221DCFD6BB2E3E32C2CDEADC0DE # gpg: Good signature from "Philippe Mathieu-Daudé (F4BUG) <f4bug@amsat.org>" [full] # Primary key fingerprint: FAAB E75E 1291 7221 DCFD 6BB2 E3E3 2C2C DEAD C0DE * remotes/philmd/tags/sdmmc-20210322: hw/sd: sdhci: Reset the data pointer of s->fifo_buffer[] when a different block size is programmed hw/sd: sdhci: Limit block size only when SDHC_BLKSIZE register is writable hw/sd: sdhci: Correctly set the controller status for ADMA hw/sd: sdhci: Don't write to SDHC_SYSAD register when transfer is in progress hw/sd: sdhci: Don't transfer any data when command time out hw/sd: sd: Actually perform the erase operation hw/sd: sd: Fix build error when DEBUG_SD is on Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--hw/sd/sd.c23
-rw-r--r--hw/sd/sdhci.c53
2 files changed, 50 insertions, 26 deletions
diff --git a/hw/sd/sd.c b/hw/sd/sd.c
index 8b397effbc..282d39a704 100644
--- a/hw/sd/sd.c
+++ b/hw/sd/sd.c
@@ -47,6 +47,7 @@
#include "qemu/timer.h"
#include "qemu/log.h"
#include "qemu/module.h"
+#include "qemu-common.h"
#include "sdmmc-internal.h"
#include "trace.h"
@@ -762,10 +763,12 @@ static void sd_blk_write(SDState *sd, uint64_t addr, uint32_t len)
static void sd_erase(SDState *sd)
{
- int i;
uint64_t erase_start = sd->erase_start;
uint64_t erase_end = sd->erase_end;
bool sdsc = true;
+ uint64_t wpnum;
+ uint64_t erase_addr;
+ int erase_len = 1 << HWBLOCK_SHIFT;
trace_sdcard_erase(sd->erase_start, sd->erase_end);
if (sd->erase_start == INVALID_ADDRESS
@@ -794,17 +797,19 @@ static void sd_erase(SDState *sd)
sd->erase_end = INVALID_ADDRESS;
sd->csd[14] |= 0x40;
- /* Only SDSC cards support write protect groups */
- if (sdsc) {
- erase_start = sd_addr_to_wpnum(erase_start);
- erase_end = sd_addr_to_wpnum(erase_end);
-
- for (i = erase_start; i <= erase_end; i++) {
- assert(i < sd->wpgrps_size);
- if (test_bit(i, sd->wp_groups)) {
+ memset(sd->data, 0xff, erase_len);
+ for (erase_addr = erase_start; erase_addr <= erase_end;
+ erase_addr += erase_len) {
+ if (sdsc) {
+ /* Only SDSC cards support write protect groups */
+ wpnum = sd_addr_to_wpnum(erase_addr);
+ assert(wpnum < sd->wpgrps_size);
+ if (test_bit(wpnum, sd->wp_groups)) {
sd->card_status |= WP_ERASE_SKIP;
+ continue;
}
}
+ BLK_WRITE_BLOCK(erase_addr, erase_len);
}
}
diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
index 9acf4467a3..5b8678110b 100644
--- a/hw/sd/sdhci.c
+++ b/hw/sd/sdhci.c
@@ -326,6 +326,7 @@ static void sdhci_send_command(SDHCIState *s)
SDRequest request;
uint8_t response[16];
int rlen;
+ bool timeout = false;
s->errintsts = 0;
s->acmd12errsts = 0;
@@ -349,6 +350,7 @@ static void sdhci_send_command(SDHCIState *s)
trace_sdhci_response16(s->rspreg[3], s->rspreg[2],
s->rspreg[1], s->rspreg[0]);
} else {
+ timeout = true;
trace_sdhci_error("timeout waiting for command response");
if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
s->errintsts |= SDHC_EIS_CMDTIMEOUT;
@@ -369,7 +371,7 @@ static void sdhci_send_command(SDHCIState *s)
sdhci_update_irq(s);
- if (s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
+ if (!timeout && s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
s->data_count = 0;
sdhci_data_transfer(s);
}
@@ -766,7 +768,9 @@ static void sdhci_do_adma(SDHCIState *s)
switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) {
case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */
+ s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
if (s->trnmod & SDHC_TRNS_READ) {
+ s->prnsts |= SDHC_DOING_READ;
while (length) {
if (s->data_count == 0) {
sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size);
@@ -794,6 +798,7 @@ static void sdhci_do_adma(SDHCIState *s)
}
}
} else {
+ s->prnsts |= SDHC_DOING_WRITE;
while (length) {
begin = s->data_count;
if ((length + begin) < block_size) {
@@ -1119,31 +1124,45 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
switch (offset & ~0x3) {
case SDHC_SYSAD:
- s->sdmasysad = (s->sdmasysad & mask) | value;
- MASKED_WRITE(s->sdmasysad, mask, value);
- /* Writing to last byte of sdmasysad might trigger transfer */
- if (!(mask & 0xFF000000) && TRANSFERRING_DATA(s->prnsts) && s->blkcnt &&
- s->blksize && SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) {
- if (s->trnmod & SDHC_TRNS_MULTI) {
- sdhci_sdma_transfer_multi_blocks(s);
- } else {
- sdhci_sdma_transfer_single_block(s);
+ if (!TRANSFERRING_DATA(s->prnsts)) {
+ s->sdmasysad = (s->sdmasysad & mask) | value;
+ MASKED_WRITE(s->sdmasysad, mask, value);
+ /* Writing to last byte of sdmasysad might trigger transfer */
+ if (!(mask & 0xFF000000) && s->blkcnt && s->blksize &&
+ SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) {
+ if (s->trnmod & SDHC_TRNS_MULTI) {
+ sdhci_sdma_transfer_multi_blocks(s);
+ } else {
+ sdhci_sdma_transfer_single_block(s);
+ }
}
}
break;
case SDHC_BLKSIZE:
if (!TRANSFERRING_DATA(s->prnsts)) {
+ uint16_t blksize = s->blksize;
+
MASKED_WRITE(s->blksize, mask, extract32(value, 0, 12));
MASKED_WRITE(s->blkcnt, mask >> 16, value >> 16);
- }
- /* Limit block size to the maximum buffer size */
- if (extract32(s->blksize, 0, 12) > s->buf_maxsz) {
- qemu_log_mask(LOG_GUEST_ERROR, "%s: Size 0x%x is larger than "
- "the maximum buffer 0x%x\n", __func__, s->blksize,
- s->buf_maxsz);
+ /* Limit block size to the maximum buffer size */
+ if (extract32(s->blksize, 0, 12) > s->buf_maxsz) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Size 0x%x is larger than "
+ "the maximum buffer 0x%x\n", __func__, s->blksize,
+ s->buf_maxsz);
+
+ s->blksize = deposit32(s->blksize, 0, 12, s->buf_maxsz);
+ }
- s->blksize = deposit32(s->blksize, 0, 12, s->buf_maxsz);
+ /*
+ * If the block size is programmed to a different value from
+ * the previous one, reset the data pointer of s->fifo_buffer[]
+ * so that s->fifo_buffer[] can be filled in using the new block
+ * size in the next transfer.
+ */
+ if (blksize != s->blksize) {
+ s->data_count = 0;
+ }
}
break;