qemu-e2k/hw/sd/sdhci.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1962 lines
62 KiB
C
Raw Normal View History

/*
* SD Association Host Standard Specification v2.0 controller emulation
*
* Datasheet: PartA2_SD_Host_Controller_Simplified_Specification_Ver2.00.pdf
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Mitsyanko Igor <i.mitsyanko@samsung.com>
* Peter A.G. Crosthwaite <peter.crosthwaite@petalogix.com>
*
* Based on MMC controller for Samsung S5PC1xx-based board emulation
* by Alexey Merkulov and Vladimir Monakhov.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "hw/irq.h"
#include "hw/qdev-properties.h"
#include "sysemu/dma.h"
#include "qemu/timer.h"
#include "qemu/bitops.h"
#include "hw/sd/sdhci.h"
#include "migration/vmstate.h"
#include "sdhci-internal.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "trace.h"
#include "qom/object.h"
#define TYPE_SDHCI_BUS "sdhci-bus"
/* This is reusing the SDBus typedef from SD_BUS */
DECLARE_INSTANCE_CHECKER(SDBus, SDHCI_BUS,
TYPE_SDHCI_BUS)
#define MASKED_WRITE(reg, mask, val) (reg = (reg & (mask)) | (val))
static inline unsigned int sdhci_get_fifolen(SDHCIState *s)
{
return 1 << (9 + FIELD_EX32(s->capareg, SDHC_CAPAB, MAXBLOCKLENGTH));
}
/* return true on error */
static bool sdhci_check_capab_freq_range(SDHCIState *s, const char *desc,
uint8_t freq, Error **errp)
{
if (s->sd_spec_version >= 3) {
return false;
}
switch (freq) {
case 0:
case 10 ... 63:
break;
default:
error_setg(errp, "SD %s clock frequency can have value"
"in range 0-63 only", desc);
return true;
}
return false;
}
static void sdhci_check_capareg(SDHCIState *s, Error **errp)
{
uint64_t msk = s->capareg;
uint32_t val;
bool y;
switch (s->sd_spec_version) {
case 4:
val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS64BIT_V4);
trace_sdhci_capareg("64-bit system bus (v4)", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, BUS64BIT_V4, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, UHS_II);
trace_sdhci_capareg("UHS-II", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, UHS_II, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA3);
trace_sdhci_capareg("ADMA3", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA3, 0);
/* fallthrough */
case 3:
val = FIELD_EX64(s->capareg, SDHC_CAPAB, ASYNC_INT);
trace_sdhci_capareg("async interrupt", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, ASYNC_INT, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, SLOT_TYPE);
if (val) {
error_setg(errp, "slot-type not supported");
return;
}
trace_sdhci_capareg("slot type", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, SLOT_TYPE, 0);
if (val != 2) {
val = FIELD_EX64(s->capareg, SDHC_CAPAB, EMBEDDED_8BIT);
trace_sdhci_capareg("8-bit bus", val);
}
msk = FIELD_DP64(msk, SDHC_CAPAB, EMBEDDED_8BIT, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS_SPEED);
trace_sdhci_capareg("bus speed mask", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, BUS_SPEED, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, DRIVER_STRENGTH);
trace_sdhci_capareg("driver strength mask", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, DRIVER_STRENGTH, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, TIMER_RETUNING);
trace_sdhci_capareg("timer re-tuning", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, TIMER_RETUNING, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, SDR50_TUNING);
trace_sdhci_capareg("use SDR50 tuning", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, SDR50_TUNING, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, RETUNING_MODE);
trace_sdhci_capareg("re-tuning mode", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, RETUNING_MODE, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, CLOCK_MULT);
trace_sdhci_capareg("clock multiplier", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, CLOCK_MULT, 0);
/* fallthrough */
case 2: /* default version */
val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA2);
trace_sdhci_capareg("ADMA2", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA2, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, ADMA1);
trace_sdhci_capareg("ADMA1", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, ADMA1, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, BUS64BIT);
trace_sdhci_capareg("64-bit system bus (v3)", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, BUS64BIT, 0);
/* fallthrough */
case 1:
y = FIELD_EX64(s->capareg, SDHC_CAPAB, TOUNIT);
msk = FIELD_DP64(msk, SDHC_CAPAB, TOUNIT, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, TOCLKFREQ);
trace_sdhci_capareg(y ? "timeout (MHz)" : "Timeout (KHz)", val);
if (sdhci_check_capab_freq_range(s, "timeout", val, errp)) {
return;
}
msk = FIELD_DP64(msk, SDHC_CAPAB, TOCLKFREQ, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, BASECLKFREQ);
trace_sdhci_capareg(y ? "base (MHz)" : "Base (KHz)", val);
if (sdhci_check_capab_freq_range(s, "base", val, errp)) {
return;
}
msk = FIELD_DP64(msk, SDHC_CAPAB, BASECLKFREQ, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, MAXBLOCKLENGTH);
if (val >= 3) {
error_setg(errp, "block size can be 512, 1024 or 2048 only");
return;
}
trace_sdhci_capareg("max block length", sdhci_get_fifolen(s));
msk = FIELD_DP64(msk, SDHC_CAPAB, MAXBLOCKLENGTH, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, HIGHSPEED);
trace_sdhci_capareg("high speed", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, HIGHSPEED, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, SDMA);
trace_sdhci_capareg("SDMA", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, SDMA, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, SUSPRESUME);
trace_sdhci_capareg("suspend/resume", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, SUSPRESUME, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, V33);
trace_sdhci_capareg("3.3v", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, V33, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, V30);
trace_sdhci_capareg("3.0v", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, V30, 0);
val = FIELD_EX64(s->capareg, SDHC_CAPAB, V18);
trace_sdhci_capareg("1.8v", val);
msk = FIELD_DP64(msk, SDHC_CAPAB, V18, 0);
break;
default:
error_setg(errp, "Unsupported spec version: %u", s->sd_spec_version);
}
if (msk) {
qemu_log_mask(LOG_UNIMP,
"SDHCI: unknown CAPAB mask: 0x%016" PRIx64 "\n", msk);
}
}
static uint8_t sdhci_slotint(SDHCIState *s)
{
return (s->norintsts & s->norintsigen) || (s->errintsts & s->errintsigen) ||
((s->norintsts & SDHC_NIS_INSERT) && (s->wakcon & SDHC_WKUP_ON_INS)) ||
((s->norintsts & SDHC_NIS_REMOVE) && (s->wakcon & SDHC_WKUP_ON_RMV));
}
/* Return true if IRQ was pending and delivered */
static bool sdhci_update_irq(SDHCIState *s)
{
bool pending = sdhci_slotint(s);
qemu_set_irq(s->irq, pending);
return pending;
}
static void sdhci_raise_insertion_irq(void *opaque)
{
SDHCIState *s = (SDHCIState *)opaque;
if (s->norintsts & SDHC_NIS_REMOVE) {
timer_mod(s->insert_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_INSERTION_DELAY);
} else {
s->prnsts = 0x1ff0000;
if (s->norintstsen & SDHC_NISEN_INSERT) {
s->norintsts |= SDHC_NIS_INSERT;
}
sdhci_update_irq(s);
}
}
static void sdhci_set_inserted(DeviceState *dev, bool level)
{
SDHCIState *s = (SDHCIState *)dev;
trace_sdhci_set_inserted(level ? "insert" : "eject");
if ((s->norintsts & SDHC_NIS_REMOVE) && level) {
/* Give target some time to notice card ejection */
timer_mod(s->insert_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_INSERTION_DELAY);
} else {
if (level) {
s->prnsts = 0x1ff0000;
if (s->norintstsen & SDHC_NISEN_INSERT) {
s->norintsts |= SDHC_NIS_INSERT;
}
} else {
s->prnsts = 0x1fa0000;
s->pwrcon &= ~SDHC_POWER_ON;
s->clkcon &= ~SDHC_CLOCK_SDCLK_EN;
if (s->norintstsen & SDHC_NISEN_REMOVE) {
s->norintsts |= SDHC_NIS_REMOVE;
}
}
sdhci_update_irq(s);
}
}
static void sdhci_set_readonly(DeviceState *dev, bool level)
{
SDHCIState *s = (SDHCIState *)dev;
if (level) {
s->prnsts &= ~SDHC_WRITE_PROTECT;
} else {
/* Write enabled */
s->prnsts |= SDHC_WRITE_PROTECT;
}
}
static void sdhci_reset(SDHCIState *s)
{
DeviceState *dev = DEVICE(s);
timer_del(s->insert_timer);
timer_del(s->transfer_timer);
/* Set all registers to 0. Capabilities/Version registers are not cleared
* and assumed to always preserve their value, given to them during
* initialization */
memset(&s->sdmasysad, 0, (uintptr_t)&s->capareg - (uintptr_t)&s->sdmasysad);
/* Reset other state based on current card insertion/readonly status */
sdhci_set_inserted(dev, sdbus_get_inserted(&s->sdbus));
sdhci_set_readonly(dev, sdbus_get_readonly(&s->sdbus));
s->data_count = 0;
s->stopped_state = sdhc_not_stopped;
s->pending_insert_state = false;
}
static void sdhci_poweron_reset(DeviceState *dev)
{
/* QOM (ie power-on) reset. This is identical to reset
* commanded via device register apart from handling of the
* 'pending insert on powerup' quirk.
*/
SDHCIState *s = (SDHCIState *)dev;
sdhci_reset(s);
if (s->pending_insert_quirk) {
s->pending_insert_state = true;
}
}
static void sdhci_data_transfer(void *opaque);
#define BLOCK_SIZE_MASK (4 * KiB - 1)
static void sdhci_send_command(SDHCIState *s)
{
SDRequest request;
uint8_t response[16];
int rlen;
hw/sd: sdhci: Don't transfer any data when command time out At the end of sdhci_send_command(), it starts a data transfer if the command register indicates data is associated. But the data transfer should only be initiated when the command execution has succeeded. With this fix, the following reproducer: outl 0xcf8 0x80001810 outl 0xcfc 0xe1068000 outl 0xcf8 0x80001804 outw 0xcfc 0x7 write 0xe106802c 0x1 0x0f write 0xe1068004 0xc 0x2801d10101fffffbff28a384 write 0xe106800c 0x1f 0x9dacbbcad9e8f7061524334251606f7e8d9cabbac9d8e7f60514233241505f write 0xe1068003 0x28 0x80d000251480d000252280d000253080d000253e80d000254c80d000255a80d000256880d0002576 write 0xe1068003 0x1 0xfe cannot be reproduced with the following QEMU command line: $ qemu-system-x86_64 -nographic -M pc-q35-5.0 \ -device sdhci-pci,sd-spec-version=3 \ -drive if=sd,index=0,file=null-co://,format=raw,id=mydrive \ -device sd-card,drive=mydrive \ -monitor none -serial none -qtest stdio Cc: qemu-stable@nongnu.org Fixes: CVE-2020-17380 Fixes: CVE-2020-25085 Fixes: CVE-2021-3409 Fixes: d7dfca0807a0 ("hw/sdhci: introduce standard SD host controller") Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Cornelius Aschermann (Ruhr-Universität Bochum) Reported-by: Sergej Schumilo (Ruhr-Universität Bochum) Reported-by: Simon Wörner (Ruhr-Universität Bochum) Buglink: https://bugs.launchpad.net/qemu/+bug/1892960 Buglink: https://bugs.launchpad.net/qemu/+bug/1909418 Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1928146 Acked-by: Alistair Francis <alistair.francis@wdc.com> Tested-by: Alexander Bulekov <alxndr@bu.edu> Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Message-Id: <20210303122639.20004-2-bmeng.cn@gmail.com> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
2021-03-03 13:26:35 +01:00
bool timeout = false;
s->errintsts = 0;
s->acmd12errsts = 0;
request.cmd = s->cmdreg >> 8;
request.arg = s->argument;
trace_sdhci_send_command(request.cmd, request.arg);
rlen = sdbus_do_command(&s->sdbus, &request, response);
if (s->cmdreg & SDHC_CMD_RESPONSE) {
if (rlen == 4) {
s->rspreg[0] = ldl_be_p(response);
s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0;
trace_sdhci_response4(s->rspreg[0]);
} else if (rlen == 16) {
s->rspreg[0] = ldl_be_p(&response[11]);
s->rspreg[1] = ldl_be_p(&response[7]);
s->rspreg[2] = ldl_be_p(&response[3]);
s->rspreg[3] = (response[0] << 16) | (response[1] << 8) |
response[2];
trace_sdhci_response16(s->rspreg[3], s->rspreg[2],
s->rspreg[1], s->rspreg[0]);
} else {
hw/sd: sdhci: Don't transfer any data when command time out At the end of sdhci_send_command(), it starts a data transfer if the command register indicates data is associated. But the data transfer should only be initiated when the command execution has succeeded. With this fix, the following reproducer: outl 0xcf8 0x80001810 outl 0xcfc 0xe1068000 outl 0xcf8 0x80001804 outw 0xcfc 0x7 write 0xe106802c 0x1 0x0f write 0xe1068004 0xc 0x2801d10101fffffbff28a384 write 0xe106800c 0x1f 0x9dacbbcad9e8f7061524334251606f7e8d9cabbac9d8e7f60514233241505f write 0xe1068003 0x28 0x80d000251480d000252280d000253080d000253e80d000254c80d000255a80d000256880d0002576 write 0xe1068003 0x1 0xfe cannot be reproduced with the following QEMU command line: $ qemu-system-x86_64 -nographic -M pc-q35-5.0 \ -device sdhci-pci,sd-spec-version=3 \ -drive if=sd,index=0,file=null-co://,format=raw,id=mydrive \ -device sd-card,drive=mydrive \ -monitor none -serial none -qtest stdio Cc: qemu-stable@nongnu.org Fixes: CVE-2020-17380 Fixes: CVE-2020-25085 Fixes: CVE-2021-3409 Fixes: d7dfca0807a0 ("hw/sdhci: introduce standard SD host controller") Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Cornelius Aschermann (Ruhr-Universität Bochum) Reported-by: Sergej Schumilo (Ruhr-Universität Bochum) Reported-by: Simon Wörner (Ruhr-Universität Bochum) Buglink: https://bugs.launchpad.net/qemu/+bug/1892960 Buglink: https://bugs.launchpad.net/qemu/+bug/1909418 Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1928146 Acked-by: Alistair Francis <alistair.francis@wdc.com> Tested-by: Alexander Bulekov <alxndr@bu.edu> Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Message-Id: <20210303122639.20004-2-bmeng.cn@gmail.com> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
2021-03-03 13:26:35 +01:00
timeout = true;
trace_sdhci_error("timeout waiting for command response");
if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
s->errintsts |= SDHC_EIS_CMDTIMEOUT;
s->norintsts |= SDHC_NIS_ERR;
}
}
if (!(s->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) &&
(s->norintstsen & SDHC_NISEN_TRSCMP) &&
(s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY) {
s->norintsts |= SDHC_NIS_TRSCMP;
}
}
if (s->norintstsen & SDHC_NISEN_CMDCMP) {
s->norintsts |= SDHC_NIS_CMDCMP;
}
sdhci_update_irq(s);
if (!timeout && (s->blksize & BLOCK_SIZE_MASK) &&
(s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
s->data_count = 0;
sdhci_data_transfer(s);
}
}
static void sdhci_end_transfer(SDHCIState *s)
{
/* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */
if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) {
SDRequest request;
uint8_t response[16];
request.cmd = 0x0C;
request.arg = 0;
trace_sdhci_end_transfer(request.cmd, request.arg);
sdbus_do_command(&s->sdbus, &request, response);
/* Auto CMD12 response goes to the upper Response register */
s->rspreg[3] = ldl_be_p(response);
}
s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE |
SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT |
SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE);
if (s->norintstsen & SDHC_NISEN_TRSCMP) {
s->norintsts |= SDHC_NIS_TRSCMP;
}
sdhci_update_irq(s);
}
/*
* Programmed i/o data transfer
*/
/* Fill host controller's read buffer with BLKSIZE bytes of data from card */
static void sdhci_read_block_from_card(SDHCIState *s)
{
const uint16_t blk_size = s->blksize & BLOCK_SIZE_MASK;
if ((s->trnmod & SDHC_TRNS_MULTI) &&
(s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) {
return;
}
if (!FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, EXECUTE_TUNING)) {
/* Device is not in tuning */
sdbus_read_data(&s->sdbus, s->fifo_buffer, blk_size);
}
if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, EXECUTE_TUNING)) {
/* Device is in tuning */
s->hostctl2 &= ~R_SDHC_HOSTCTL2_EXECUTE_TUNING_MASK;
s->hostctl2 |= R_SDHC_HOSTCTL2_SAMPLING_CLKSEL_MASK;
s->prnsts &= ~(SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ |
SDHC_DATA_INHIBIT);
goto read_done;
}
/* New data now available for READ through Buffer Port Register */
s->prnsts |= SDHC_DATA_AVAILABLE;
if (s->norintstsen & SDHC_NISEN_RBUFRDY) {
s->norintsts |= SDHC_NIS_RBUFRDY;
}
/* Clear DAT line active status if that was the last block */
if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) {
s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
}
/* If stop at block gap request was set and it's not the last block of
* data - generate Block Event interrupt */
if (s->stopped_state == sdhc_gap_read && (s->trnmod & SDHC_TRNS_MULTI) &&
s->blkcnt != 1) {
s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
if (s->norintstsen & SDHC_EISEN_BLKGAP) {
s->norintsts |= SDHC_EIS_BLKGAP;
}
}
read_done:
sdhci_update_irq(s);
}
/* Read @size byte of data from host controller @s BUFFER DATA PORT register */
static uint32_t sdhci_read_dataport(SDHCIState *s, unsigned size)
{
uint32_t value = 0;
int i;
/* first check that a valid data exists in host controller input buffer */
if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0) {
trace_sdhci_error("read from empty buffer");
return 0;
}
for (i = 0; i < size; i++) {
hw/sd/sdhci: Do not update TRNMOD when Command Inhibit (DAT) is set Per "SD Host Controller Standard Specification Version 3.00": * 2.2.5 Transfer Mode Register (Offset 00Ch) Writes to this register shall be ignored when the Command Inhibit (DAT) in the Present State register is 1. Do not update the TRNMOD register when Command Inhibit (DAT) bit is set to avoid the present-status register going out of sync, leading to malicious guest using DMA mode and overflowing the FIFO buffer: $ cat << EOF | qemu-system-i386 \ -display none -nographic -nodefaults \ -machine accel=qtest -m 512M \ -device sdhci-pci,sd-spec-version=3 \ -device sd-card,drive=mydrive \ -drive if=none,index=0,file=null-co://,format=raw,id=mydrive \ -qtest stdio outl 0xcf8 0x80001013 outl 0xcfc 0x91 outl 0xcf8 0x80001001 outl 0xcfc 0x06000000 write 0x9100002c 0x1 0x05 write 0x91000058 0x1 0x16 write 0x91000005 0x1 0x04 write 0x91000028 0x1 0x08 write 0x16 0x1 0x21 write 0x19 0x1 0x20 write 0x9100000c 0x1 0x01 write 0x9100000e 0x1 0x20 write 0x9100000f 0x1 0x00 write 0x9100000c 0x1 0x00 write 0x91000020 0x1 0x00 EOF Stack trace (part): ================================================================= ==89993==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x615000029900 at pc 0x55d5f885700d bp 0x7ffc1e1e9470 sp 0x7ffc1e1e9468 WRITE of size 1 at 0x615000029900 thread T0 #0 0x55d5f885700c in sdhci_write_dataport hw/sd/sdhci.c:564:39 #1 0x55d5f8849150 in sdhci_write hw/sd/sdhci.c:1223:13 #2 0x55d5fa01db63 in memory_region_write_accessor system/memory.c:497:5 #3 0x55d5fa01d245 in access_with_adjusted_size system/memory.c:573:18 #4 0x55d5fa01b1a9 in memory_region_dispatch_write system/memory.c:1521:16 #5 0x55d5fa09f5c9 in flatview_write_continue system/physmem.c:2711:23 #6 0x55d5fa08f78b in flatview_write system/physmem.c:2753:12 #7 0x55d5fa08f258 in address_space_write system/physmem.c:2860:18 ... 0x615000029900 is located 0 bytes to the right of 512-byte region [0x615000029700,0x615000029900) allocated by thread T0 here: #0 0x55d5f7237b27 in __interceptor_calloc #1 0x7f9e36dd4c50 in g_malloc0 #2 0x55d5f88672f7 in sdhci_pci_realize hw/sd/sdhci-pci.c:36:5 #3 0x55d5f844b582 in pci_qdev_realize hw/pci/pci.c:2092:9 #4 0x55d5fa2ee74b in device_set_realized hw/core/qdev.c:510:13 #5 0x55d5fa325bfb in property_set_bool qom/object.c:2358:5 #6 0x55d5fa31ea45 in object_property_set qom/object.c:1472:5 #7 0x55d5fa332509 in object_property_set_qobject om/qom-qobject.c:28:10 #8 0x55d5fa31f6ed in object_property_set_bool qom/object.c:1541:15 #9 0x55d5fa2e2948 in qdev_realize hw/core/qdev.c:292:12 #10 0x55d5f8eed3f1 in qdev_device_add_from_qdict system/qdev-monitor.c:719:10 #11 0x55d5f8eef7ff in qdev_device_add system/qdev-monitor.c:738:11 #12 0x55d5f8f211f0 in device_init_func system/vl.c:1200:11 #13 0x55d5fad0877d in qemu_opts_foreach util/qemu-option.c:1135:14 #14 0x55d5f8f0df9c in qemu_create_cli_devices system/vl.c:2638:5 #15 0x55d5f8f0db24 in qmp_x_exit_preconfig system/vl.c:2706:5 #16 0x55d5f8f14dc0 in qemu_init system/vl.c:3737:9 ... SUMMARY: AddressSanitizer: heap-buffer-overflow hw/sd/sdhci.c:564:39 in sdhci_write_dataport Add assertions to ensure the fifo_buffer[] is not overflowed by malicious accesses to the Buffer Data Port register. Fixes: CVE-2024-3447 Cc: qemu-stable@nongnu.org Fixes: d7dfca0807 ("hw/sdhci: introduce standard SD host controller") Buglink: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=58813 Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Chuhong Yuan <hslester96@gmail.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Message-Id: <CAFEAcA9iLiv1XGTGKeopgMa8Y9+8kvptvsb8z2OBeuy+5=NUfg@mail.gmail.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-Id: <20240409145524.27913-1-philmd@linaro.org>
2024-04-09 16:19:27 +02:00
assert(s->data_count < s->buf_maxsz);
value |= s->fifo_buffer[s->data_count] << i * 8;
s->data_count++;
/* check if we've read all valid data (blksize bytes) from buffer */
if ((s->data_count) >= (s->blksize & BLOCK_SIZE_MASK)) {
trace_sdhci_read_dataport(s->data_count);
s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */
s->data_count = 0; /* next buff read must start at position [0] */
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
s->blkcnt--;
}
/* if that was the last block of data */
if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) ||
/* stop at gap request */
(s->stopped_state == sdhc_gap_read &&
!(s->prnsts & SDHC_DAT_LINE_ACTIVE))) {
sdhci_end_transfer(s);
} else { /* if there are more data, read next block from card */
sdhci_read_block_from_card(s);
}
break;
}
}
return value;
}
/* Write data from host controller FIFO to card */
static void sdhci_write_block_to_card(SDHCIState *s)
{
if (s->prnsts & SDHC_SPACE_AVAILABLE) {
if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
s->norintsts |= SDHC_NIS_WBUFRDY;
}
sdhci_update_irq(s);
return;
}
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
if (s->blkcnt == 0) {
return;
} else {
s->blkcnt--;
}
}
sdbus_write_data(&s->sdbus, s->fifo_buffer, s->blksize & BLOCK_SIZE_MASK);
/* Next data can be written through BUFFER DATORT register */
s->prnsts |= SDHC_SPACE_AVAILABLE;
/* Finish transfer if that was the last block of data */
if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
((s->trnmod & SDHC_TRNS_MULTI) &&
(s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
sdhci_end_transfer(s);
} else if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
s->norintsts |= SDHC_NIS_WBUFRDY;
}
/* Generate Block Gap Event if requested and if not the last block */
if (s->stopped_state == sdhc_gap_write && (s->trnmod & SDHC_TRNS_MULTI) &&
s->blkcnt > 0) {
s->prnsts &= ~SDHC_DOING_WRITE;
if (s->norintstsen & SDHC_EISEN_BLKGAP) {
s->norintsts |= SDHC_EIS_BLKGAP;
}
sdhci_end_transfer(s);
}
sdhci_update_irq(s);
}
/* Write @size bytes of @value data to host controller @s Buffer Data Port
* register */
static void sdhci_write_dataport(SDHCIState *s, uint32_t value, unsigned size)
{
unsigned i;
/* Check that there is free space left in a buffer */
if (!(s->prnsts & SDHC_SPACE_AVAILABLE)) {
trace_sdhci_error("Can't write to data buffer: buffer full");
return;
}
for (i = 0; i < size; i++) {
hw/sd/sdhci: Do not update TRNMOD when Command Inhibit (DAT) is set Per "SD Host Controller Standard Specification Version 3.00": * 2.2.5 Transfer Mode Register (Offset 00Ch) Writes to this register shall be ignored when the Command Inhibit (DAT) in the Present State register is 1. Do not update the TRNMOD register when Command Inhibit (DAT) bit is set to avoid the present-status register going out of sync, leading to malicious guest using DMA mode and overflowing the FIFO buffer: $ cat << EOF | qemu-system-i386 \ -display none -nographic -nodefaults \ -machine accel=qtest -m 512M \ -device sdhci-pci,sd-spec-version=3 \ -device sd-card,drive=mydrive \ -drive if=none,index=0,file=null-co://,format=raw,id=mydrive \ -qtest stdio outl 0xcf8 0x80001013 outl 0xcfc 0x91 outl 0xcf8 0x80001001 outl 0xcfc 0x06000000 write 0x9100002c 0x1 0x05 write 0x91000058 0x1 0x16 write 0x91000005 0x1 0x04 write 0x91000028 0x1 0x08 write 0x16 0x1 0x21 write 0x19 0x1 0x20 write 0x9100000c 0x1 0x01 write 0x9100000e 0x1 0x20 write 0x9100000f 0x1 0x00 write 0x9100000c 0x1 0x00 write 0x91000020 0x1 0x00 EOF Stack trace (part): ================================================================= ==89993==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x615000029900 at pc 0x55d5f885700d bp 0x7ffc1e1e9470 sp 0x7ffc1e1e9468 WRITE of size 1 at 0x615000029900 thread T0 #0 0x55d5f885700c in sdhci_write_dataport hw/sd/sdhci.c:564:39 #1 0x55d5f8849150 in sdhci_write hw/sd/sdhci.c:1223:13 #2 0x55d5fa01db63 in memory_region_write_accessor system/memory.c:497:5 #3 0x55d5fa01d245 in access_with_adjusted_size system/memory.c:573:18 #4 0x55d5fa01b1a9 in memory_region_dispatch_write system/memory.c:1521:16 #5 0x55d5fa09f5c9 in flatview_write_continue system/physmem.c:2711:23 #6 0x55d5fa08f78b in flatview_write system/physmem.c:2753:12 #7 0x55d5fa08f258 in address_space_write system/physmem.c:2860:18 ... 0x615000029900 is located 0 bytes to the right of 512-byte region [0x615000029700,0x615000029900) allocated by thread T0 here: #0 0x55d5f7237b27 in __interceptor_calloc #1 0x7f9e36dd4c50 in g_malloc0 #2 0x55d5f88672f7 in sdhci_pci_realize hw/sd/sdhci-pci.c:36:5 #3 0x55d5f844b582 in pci_qdev_realize hw/pci/pci.c:2092:9 #4 0x55d5fa2ee74b in device_set_realized hw/core/qdev.c:510:13 #5 0x55d5fa325bfb in property_set_bool qom/object.c:2358:5 #6 0x55d5fa31ea45 in object_property_set qom/object.c:1472:5 #7 0x55d5fa332509 in object_property_set_qobject om/qom-qobject.c:28:10 #8 0x55d5fa31f6ed in object_property_set_bool qom/object.c:1541:15 #9 0x55d5fa2e2948 in qdev_realize hw/core/qdev.c:292:12 #10 0x55d5f8eed3f1 in qdev_device_add_from_qdict system/qdev-monitor.c:719:10 #11 0x55d5f8eef7ff in qdev_device_add system/qdev-monitor.c:738:11 #12 0x55d5f8f211f0 in device_init_func system/vl.c:1200:11 #13 0x55d5fad0877d in qemu_opts_foreach util/qemu-option.c:1135:14 #14 0x55d5f8f0df9c in qemu_create_cli_devices system/vl.c:2638:5 #15 0x55d5f8f0db24 in qmp_x_exit_preconfig system/vl.c:2706:5 #16 0x55d5f8f14dc0 in qemu_init system/vl.c:3737:9 ... SUMMARY: AddressSanitizer: heap-buffer-overflow hw/sd/sdhci.c:564:39 in sdhci_write_dataport Add assertions to ensure the fifo_buffer[] is not overflowed by malicious accesses to the Buffer Data Port register. Fixes: CVE-2024-3447 Cc: qemu-stable@nongnu.org Fixes: d7dfca0807 ("hw/sdhci: introduce standard SD host controller") Buglink: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=58813 Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Chuhong Yuan <hslester96@gmail.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Message-Id: <CAFEAcA9iLiv1XGTGKeopgMa8Y9+8kvptvsb8z2OBeuy+5=NUfg@mail.gmail.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-Id: <20240409145524.27913-1-philmd@linaro.org>
2024-04-09 16:19:27 +02:00
assert(s->data_count < s->buf_maxsz);
s->fifo_buffer[s->data_count] = value & 0xFF;
s->data_count++;
value >>= 8;
if (s->data_count >= (s->blksize & BLOCK_SIZE_MASK)) {
trace_sdhci_write_dataport(s->data_count);
s->data_count = 0;
s->prnsts &= ~SDHC_SPACE_AVAILABLE;
if (s->prnsts & SDHC_DOING_WRITE) {
sdhci_write_block_to_card(s);
}
}
}
}
/*
* Single DMA data transfer
*/
/* Multi block SDMA transfer */
static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s)
{
bool page_aligned = false;
unsigned int begin;
const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
uint32_t boundary_chk = 1 << (((s->blksize & ~BLOCK_SIZE_MASK) >> 12) + 12);
uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk);
if (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || !s->blkcnt) {
qemu_log_mask(LOG_UNIMP, "infinite transfer is not supported\n");
return;
}
/* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for
* possible stop at page boundary if initial address is not page aligned,
* allow them to work properly */
if ((s->sdmasysad % boundary_chk) == 0) {
page_aligned = true;
}
s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
if (s->trnmod & SDHC_TRNS_READ) {
s->prnsts |= SDHC_DOING_READ;
while (s->blkcnt) {
if (s->data_count == 0) {
sdbus_read_data(&s->sdbus, s->fifo_buffer, block_size);
}
begin = s->data_count;
if (((boundary_count + begin) < block_size) && page_aligned) {
s->data_count = boundary_count + begin;
boundary_count = 0;
} else {
s->data_count = block_size;
boundary_count -= block_size - begin;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
s->blkcnt--;
}
}
dma_memory_write(s->dma_as, s->sdmasysad, &s->fifo_buffer[begin],
s->data_count - begin, MEMTXATTRS_UNSPECIFIED);
s->sdmasysad += s->data_count - begin;
if (s->data_count == block_size) {
s->data_count = 0;
}
if (page_aligned && boundary_count == 0) {
break;
}
}
} else {
s->prnsts |= SDHC_DOING_WRITE;
while (s->blkcnt) {
begin = s->data_count;
if (((boundary_count + begin) < block_size) && page_aligned) {
s->data_count = boundary_count + begin;
boundary_count = 0;
} else {
s->data_count = block_size;
boundary_count -= block_size - begin;
}
dma_memory_read(s->dma_as, s->sdmasysad, &s->fifo_buffer[begin],
s->data_count - begin, MEMTXATTRS_UNSPECIFIED);
s->sdmasysad += s->data_count - begin;
if (s->data_count == block_size) {
sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size);
s->data_count = 0;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
s->blkcnt--;
}
}
if (page_aligned && boundary_count == 0) {
break;
}
}
}
if (s->blkcnt == 0) {
sdhci_end_transfer(s);
} else {
if (s->norintstsen & SDHC_NISEN_DMA) {
s->norintsts |= SDHC_NIS_DMA;
}
sdhci_update_irq(s);
}
}
/* single block SDMA transfer */
static void sdhci_sdma_transfer_single_block(SDHCIState *s)
{
uint32_t datacnt = s->blksize & BLOCK_SIZE_MASK;
if (s->trnmod & SDHC_TRNS_READ) {
sdbus_read_data(&s->sdbus, s->fifo_buffer, datacnt);
dma_memory_write(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt,
MEMTXATTRS_UNSPECIFIED);
} else {
dma_memory_read(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt,
MEMTXATTRS_UNSPECIFIED);
sdbus_write_data(&s->sdbus, s->fifo_buffer, datacnt);
}
s->blkcnt--;
sdhci_end_transfer(s);
}
typedef struct ADMADescr {
hwaddr addr;
uint16_t length;
uint8_t attr;
uint8_t incr;
} ADMADescr;
static void get_adma_description(SDHCIState *s, ADMADescr *dscr)
{
uint32_t adma1 = 0;
uint64_t adma2 = 0;
hwaddr entry_addr = (hwaddr)s->admasysaddr;
switch (SDHC_DMA_TYPE(s->hostctl1)) {
case SDHC_CTRL_ADMA2_32:
dma_memory_read(s->dma_as, entry_addr, &adma2, sizeof(adma2),
MEMTXATTRS_UNSPECIFIED);
adma2 = le64_to_cpu(adma2);
/* The spec does not specify endianness of descriptor table.
* We currently assume that it is LE.
*/
dscr->addr = (hwaddr)extract64(adma2, 32, 32) & ~0x3ull;
dscr->length = (uint16_t)extract64(adma2, 16, 16);
dscr->attr = (uint8_t)extract64(adma2, 0, 7);
dscr->incr = 8;
break;
case SDHC_CTRL_ADMA1_32:
dma_memory_read(s->dma_as, entry_addr, &adma1, sizeof(adma1),
MEMTXATTRS_UNSPECIFIED);
adma1 = le32_to_cpu(adma1);
dscr->addr = (hwaddr)(adma1 & 0xFFFFF000);
dscr->attr = (uint8_t)extract32(adma1, 0, 7);
dscr->incr = 4;
if ((dscr->attr & SDHC_ADMA_ATTR_ACT_MASK) == SDHC_ADMA_ATTR_SET_LEN) {
dscr->length = (uint16_t)extract32(adma1, 12, 16);
} else {
dscr->length = 4 * KiB;
}
break;
case SDHC_CTRL_ADMA2_64:
dma_memory_read(s->dma_as, entry_addr, &dscr->attr, 1,
MEMTXATTRS_UNSPECIFIED);
dma_memory_read(s->dma_as, entry_addr + 2, &dscr->length, 2,
MEMTXATTRS_UNSPECIFIED);
dscr->length = le16_to_cpu(dscr->length);
dma_memory_read(s->dma_as, entry_addr + 4, &dscr->addr, 8,
MEMTXATTRS_UNSPECIFIED);
dscr->addr = le64_to_cpu(dscr->addr);
dscr->attr &= (uint8_t) ~0xC0;
dscr->incr = 12;
break;
}
}
/* Advanced DMA data transfer */
static void sdhci_do_adma(SDHCIState *s)
{
unsigned int begin, length;
const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
hw/sd/sdhci: Prohibit DMA accesses to devices The issue reported by OSS-Fuzz produces the following backtrace: ==447470==ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 at 0x61500002a080 thread T0 #0 0x71766d47 in sdhci_read_dataport hw/sd/sdhci.c:474:18 #1 0x7175f139 in sdhci_read hw/sd/sdhci.c:1022:19 #2 0x721b937b in memory_region_read_accessor softmmu/memory.c:440:11 #3 0x72171e51 in access_with_adjusted_size softmmu/memory.c:554:18 #4 0x7216f47c in memory_region_dispatch_read1 softmmu/memory.c:1424:16 #5 0x7216ebb9 in memory_region_dispatch_read softmmu/memory.c:1452:9 #6 0x7212db5d in flatview_read_continue softmmu/physmem.c:2879:23 #7 0x7212f958 in flatview_read softmmu/physmem.c:2921:12 #8 0x7212f418 in address_space_read_full softmmu/physmem.c:2934:18 #9 0x721305a9 in address_space_rw softmmu/physmem.c:2962:16 #10 0x7175a392 in dma_memory_rw_relaxed include/sysemu/dma.h:89:12 #11 0x7175a0ea in dma_memory_rw include/sysemu/dma.h:132:12 #12 0x71759684 in dma_memory_read include/sysemu/dma.h:152:12 #13 0x7175518c in sdhci_do_adma hw/sd/sdhci.c:823:27 #14 0x7174bf69 in sdhci_data_transfer hw/sd/sdhci.c:935:13 #15 0x7176aaa7 in sdhci_send_command hw/sd/sdhci.c:376:9 #16 0x717629ee in sdhci_write hw/sd/sdhci.c:1212:9 #17 0x72172513 in memory_region_write_accessor softmmu/memory.c:492:5 #18 0x72171e51 in access_with_adjusted_size softmmu/memory.c:554:18 #19 0x72170766 in memory_region_dispatch_write softmmu/memory.c:1504:16 #20 0x721419ee in flatview_write_continue softmmu/physmem.c:2812:23 #21 0x721301eb in flatview_write softmmu/physmem.c:2854:12 #22 0x7212fca8 in address_space_write softmmu/physmem.c:2950:18 #23 0x721d9a53 in qtest_process_command softmmu/qtest.c:727:9 A DMA descriptor is previously filled in RAM. An I/O access to the device (frames #22 to #16) start the DMA engine (frame #13). The engine fetch the descriptor and execute the request, which itself accesses the SDHCI I/O registers (frame #1 and #0), triggering a re-entrancy issue. Fix by prohibit transactions from the DMA to devices. The DMA engine is thus restricted to memories. Reported-by: OSS-Fuzz (Issue 36391) Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/451 Message-Id: <20211215205656.488940-3-philmd@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
2021-12-15 21:56:55 +01:00
const MemTxAttrs attrs = { .memory = true };
ADMADescr dscr = {};
MemTxResult res;
int i;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN && !s->blkcnt) {
/* Stop Multiple Transfer */
sdhci_end_transfer(s);
return;
}
for (i = 0; i < SDHC_ADMA_DESCS_PER_DELAY; ++i) {
s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH;
get_adma_description(s, &dscr);
trace_sdhci_adma_loop(dscr.addr, dscr.length, dscr.attr);
if ((dscr.attr & SDHC_ADMA_ATTR_VALID) == 0) {
/* Indicate that error occurred in ST_FDS state */
s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
/* Generate ADMA error interrupt */
if (s->errintstsen & SDHC_EISEN_ADMAERR) {
s->errintsts |= SDHC_EIS_ADMAERR;
s->norintsts |= SDHC_NIS_ERR;
}
sdhci_update_irq(s);
return;
}
length = dscr.length ? dscr.length : 64 * KiB;
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);
}
begin = s->data_count;
if ((length + begin) < block_size) {
s->data_count = length + begin;
length = 0;
} else {
s->data_count = block_size;
length -= block_size - begin;
}
res = dma_memory_write(s->dma_as, dscr.addr,
&s->fifo_buffer[begin],
s->data_count - begin,
hw/sd/sdhci: Prohibit DMA accesses to devices The issue reported by OSS-Fuzz produces the following backtrace: ==447470==ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 at 0x61500002a080 thread T0 #0 0x71766d47 in sdhci_read_dataport hw/sd/sdhci.c:474:18 #1 0x7175f139 in sdhci_read hw/sd/sdhci.c:1022:19 #2 0x721b937b in memory_region_read_accessor softmmu/memory.c:440:11 #3 0x72171e51 in access_with_adjusted_size softmmu/memory.c:554:18 #4 0x7216f47c in memory_region_dispatch_read1 softmmu/memory.c:1424:16 #5 0x7216ebb9 in memory_region_dispatch_read softmmu/memory.c:1452:9 #6 0x7212db5d in flatview_read_continue softmmu/physmem.c:2879:23 #7 0x7212f958 in flatview_read softmmu/physmem.c:2921:12 #8 0x7212f418 in address_space_read_full softmmu/physmem.c:2934:18 #9 0x721305a9 in address_space_rw softmmu/physmem.c:2962:16 #10 0x7175a392 in dma_memory_rw_relaxed include/sysemu/dma.h:89:12 #11 0x7175a0ea in dma_memory_rw include/sysemu/dma.h:132:12 #12 0x71759684 in dma_memory_read include/sysemu/dma.h:152:12 #13 0x7175518c in sdhci_do_adma hw/sd/sdhci.c:823:27 #14 0x7174bf69 in sdhci_data_transfer hw/sd/sdhci.c:935:13 #15 0x7176aaa7 in sdhci_send_command hw/sd/sdhci.c:376:9 #16 0x717629ee in sdhci_write hw/sd/sdhci.c:1212:9 #17 0x72172513 in memory_region_write_accessor softmmu/memory.c:492:5 #18 0x72171e51 in access_with_adjusted_size softmmu/memory.c:554:18 #19 0x72170766 in memory_region_dispatch_write softmmu/memory.c:1504:16 #20 0x721419ee in flatview_write_continue softmmu/physmem.c:2812:23 #21 0x721301eb in flatview_write softmmu/physmem.c:2854:12 #22 0x7212fca8 in address_space_write softmmu/physmem.c:2950:18 #23 0x721d9a53 in qtest_process_command softmmu/qtest.c:727:9 A DMA descriptor is previously filled in RAM. An I/O access to the device (frames #22 to #16) start the DMA engine (frame #13). The engine fetch the descriptor and execute the request, which itself accesses the SDHCI I/O registers (frame #1 and #0), triggering a re-entrancy issue. Fix by prohibit transactions from the DMA to devices. The DMA engine is thus restricted to memories. Reported-by: OSS-Fuzz (Issue 36391) Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/451 Message-Id: <20211215205656.488940-3-philmd@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
2021-12-15 21:56:55 +01:00
attrs);
if (res != MEMTX_OK) {
break;
}
dscr.addr += s->data_count - begin;
if (s->data_count == block_size) {
s->data_count = 0;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
s->blkcnt--;
if (s->blkcnt == 0) {
break;
}
}
}
}
} else {
s->prnsts |= SDHC_DOING_WRITE;
while (length) {
begin = s->data_count;
if ((length + begin) < block_size) {
s->data_count = length + begin;
length = 0;
} else {
s->data_count = block_size;
length -= block_size - begin;
}
res = dma_memory_read(s->dma_as, dscr.addr,
&s->fifo_buffer[begin],
s->data_count - begin,
hw/sd/sdhci: Prohibit DMA accesses to devices The issue reported by OSS-Fuzz produces the following backtrace: ==447470==ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 at 0x61500002a080 thread T0 #0 0x71766d47 in sdhci_read_dataport hw/sd/sdhci.c:474:18 #1 0x7175f139 in sdhci_read hw/sd/sdhci.c:1022:19 #2 0x721b937b in memory_region_read_accessor softmmu/memory.c:440:11 #3 0x72171e51 in access_with_adjusted_size softmmu/memory.c:554:18 #4 0x7216f47c in memory_region_dispatch_read1 softmmu/memory.c:1424:16 #5 0x7216ebb9 in memory_region_dispatch_read softmmu/memory.c:1452:9 #6 0x7212db5d in flatview_read_continue softmmu/physmem.c:2879:23 #7 0x7212f958 in flatview_read softmmu/physmem.c:2921:12 #8 0x7212f418 in address_space_read_full softmmu/physmem.c:2934:18 #9 0x721305a9 in address_space_rw softmmu/physmem.c:2962:16 #10 0x7175a392 in dma_memory_rw_relaxed include/sysemu/dma.h:89:12 #11 0x7175a0ea in dma_memory_rw include/sysemu/dma.h:132:12 #12 0x71759684 in dma_memory_read include/sysemu/dma.h:152:12 #13 0x7175518c in sdhci_do_adma hw/sd/sdhci.c:823:27 #14 0x7174bf69 in sdhci_data_transfer hw/sd/sdhci.c:935:13 #15 0x7176aaa7 in sdhci_send_command hw/sd/sdhci.c:376:9 #16 0x717629ee in sdhci_write hw/sd/sdhci.c:1212:9 #17 0x72172513 in memory_region_write_accessor softmmu/memory.c:492:5 #18 0x72171e51 in access_with_adjusted_size softmmu/memory.c:554:18 #19 0x72170766 in memory_region_dispatch_write softmmu/memory.c:1504:16 #20 0x721419ee in flatview_write_continue softmmu/physmem.c:2812:23 #21 0x721301eb in flatview_write softmmu/physmem.c:2854:12 #22 0x7212fca8 in address_space_write softmmu/physmem.c:2950:18 #23 0x721d9a53 in qtest_process_command softmmu/qtest.c:727:9 A DMA descriptor is previously filled in RAM. An I/O access to the device (frames #22 to #16) start the DMA engine (frame #13). The engine fetch the descriptor and execute the request, which itself accesses the SDHCI I/O registers (frame #1 and #0), triggering a re-entrancy issue. Fix by prohibit transactions from the DMA to devices. The DMA engine is thus restricted to memories. Reported-by: OSS-Fuzz (Issue 36391) Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/451 Message-Id: <20211215205656.488940-3-philmd@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
2021-12-15 21:56:55 +01:00
attrs);
if (res != MEMTX_OK) {
break;
}
dscr.addr += s->data_count - begin;
if (s->data_count == block_size) {
sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size);
s->data_count = 0;
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
s->blkcnt--;
if (s->blkcnt == 0) {
break;
}
}
}
}
}
if (res != MEMTX_OK) {
if (s->errintstsen & SDHC_EISEN_ADMAERR) {
trace_sdhci_error("Set ADMA error flag");
s->errintsts |= SDHC_EIS_ADMAERR;
s->norintsts |= SDHC_NIS_ERR;
}
sdhci_update_irq(s);
} else {
s->admasysaddr += dscr.incr;
}
break;
case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */
s->admasysaddr = dscr.addr;
trace_sdhci_adma("link", s->admasysaddr);
break;
default:
s->admasysaddr += dscr.incr;
break;
}
if (dscr.attr & SDHC_ADMA_ATTR_INT) {
trace_sdhci_adma("interrupt", s->admasysaddr);
if (s->norintstsen & SDHC_NISEN_DMA) {
s->norintsts |= SDHC_NIS_DMA;
}
if (sdhci_update_irq(s) && !(dscr.attr & SDHC_ADMA_ATTR_END)) {
/* IRQ delivered, reschedule current transfer */
break;
}
}
/* ADMA transfer terminates if blkcnt == 0 or by END attribute */
if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
(s->blkcnt == 0)) || (dscr.attr & SDHC_ADMA_ATTR_END)) {
trace_sdhci_adma_transfer_completed();
if (length || ((dscr.attr & SDHC_ADMA_ATTR_END) &&
(s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
s->blkcnt != 0)) {
trace_sdhci_error("SD/MMC host ADMA length mismatch");
s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
SDHC_ADMAERR_STATE_ST_TFR;
if (s->errintstsen & SDHC_EISEN_ADMAERR) {
trace_sdhci_error("Set ADMA error flag");
s->errintsts |= SDHC_EIS_ADMAERR;
s->norintsts |= SDHC_NIS_ERR;
}
sdhci_update_irq(s);
}
sdhci_end_transfer(s);
return;
}
}
/* we have unfinished business - reschedule to continue ADMA */
timer_mod(s->transfer_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY);
}
/* Perform data transfer according to controller configuration */
static void sdhci_data_transfer(void *opaque)
{
SDHCIState *s = (SDHCIState *)opaque;
if (s->trnmod & SDHC_TRNS_DMA) {
switch (SDHC_DMA_TYPE(s->hostctl1)) {
case SDHC_CTRL_SDMA:
if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) {
sdhci_sdma_transfer_single_block(s);
} else {
sdhci_sdma_transfer_multi_blocks(s);
}
break;
case SDHC_CTRL_ADMA1_32:
if (!(s->capareg & R_SDHC_CAPAB_ADMA1_MASK)) {
trace_sdhci_error("ADMA1 not supported");
break;
}
sdhci_do_adma(s);
break;
case SDHC_CTRL_ADMA2_32:
if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) {
trace_sdhci_error("ADMA2 not supported");
break;
}
sdhci_do_adma(s);
break;
case SDHC_CTRL_ADMA2_64:
if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) ||
!(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) {
trace_sdhci_error("64 bit ADMA not supported");
break;
}
sdhci_do_adma(s);
break;
default:
trace_sdhci_error("Unsupported DMA type");
break;
}
} else {
if ((s->trnmod & SDHC_TRNS_READ) && sdbus_data_ready(&s->sdbus)) {
s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
SDHC_DAT_LINE_ACTIVE;
sdhci_read_block_from_card(s);
} else {
s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE |
SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT;
sdhci_write_block_to_card(s);
}
}
}
static bool sdhci_can_issue_command(SDHCIState *s)
{
if (!SDHC_CLOCK_IS_ON(s->clkcon) ||
(((s->prnsts & SDHC_DATA_INHIBIT) || s->stopped_state) &&
((s->cmdreg & SDHC_CMD_DATA_PRESENT) ||
((s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY &&
!(SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT))))) {
return false;
}
return true;
}
/* The Buffer Data Port register must be accessed in sequential and
* continuous manner */
static inline bool
sdhci_buff_access_is_sequential(SDHCIState *s, unsigned byte_num)
{
if ((s->data_count & 0x3) != byte_num) {
trace_sdhci_error("Non-sequential access to Buffer Data Port register"
"is prohibited\n");
return false;
}
return true;
}
static void sdhci_resume_pending_transfer(SDHCIState *s)
{
timer_del(s->transfer_timer);
sdhci_data_transfer(s);
}
static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size)
{
SDHCIState *s = (SDHCIState *)opaque;
uint32_t ret = 0;
if (timer_pending(s->transfer_timer)) {
sdhci_resume_pending_transfer(s);
}
switch (offset & ~0x3) {
case SDHC_SYSAD:
ret = s->sdmasysad;
break;
case SDHC_BLKSIZE:
ret = s->blksize | (s->blkcnt << 16);
break;
case SDHC_ARGUMENT:
ret = s->argument;
break;
case SDHC_TRNMOD:
ret = s->trnmod | (s->cmdreg << 16);
break;
case SDHC_RSPREG0 ... SDHC_RSPREG3:
ret = s->rspreg[((offset & ~0x3) - SDHC_RSPREG0) >> 2];
break;
case SDHC_BDATA:
if (sdhci_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
ret = sdhci_read_dataport(s, size);
trace_sdhci_access("rd", size << 3, offset, "->", ret, ret);
return ret;
}
break;
case SDHC_PRNSTS:
ret = s->prnsts;
ret = FIELD_DP32(ret, SDHC_PRNSTS, DAT_LVL,
sdbus_get_dat_lines(&s->sdbus));
ret = FIELD_DP32(ret, SDHC_PRNSTS, CMD_LVL,
sdbus_get_cmd_line(&s->sdbus));
break;
case SDHC_HOSTCTL:
ret = s->hostctl1 | (s->pwrcon << 8) | (s->blkgap << 16) |
(s->wakcon << 24);
break;
case SDHC_CLKCON:
ret = s->clkcon | (s->timeoutcon << 16);
break;
case SDHC_NORINTSTS:
ret = s->norintsts | (s->errintsts << 16);
break;
case SDHC_NORINTSTSEN:
ret = s->norintstsen | (s->errintstsen << 16);
break;
case SDHC_NORINTSIGEN:
ret = s->norintsigen | (s->errintsigen << 16);
break;
case SDHC_ACMD12ERRSTS:
ret = s->acmd12errsts | (s->hostctl2 << 16);
break;
case SDHC_CAPAB:
ret = (uint32_t)s->capareg;
break;
case SDHC_CAPAB + 4:
ret = (uint32_t)(s->capareg >> 32);
break;
case SDHC_MAXCURR:
ret = (uint32_t)s->maxcurr;
break;
case SDHC_MAXCURR + 4:
ret = (uint32_t)(s->maxcurr >> 32);
break;
case SDHC_ADMAERR:
ret = s->admaerr;
break;
case SDHC_ADMASYSADDR:
ret = (uint32_t)s->admasysaddr;
break;
case SDHC_ADMASYSADDR + 4:
ret = (uint32_t)(s->admasysaddr >> 32);
break;
case SDHC_SLOT_INT_STATUS:
ret = (s->version << 16) | sdhci_slotint(s);
break;
default:
qemu_log_mask(LOG_UNIMP, "SDHC rd_%ub @0x%02" HWADDR_PRIx " "
"not implemented\n", size, offset);
break;
}
ret >>= (offset & 0x3) * 8;
ret &= (1ULL << (size * 8)) - 1;
trace_sdhci_access("rd", size << 3, offset, "->", ret, ret);
return ret;
}
static inline void sdhci_blkgap_write(SDHCIState *s, uint8_t value)
{
if ((value & SDHC_STOP_AT_GAP_REQ) && (s->blkgap & SDHC_STOP_AT_GAP_REQ)) {
return;
}
s->blkgap = value & SDHC_STOP_AT_GAP_REQ;
if ((value & SDHC_CONTINUE_REQ) && s->stopped_state &&
(s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) {
if (s->stopped_state == sdhc_gap_read) {
s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ;
sdhci_read_block_from_card(s);
} else {
s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE;
sdhci_write_block_to_card(s);
}
s->stopped_state = sdhc_not_stopped;
} else if (!s->stopped_state && (value & SDHC_STOP_AT_GAP_REQ)) {
if (s->prnsts & SDHC_DOING_READ) {
s->stopped_state = sdhc_gap_read;
} else if (s->prnsts & SDHC_DOING_WRITE) {
s->stopped_state = sdhc_gap_write;
}
}
}
static inline void sdhci_reset_write(SDHCIState *s, uint8_t value)
{
switch (value) {
case SDHC_RESET_ALL:
sdhci_reset(s);
break;
case SDHC_RESET_CMD:
s->prnsts &= ~SDHC_CMD_INHIBIT;
s->norintsts &= ~SDHC_NIS_CMDCMP;
break;
case SDHC_RESET_DATA:
s->data_count = 0;
s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE |
SDHC_DOING_READ | SDHC_DOING_WRITE |
SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE);
s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ);
s->stopped_state = sdhc_not_stopped;
s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
break;
}
}
static void
sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
SDHCIState *s = (SDHCIState *)opaque;
unsigned shift = 8 * (offset & 0x3);
uint32_t mask = ~(((1ULL << (size * 8)) - 1) << shift);
uint32_t value = val;
value <<= shift;
if (timer_pending(s->transfer_timer)) {
sdhci_resume_pending_transfer(s);
}
switch (offset & ~0x3) {
case SDHC_SYSAD:
hw/sd: sdhci: Don't write to SDHC_SYSAD register when transfer is in progress Per "SD Host Controller Standard Specification Version 7.00" chapter 2.2.1 SDMA System Address Register: This register can be accessed only if no transaction is executing (i.e., after a transaction has stopped). With this fix, the following reproducer: outl 0xcf8 0x80001010 outl 0xcfc 0xfbefff00 outl 0xcf8 0x80001001 outl 0xcfc 0x06000000 write 0xfbefff2c 0x1 0x05 write 0xfbefff0f 0x1 0x37 write 0xfbefff0a 0x1 0x01 write 0xfbefff0f 0x1 0x29 write 0xfbefff0f 0x1 0x02 write 0xfbefff0f 0x1 0x03 write 0xfbefff04 0x1 0x01 write 0xfbefff05 0x1 0x01 write 0xfbefff07 0x1 0x02 write 0xfbefff0c 0x1 0x33 write 0xfbefff0e 0x1 0x20 write 0xfbefff0f 0x1 0x00 write 0xfbefff2a 0x1 0x01 write 0xfbefff0c 0x1 0x00 write 0xfbefff03 0x1 0x00 write 0xfbefff05 0x1 0x00 write 0xfbefff2a 0x1 0x02 write 0xfbefff0c 0x1 0x32 write 0xfbefff01 0x1 0x01 write 0xfbefff02 0x1 0x01 write 0xfbefff03 0x1 0x01 cannot be reproduced with the following QEMU command line: $ qemu-system-x86_64 -nographic -machine accel=qtest -m 512M \ -nodefaults -device sdhci-pci,sd-spec-version=3 \ -drive if=sd,index=0,file=null-co://,format=raw,id=mydrive \ -device sd-card,drive=mydrive -qtest stdio Cc: qemu-stable@nongnu.org Fixes: CVE-2020-17380 Fixes: CVE-2020-25085 Fixes: CVE-2021-3409 Fixes: d7dfca0807a0 ("hw/sdhci: introduce standard SD host controller") Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Cornelius Aschermann (Ruhr-Universität Bochum) Reported-by: Sergej Schumilo (Ruhr-Universität Bochum) Reported-by: Simon Wörner (Ruhr-Universität Bochum) Buglink: https://bugs.launchpad.net/qemu/+bug/1892960 Buglink: https://bugs.launchpad.net/qemu/+bug/1909418 Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1928146 Tested-by: Alexander Bulekov <alxndr@bu.edu> Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Message-Id: <20210303122639.20004-3-bmeng.cn@gmail.com> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
2021-03-03 13:26:36 +01:00
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 & BLOCK_SIZE_MASK) &&
hw/sd: sdhci: Don't write to SDHC_SYSAD register when transfer is in progress Per "SD Host Controller Standard Specification Version 7.00" chapter 2.2.1 SDMA System Address Register: This register can be accessed only if no transaction is executing (i.e., after a transaction has stopped). With this fix, the following reproducer: outl 0xcf8 0x80001010 outl 0xcfc 0xfbefff00 outl 0xcf8 0x80001001 outl 0xcfc 0x06000000 write 0xfbefff2c 0x1 0x05 write 0xfbefff0f 0x1 0x37 write 0xfbefff0a 0x1 0x01 write 0xfbefff0f 0x1 0x29 write 0xfbefff0f 0x1 0x02 write 0xfbefff0f 0x1 0x03 write 0xfbefff04 0x1 0x01 write 0xfbefff05 0x1 0x01 write 0xfbefff07 0x1 0x02 write 0xfbefff0c 0x1 0x33 write 0xfbefff0e 0x1 0x20 write 0xfbefff0f 0x1 0x00 write 0xfbefff2a 0x1 0x01 write 0xfbefff0c 0x1 0x00 write 0xfbefff03 0x1 0x00 write 0xfbefff05 0x1 0x00 write 0xfbefff2a 0x1 0x02 write 0xfbefff0c 0x1 0x32 write 0xfbefff01 0x1 0x01 write 0xfbefff02 0x1 0x01 write 0xfbefff03 0x1 0x01 cannot be reproduced with the following QEMU command line: $ qemu-system-x86_64 -nographic -machine accel=qtest -m 512M \ -nodefaults -device sdhci-pci,sd-spec-version=3 \ -drive if=sd,index=0,file=null-co://,format=raw,id=mydrive \ -device sd-card,drive=mydrive -qtest stdio Cc: qemu-stable@nongnu.org Fixes: CVE-2020-17380 Fixes: CVE-2020-25085 Fixes: CVE-2021-3409 Fixes: d7dfca0807a0 ("hw/sdhci: introduce standard SD host controller") Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Cornelius Aschermann (Ruhr-Universität Bochum) Reported-by: Sergej Schumilo (Ruhr-Universität Bochum) Reported-by: Simon Wörner (Ruhr-Universität Bochum) Buglink: https://bugs.launchpad.net/qemu/+bug/1892960 Buglink: https://bugs.launchpad.net/qemu/+bug/1909418 Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1928146 Tested-by: Alexander Bulekov <alxndr@bu.edu> Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Message-Id: <20210303122639.20004-3-bmeng.cn@gmail.com> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
2021-03-03 13:26:36 +01:00
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)) {
hw/sd: sdhci: Reset the data pointer of s->fifo_buffer[] when a different block size is programmed 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. With this fix, the following reproducer: outl 0xcf8 0x80001010 outl 0xcfc 0xe0000000 outl 0xcf8 0x80001001 outl 0xcfc 0x06000000 write 0xe000002c 0x1 0x05 write 0xe0000005 0x1 0x02 write 0xe0000007 0x1 0x01 write 0xe0000028 0x1 0x10 write 0x0 0x1 0x23 write 0x2 0x1 0x08 write 0xe000000c 0x1 0x01 write 0xe000000e 0x1 0x20 write 0xe000000f 0x1 0x00 write 0xe000000c 0x1 0x32 write 0xe0000004 0x2 0x0200 write 0xe0000028 0x1 0x00 write 0xe0000003 0x1 0x40 cannot be reproduced with the following QEMU command line: $ qemu-system-x86_64 -nographic -machine accel=qtest -m 512M \ -nodefaults -device sdhci-pci,sd-spec-version=3 \ -drive if=sd,index=0,file=null-co://,format=raw,id=mydrive \ -device sd-card,drive=mydrive -qtest stdio Cc: qemu-stable@nongnu.org Fixes: CVE-2020-17380 Fixes: CVE-2020-25085 Fixes: CVE-2021-3409 Fixes: d7dfca0807a0 ("hw/sdhci: introduce standard SD host controller") Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Cornelius Aschermann (Ruhr-Universität Bochum) Reported-by: Sergej Schumilo (Ruhr-Universität Bochum) Reported-by: Simon Wörner (Ruhr-Universität Bochum) Buglink: https://bugs.launchpad.net/qemu/+bug/1892960 Buglink: https://bugs.launchpad.net/qemu/+bug/1909418 Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1928146 Tested-by: Alexander Bulekov <alxndr@bu.edu> Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Message-Id: <20210303122639.20004-6-bmeng.cn@gmail.com> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
2021-03-03 13:26:39 +01:00
uint16_t blksize = s->blksize;
/*
* [14:12] SDMA Buffer Boundary
* [11:00] Transfer Block Size
*/
MASKED_WRITE(s->blksize, mask, extract32(value, 0, 15));
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);
s->blksize = deposit32(s->blksize, 0, 12, s->buf_maxsz);
}
hw/sd: sdhci: Reset the data pointer of s->fifo_buffer[] when a different block size is programmed 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. With this fix, the following reproducer: outl 0xcf8 0x80001010 outl 0xcfc 0xe0000000 outl 0xcf8 0x80001001 outl 0xcfc 0x06000000 write 0xe000002c 0x1 0x05 write 0xe0000005 0x1 0x02 write 0xe0000007 0x1 0x01 write 0xe0000028 0x1 0x10 write 0x0 0x1 0x23 write 0x2 0x1 0x08 write 0xe000000c 0x1 0x01 write 0xe000000e 0x1 0x20 write 0xe000000f 0x1 0x00 write 0xe000000c 0x1 0x32 write 0xe0000004 0x2 0x0200 write 0xe0000028 0x1 0x00 write 0xe0000003 0x1 0x40 cannot be reproduced with the following QEMU command line: $ qemu-system-x86_64 -nographic -machine accel=qtest -m 512M \ -nodefaults -device sdhci-pci,sd-spec-version=3 \ -drive if=sd,index=0,file=null-co://,format=raw,id=mydrive \ -device sd-card,drive=mydrive -qtest stdio Cc: qemu-stable@nongnu.org Fixes: CVE-2020-17380 Fixes: CVE-2020-25085 Fixes: CVE-2021-3409 Fixes: d7dfca0807a0 ("hw/sdhci: introduce standard SD host controller") Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Cornelius Aschermann (Ruhr-Universität Bochum) Reported-by: Sergej Schumilo (Ruhr-Universität Bochum) Reported-by: Simon Wörner (Ruhr-Universität Bochum) Buglink: https://bugs.launchpad.net/qemu/+bug/1892960 Buglink: https://bugs.launchpad.net/qemu/+bug/1909418 Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1928146 Tested-by: Alexander Bulekov <alxndr@bu.edu> Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Message-Id: <20210303122639.20004-6-bmeng.cn@gmail.com> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
2021-03-03 13:26:39 +01:00
/*
* 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;
case SDHC_ARGUMENT:
MASKED_WRITE(s->argument, mask, value);
break;
case SDHC_TRNMOD:
/* DMA can be enabled only if it is supported as indicated by
* capabilities register */
if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) {
value &= ~SDHC_TRNS_DMA;
}
hw/sd/sdhci: Do not update TRNMOD when Command Inhibit (DAT) is set Per "SD Host Controller Standard Specification Version 3.00": * 2.2.5 Transfer Mode Register (Offset 00Ch) Writes to this register shall be ignored when the Command Inhibit (DAT) in the Present State register is 1. Do not update the TRNMOD register when Command Inhibit (DAT) bit is set to avoid the present-status register going out of sync, leading to malicious guest using DMA mode and overflowing the FIFO buffer: $ cat << EOF | qemu-system-i386 \ -display none -nographic -nodefaults \ -machine accel=qtest -m 512M \ -device sdhci-pci,sd-spec-version=3 \ -device sd-card,drive=mydrive \ -drive if=none,index=0,file=null-co://,format=raw,id=mydrive \ -qtest stdio outl 0xcf8 0x80001013 outl 0xcfc 0x91 outl 0xcf8 0x80001001 outl 0xcfc 0x06000000 write 0x9100002c 0x1 0x05 write 0x91000058 0x1 0x16 write 0x91000005 0x1 0x04 write 0x91000028 0x1 0x08 write 0x16 0x1 0x21 write 0x19 0x1 0x20 write 0x9100000c 0x1 0x01 write 0x9100000e 0x1 0x20 write 0x9100000f 0x1 0x00 write 0x9100000c 0x1 0x00 write 0x91000020 0x1 0x00 EOF Stack trace (part): ================================================================= ==89993==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x615000029900 at pc 0x55d5f885700d bp 0x7ffc1e1e9470 sp 0x7ffc1e1e9468 WRITE of size 1 at 0x615000029900 thread T0 #0 0x55d5f885700c in sdhci_write_dataport hw/sd/sdhci.c:564:39 #1 0x55d5f8849150 in sdhci_write hw/sd/sdhci.c:1223:13 #2 0x55d5fa01db63 in memory_region_write_accessor system/memory.c:497:5 #3 0x55d5fa01d245 in access_with_adjusted_size system/memory.c:573:18 #4 0x55d5fa01b1a9 in memory_region_dispatch_write system/memory.c:1521:16 #5 0x55d5fa09f5c9 in flatview_write_continue system/physmem.c:2711:23 #6 0x55d5fa08f78b in flatview_write system/physmem.c:2753:12 #7 0x55d5fa08f258 in address_space_write system/physmem.c:2860:18 ... 0x615000029900 is located 0 bytes to the right of 512-byte region [0x615000029700,0x615000029900) allocated by thread T0 here: #0 0x55d5f7237b27 in __interceptor_calloc #1 0x7f9e36dd4c50 in g_malloc0 #2 0x55d5f88672f7 in sdhci_pci_realize hw/sd/sdhci-pci.c:36:5 #3 0x55d5f844b582 in pci_qdev_realize hw/pci/pci.c:2092:9 #4 0x55d5fa2ee74b in device_set_realized hw/core/qdev.c:510:13 #5 0x55d5fa325bfb in property_set_bool qom/object.c:2358:5 #6 0x55d5fa31ea45 in object_property_set qom/object.c:1472:5 #7 0x55d5fa332509 in object_property_set_qobject om/qom-qobject.c:28:10 #8 0x55d5fa31f6ed in object_property_set_bool qom/object.c:1541:15 #9 0x55d5fa2e2948 in qdev_realize hw/core/qdev.c:292:12 #10 0x55d5f8eed3f1 in qdev_device_add_from_qdict system/qdev-monitor.c:719:10 #11 0x55d5f8eef7ff in qdev_device_add system/qdev-monitor.c:738:11 #12 0x55d5f8f211f0 in device_init_func system/vl.c:1200:11 #13 0x55d5fad0877d in qemu_opts_foreach util/qemu-option.c:1135:14 #14 0x55d5f8f0df9c in qemu_create_cli_devices system/vl.c:2638:5 #15 0x55d5f8f0db24 in qmp_x_exit_preconfig system/vl.c:2706:5 #16 0x55d5f8f14dc0 in qemu_init system/vl.c:3737:9 ... SUMMARY: AddressSanitizer: heap-buffer-overflow hw/sd/sdhci.c:564:39 in sdhci_write_dataport Add assertions to ensure the fifo_buffer[] is not overflowed by malicious accesses to the Buffer Data Port register. Fixes: CVE-2024-3447 Cc: qemu-stable@nongnu.org Fixes: d7dfca0807 ("hw/sdhci: introduce standard SD host controller") Buglink: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=58813 Reported-by: Alexander Bulekov <alxndr@bu.edu> Reported-by: Chuhong Yuan <hslester96@gmail.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Message-Id: <CAFEAcA9iLiv1XGTGKeopgMa8Y9+8kvptvsb8z2OBeuy+5=NUfg@mail.gmail.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-Id: <20240409145524.27913-1-philmd@linaro.org>
2024-04-09 16:19:27 +02:00
/* TRNMOD writes are inhibited while Command Inhibit (DAT) is true */
if (s->prnsts & SDHC_DATA_INHIBIT) {
mask |= 0xffff;
}
MASKED_WRITE(s->trnmod, mask, value & SDHC_TRNMOD_MASK);
MASKED_WRITE(s->cmdreg, mask >> 16, value >> 16);
/* Writing to the upper byte of CMDREG triggers SD command generation */
if ((mask & 0xFF000000) || !sdhci_can_issue_command(s)) {
break;
}
sdhci_send_command(s);
break;
case SDHC_BDATA:
if (sdhci_buff_access_is_sequential(s, offset - SDHC_BDATA)) {
sdhci_write_dataport(s, value >> shift, size);
}
break;
case SDHC_HOSTCTL:
if (!(mask & 0xFF0000)) {
sdhci_blkgap_write(s, value >> 16);
}
MASKED_WRITE(s->hostctl1, mask, value);
MASKED_WRITE(s->pwrcon, mask >> 8, value >> 8);
MASKED_WRITE(s->wakcon, mask >> 24, value >> 24);
if (!(s->prnsts & SDHC_CARD_PRESENT) || ((s->pwrcon >> 1) & 0x7) < 5 ||
!(s->capareg & (1 << (31 - ((s->pwrcon >> 1) & 0x7))))) {
s->pwrcon &= ~SDHC_POWER_ON;
}
break;
case SDHC_CLKCON:
if (!(mask & 0xFF000000)) {
sdhci_reset_write(s, value >> 24);
}
MASKED_WRITE(s->clkcon, mask, value);
MASKED_WRITE(s->timeoutcon, mask >> 16, value >> 16);
if (s->clkcon & SDHC_CLOCK_INT_EN) {
s->clkcon |= SDHC_CLOCK_INT_STABLE;
} else {
s->clkcon &= ~SDHC_CLOCK_INT_STABLE;
}
break;
case SDHC_NORINTSTS:
if (s->norintstsen & SDHC_NISEN_CARDINT) {
value &= ~SDHC_NIS_CARDINT;
}
s->norintsts &= mask | ~value;
s->errintsts &= (mask >> 16) | ~(value >> 16);
if (s->errintsts) {
s->norintsts |= SDHC_NIS_ERR;
} else {
s->norintsts &= ~SDHC_NIS_ERR;
}
sdhci_update_irq(s);
break;
case SDHC_NORINTSTSEN:
MASKED_WRITE(s->norintstsen, mask, value);
MASKED_WRITE(s->errintstsen, mask >> 16, value >> 16);
s->norintsts &= s->norintstsen;
s->errintsts &= s->errintstsen;
if (s->errintsts) {
s->norintsts |= SDHC_NIS_ERR;
} else {
s->norintsts &= ~SDHC_NIS_ERR;
}
/* Quirk for Raspberry Pi: pending card insert interrupt
* appears when first enabled after power on */
if ((s->norintstsen & SDHC_NISEN_INSERT) && s->pending_insert_state) {
assert(s->pending_insert_quirk);
s->norintsts |= SDHC_NIS_INSERT;
s->pending_insert_state = false;
}
sdhci_update_irq(s);
break;
case SDHC_NORINTSIGEN:
MASKED_WRITE(s->norintsigen, mask, value);
MASKED_WRITE(s->errintsigen, mask >> 16, value >> 16);
sdhci_update_irq(s);
break;
case SDHC_ADMAERR:
MASKED_WRITE(s->admaerr, mask, value);
break;
case SDHC_ADMASYSADDR:
s->admasysaddr = (s->admasysaddr & (0xFFFFFFFF00000000ULL |
(uint64_t)mask)) | (uint64_t)value;
break;
case SDHC_ADMASYSADDR + 4:
s->admasysaddr = (s->admasysaddr & (0x00000000FFFFFFFFULL |
((uint64_t)mask << 32))) | ((uint64_t)value << 32);
break;
case SDHC_FEAER:
s->acmd12errsts |= value;
s->errintsts |= (value >> 16) & s->errintstsen;
if (s->acmd12errsts) {
s->errintsts |= SDHC_EIS_CMD12ERR;
}
if (s->errintsts) {
s->norintsts |= SDHC_NIS_ERR;
}
sdhci_update_irq(s);
break;
case SDHC_ACMD12ERRSTS:
MASKED_WRITE(s->acmd12errsts, mask, value & UINT16_MAX);
if (s->uhs_mode >= UHS_I) {
MASKED_WRITE(s->hostctl2, mask >> 16, value >> 16);
if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, V18_ENA)) {
sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_1_8V);
} else {
sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_3_3V);
}
}
break;
case SDHC_CAPAB:
case SDHC_CAPAB + 4:
case SDHC_MAXCURR:
case SDHC_MAXCURR + 4:
qemu_log_mask(LOG_GUEST_ERROR, "SDHC wr_%ub @0x%02" HWADDR_PRIx
" <- 0x%08x read-only\n", size, offset, value >> shift);
break;
default:
qemu_log_mask(LOG_UNIMP, "SDHC wr_%ub @0x%02" HWADDR_PRIx " <- 0x%08x "
"not implemented\n", size, offset, value >> shift);
break;
}
trace_sdhci_access("wr", size << 3, offset, "<-",
value >> shift, value >> shift);
}
static const MemoryRegionOps sdhci_mmio_le_ops = {
.read = sdhci_read,
.write = sdhci_write,
.valid = {
.min_access_size = 1,
.max_access_size = 4,
.unaligned = false
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static const MemoryRegionOps sdhci_mmio_be_ops = {
.read = sdhci_read,
.write = sdhci_write,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
.valid = {
.min_access_size = 1,
.max_access_size = 4,
.unaligned = false
},
.endianness = DEVICE_BIG_ENDIAN,
};
static void sdhci_init_readonly_registers(SDHCIState *s, Error **errp)
{
sd: Use ERRP_GUARD() If we want to check error after errp-function call, we need to introduce local_err and then propagate it to errp. Instead, use the ERRP_GUARD() macro, benefits are: 1. No need of explicit error_propagate call 2. No need of explicit local_err variable: use errp directly 3. ERRP_GUARD() leaves errp as is if it's not NULL or &error_fatal, this means that we don't break error_abort (we'll abort on error_set, not on error_propagate) If we want to add some info to errp (by error_prepend() or error_append_hint()), we must use the ERRP_GUARD() macro. Otherwise, this info will not be added when errp == &error_fatal (the program will exit prior to the error_append_hint() or error_prepend() call). No such cases are being fixed here. This commit is generated by command sed -n '/^SD (Secure Card)$/,/^$/{s/^F: //p}' \ MAINTAINERS | \ xargs git ls-files | grep '\.[hc]$' | \ xargs spatch \ --sp-file scripts/coccinelle/errp-guard.cocci \ --macro-file scripts/cocci-macro-file.h \ --in-place --no-show-diff --max-width 80 Reported-by: Kevin Wolf <kwolf@redhat.com> Reported-by: Greg Kurz <groug@kaod.org> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20200707165037.1026246-4-armbru@redhat.com> [ERRP_AUTO_PROPAGATE() renamed to ERRP_GUARD(), and auto-propagated-errp.cocci to errp-guard.cocci. Commit message tweaked again.]
2020-07-07 18:50:32 +02:00
ERRP_GUARD();
switch (s->sd_spec_version) {
case 2 ... 3:
break;
default:
error_setg(errp, "Only Spec v2/v3 are supported");
return;
}
s->version = (SDHC_HCVER_VENDOR << 8) | (s->sd_spec_version - 1);
sd: Use ERRP_GUARD() If we want to check error after errp-function call, we need to introduce local_err and then propagate it to errp. Instead, use the ERRP_GUARD() macro, benefits are: 1. No need of explicit error_propagate call 2. No need of explicit local_err variable: use errp directly 3. ERRP_GUARD() leaves errp as is if it's not NULL or &error_fatal, this means that we don't break error_abort (we'll abort on error_set, not on error_propagate) If we want to add some info to errp (by error_prepend() or error_append_hint()), we must use the ERRP_GUARD() macro. Otherwise, this info will not be added when errp == &error_fatal (the program will exit prior to the error_append_hint() or error_prepend() call). No such cases are being fixed here. This commit is generated by command sed -n '/^SD (Secure Card)$/,/^$/{s/^F: //p}' \ MAINTAINERS | \ xargs git ls-files | grep '\.[hc]$' | \ xargs spatch \ --sp-file scripts/coccinelle/errp-guard.cocci \ --macro-file scripts/cocci-macro-file.h \ --in-place --no-show-diff --max-width 80 Reported-by: Kevin Wolf <kwolf@redhat.com> Reported-by: Greg Kurz <groug@kaod.org> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20200707165037.1026246-4-armbru@redhat.com> [ERRP_AUTO_PROPAGATE() renamed to ERRP_GUARD(), and auto-propagated-errp.cocci to errp-guard.cocci. Commit message tweaked again.]
2020-07-07 18:50:32 +02:00
sdhci_check_capareg(s, errp);
if (*errp) {
return;
}
}
/* --- qdev common --- */
void sdhci_initfn(SDHCIState *s)
{
qbus_init(&s->sdbus, sizeof(s->sdbus), TYPE_SDHCI_BUS, DEVICE(s), "sd-bus");
s->insert_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_raise_insertion_irq, s);
s->transfer_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_data_transfer, s);
s->io_ops = &sdhci_mmio_le_ops;
}
void sdhci_uninitfn(SDHCIState *s)
{
timer_free(s->insert_timer);
timer_free(s->transfer_timer);
g_free(s->fifo_buffer);
s->fifo_buffer = NULL;
}
void sdhci_common_realize(SDHCIState *s, Error **errp)
{
sd: Use ERRP_GUARD() If we want to check error after errp-function call, we need to introduce local_err and then propagate it to errp. Instead, use the ERRP_GUARD() macro, benefits are: 1. No need of explicit error_propagate call 2. No need of explicit local_err variable: use errp directly 3. ERRP_GUARD() leaves errp as is if it's not NULL or &error_fatal, this means that we don't break error_abort (we'll abort on error_set, not on error_propagate) If we want to add some info to errp (by error_prepend() or error_append_hint()), we must use the ERRP_GUARD() macro. Otherwise, this info will not be added when errp == &error_fatal (the program will exit prior to the error_append_hint() or error_prepend() call). No such cases are being fixed here. This commit is generated by command sed -n '/^SD (Secure Card)$/,/^$/{s/^F: //p}' \ MAINTAINERS | \ xargs git ls-files | grep '\.[hc]$' | \ xargs spatch \ --sp-file scripts/coccinelle/errp-guard.cocci \ --macro-file scripts/cocci-macro-file.h \ --in-place --no-show-diff --max-width 80 Reported-by: Kevin Wolf <kwolf@redhat.com> Reported-by: Greg Kurz <groug@kaod.org> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20200707165037.1026246-4-armbru@redhat.com> [ERRP_AUTO_PROPAGATE() renamed to ERRP_GUARD(), and auto-propagated-errp.cocci to errp-guard.cocci. Commit message tweaked again.]
2020-07-07 18:50:32 +02:00
ERRP_GUARD();
switch (s->endianness) {
case DEVICE_LITTLE_ENDIAN:
/* s->io_ops is little endian by default */
break;
case DEVICE_BIG_ENDIAN:
if (s->io_ops != &sdhci_mmio_le_ops) {
error_setg(errp, "SD controller doesn't support big endianness");
return;
}
s->io_ops = &sdhci_mmio_be_ops;
break;
default:
error_setg(errp, "Incorrect endianness");
return;
}
sd: Use ERRP_GUARD() If we want to check error after errp-function call, we need to introduce local_err and then propagate it to errp. Instead, use the ERRP_GUARD() macro, benefits are: 1. No need of explicit error_propagate call 2. No need of explicit local_err variable: use errp directly 3. ERRP_GUARD() leaves errp as is if it's not NULL or &error_fatal, this means that we don't break error_abort (we'll abort on error_set, not on error_propagate) If we want to add some info to errp (by error_prepend() or error_append_hint()), we must use the ERRP_GUARD() macro. Otherwise, this info will not be added when errp == &error_fatal (the program will exit prior to the error_append_hint() or error_prepend() call). No such cases are being fixed here. This commit is generated by command sed -n '/^SD (Secure Card)$/,/^$/{s/^F: //p}' \ MAINTAINERS | \ xargs git ls-files | grep '\.[hc]$' | \ xargs spatch \ --sp-file scripts/coccinelle/errp-guard.cocci \ --macro-file scripts/cocci-macro-file.h \ --in-place --no-show-diff --max-width 80 Reported-by: Kevin Wolf <kwolf@redhat.com> Reported-by: Greg Kurz <groug@kaod.org> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20200707165037.1026246-4-armbru@redhat.com> [ERRP_AUTO_PROPAGATE() renamed to ERRP_GUARD(), and auto-propagated-errp.cocci to errp-guard.cocci. Commit message tweaked again.]
2020-07-07 18:50:32 +02:00
sdhci_init_readonly_registers(s, errp);
if (*errp) {
return;
}
s->buf_maxsz = sdhci_get_fifolen(s);
s->fifo_buffer = g_malloc0(s->buf_maxsz);
memory_region_init_io(&s->iomem, OBJECT(s), s->io_ops, s, "sdhci",
SDHC_REGISTERS_MAP_SIZE);
}
qdev: Unrealize must not fail Devices may have component devices and buses. Device realization may fail. Realization is recursive: a device's realize() method realizes its components, and device_set_realized() realizes its buses (which should in turn realize the devices on that bus, except bus_set_realized() doesn't implement that, yet). When realization of a component or bus fails, we need to roll back: unrealize everything we realized so far. If any of these unrealizes failed, the device would be left in an inconsistent state. Must not happen. device_set_realized() lets it happen: it ignores errors in the roll back code starting at label child_realize_fail. Since realization is recursive, unrealization must be recursive, too. But how could a partly failed unrealize be rolled back? We'd have to re-realize, which can fail. This design is fundamentally broken. device_set_realized() does not roll back at all. Instead, it keeps unrealizing, ignoring further errors. It can screw up even for a device with no buses: if the lone dc->unrealize() fails, it still unregisters vmstate, and calls listeners' unrealize() callback. bus_set_realized() does not roll back either. Instead, it stops unrealizing. Fortunately, no unrealize method can fail, as we'll see below. To fix the design error, drop parameter @errp from all the unrealize methods. Any unrealize method that uses @errp now needs an update. This leads us to unrealize() methods that can fail. Merely passing it to another unrealize method cannot cause failure, though. Here are the ones that do other things with @errp: * virtio_serial_device_unrealize() Fails when qbus_set_hotplug_handler() fails, but still does all the other work. On failure, the device would stay realized with its resources completely gone. Oops. Can't happen, because qbus_set_hotplug_handler() can't actually fail here. Pass &error_abort to qbus_set_hotplug_handler() instead. * hw/ppc/spapr_drc.c's unrealize() Fails when object_property_del() fails, but all the other work is already done. On failure, the device would stay realized with its vmstate registration gone. Oops. Can't happen, because object_property_del() can't actually fail here. Pass &error_abort to object_property_del() instead. * spapr_phb_unrealize() Fails and bails out when remove_drcs() fails, but other work is already done. On failure, the device would stay realized with some of its resources gone. Oops. remove_drcs() fails only when chassis_from_bus()'s object_property_get_uint() fails, and it can't here. Pass &error_abort to remove_drcs() instead. Therefore, no unrealize method can fail before this patch. device_set_realized()'s recursive unrealization via bus uses object_property_set_bool(). Can't drop @errp there, so pass &error_abort. We similarly unrealize with object_property_set_bool() elsewhere, always ignoring errors. Pass &error_abort instead. Several unrealize methods no longer handle errors from other unrealize methods: virtio_9p_device_unrealize(), virtio_input_device_unrealize(), scsi_qdev_unrealize(), ... Much of the deleted error handling looks wrong anyway. One unrealize methods no longer ignore such errors: usb_ehci_pci_exit(). Several realize methods no longer ignore errors when rolling back: v9fs_device_realize_common(), pci_qdev_unrealize(), spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(), virtio_device_realize(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 17:29:24 +02:00
void sdhci_common_unrealize(SDHCIState *s)
{
/* This function is expected to be called only once for each class:
* - SysBus: via DeviceClass->unrealize(),
* - PCI: via PCIDeviceClass->exit().
* However to avoid double-free and/or use-after-free we still nullify
* this variable (better safe than sorry!). */
g_free(s->fifo_buffer);
s->fifo_buffer = NULL;
}
static bool sdhci_pending_insert_vmstate_needed(void *opaque)
{
SDHCIState *s = opaque;
return s->pending_insert_state;
}
static const VMStateDescription sdhci_pending_insert_vmstate = {
.name = "sdhci/pending-insert",
.version_id = 1,
.minimum_version_id = 1,
.needed = sdhci_pending_insert_vmstate_needed,
.fields = (const VMStateField[]) {
VMSTATE_BOOL(pending_insert_state, SDHCIState),
VMSTATE_END_OF_LIST()
},
};
const VMStateDescription sdhci_vmstate = {
.name = "sdhci",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(sdmasysad, SDHCIState),
VMSTATE_UINT16(blksize, SDHCIState),
VMSTATE_UINT16(blkcnt, SDHCIState),
VMSTATE_UINT32(argument, SDHCIState),
VMSTATE_UINT16(trnmod, SDHCIState),
VMSTATE_UINT16(cmdreg, SDHCIState),
VMSTATE_UINT32_ARRAY(rspreg, SDHCIState, 4),
VMSTATE_UINT32(prnsts, SDHCIState),
VMSTATE_UINT8(hostctl1, SDHCIState),
VMSTATE_UINT8(pwrcon, SDHCIState),
VMSTATE_UINT8(blkgap, SDHCIState),
VMSTATE_UINT8(wakcon, SDHCIState),
VMSTATE_UINT16(clkcon, SDHCIState),
VMSTATE_UINT8(timeoutcon, SDHCIState),
VMSTATE_UINT8(admaerr, SDHCIState),
VMSTATE_UINT16(norintsts, SDHCIState),
VMSTATE_UINT16(errintsts, SDHCIState),
VMSTATE_UINT16(norintstsen, SDHCIState),
VMSTATE_UINT16(errintstsen, SDHCIState),
VMSTATE_UINT16(norintsigen, SDHCIState),
VMSTATE_UINT16(errintsigen, SDHCIState),
VMSTATE_UINT16(acmd12errsts, SDHCIState),
VMSTATE_UINT16(data_count, SDHCIState),
VMSTATE_UINT64(admasysaddr, SDHCIState),
VMSTATE_UINT8(stopped_state, SDHCIState),
VMSTATE_VBUFFER_UINT32(fifo_buffer, SDHCIState, 1, NULL, buf_maxsz),
VMSTATE_TIMER_PTR(insert_timer, SDHCIState),
VMSTATE_TIMER_PTR(transfer_timer, SDHCIState),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&sdhci_pending_insert_vmstate,
NULL
},
};
void sdhci_common_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
dc->vmsd = &sdhci_vmstate;
dc->reset = sdhci_poweron_reset;
}
/* --- qdev SysBus --- */
static Property sdhci_sysbus_properties[] = {
DEFINE_SDHCI_COMMON_PROPERTIES(SDHCIState),
DEFINE_PROP_BOOL("pending-insert-quirk", SDHCIState, pending_insert_quirk,
false),
DEFINE_PROP_LINK("dma", SDHCIState,
dma_mr, TYPE_MEMORY_REGION, MemoryRegion *),
DEFINE_PROP_END_OF_LIST(),
};
static void sdhci_sysbus_init(Object *obj)
{
SDHCIState *s = SYSBUS_SDHCI(obj);
sdhci_initfn(s);
}
static void sdhci_sysbus_finalize(Object *obj)
{
SDHCIState *s = SYSBUS_SDHCI(obj);
if (s->dma_mr) {
object_unparent(OBJECT(s->dma_mr));
}
sdhci_uninitfn(s);
}
static void sdhci_sysbus_realize(DeviceState *dev, Error **errp)
{
sd: Use ERRP_GUARD() If we want to check error after errp-function call, we need to introduce local_err and then propagate it to errp. Instead, use the ERRP_GUARD() macro, benefits are: 1. No need of explicit error_propagate call 2. No need of explicit local_err variable: use errp directly 3. ERRP_GUARD() leaves errp as is if it's not NULL or &error_fatal, this means that we don't break error_abort (we'll abort on error_set, not on error_propagate) If we want to add some info to errp (by error_prepend() or error_append_hint()), we must use the ERRP_GUARD() macro. Otherwise, this info will not be added when errp == &error_fatal (the program will exit prior to the error_append_hint() or error_prepend() call). No such cases are being fixed here. This commit is generated by command sed -n '/^SD (Secure Card)$/,/^$/{s/^F: //p}' \ MAINTAINERS | \ xargs git ls-files | grep '\.[hc]$' | \ xargs spatch \ --sp-file scripts/coccinelle/errp-guard.cocci \ --macro-file scripts/cocci-macro-file.h \ --in-place --no-show-diff --max-width 80 Reported-by: Kevin Wolf <kwolf@redhat.com> Reported-by: Greg Kurz <groug@kaod.org> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20200707165037.1026246-4-armbru@redhat.com> [ERRP_AUTO_PROPAGATE() renamed to ERRP_GUARD(), and auto-propagated-errp.cocci to errp-guard.cocci. Commit message tweaked again.]
2020-07-07 18:50:32 +02:00
ERRP_GUARD();
SDHCIState *s = SYSBUS_SDHCI(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
sd: Use ERRP_GUARD() If we want to check error after errp-function call, we need to introduce local_err and then propagate it to errp. Instead, use the ERRP_GUARD() macro, benefits are: 1. No need of explicit error_propagate call 2. No need of explicit local_err variable: use errp directly 3. ERRP_GUARD() leaves errp as is if it's not NULL or &error_fatal, this means that we don't break error_abort (we'll abort on error_set, not on error_propagate) If we want to add some info to errp (by error_prepend() or error_append_hint()), we must use the ERRP_GUARD() macro. Otherwise, this info will not be added when errp == &error_fatal (the program will exit prior to the error_append_hint() or error_prepend() call). No such cases are being fixed here. This commit is generated by command sed -n '/^SD (Secure Card)$/,/^$/{s/^F: //p}' \ MAINTAINERS | \ xargs git ls-files | grep '\.[hc]$' | \ xargs spatch \ --sp-file scripts/coccinelle/errp-guard.cocci \ --macro-file scripts/cocci-macro-file.h \ --in-place --no-show-diff --max-width 80 Reported-by: Kevin Wolf <kwolf@redhat.com> Reported-by: Greg Kurz <groug@kaod.org> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20200707165037.1026246-4-armbru@redhat.com> [ERRP_AUTO_PROPAGATE() renamed to ERRP_GUARD(), and auto-propagated-errp.cocci to errp-guard.cocci. Commit message tweaked again.]
2020-07-07 18:50:32 +02:00
sdhci_common_realize(s, errp);
if (*errp) {
return;
}
if (s->dma_mr) {
s->dma_as = &s->sysbus_dma_as;
address_space_init(s->dma_as, s->dma_mr, "sdhci-dma");
} else {
/* use system_memory() if property "dma" not set */
s->dma_as = &address_space_memory;
}
sysbus_init_irq(sbd, &s->irq);
sysbus_init_mmio(sbd, &s->iomem);
}
qdev: Unrealize must not fail Devices may have component devices and buses. Device realization may fail. Realization is recursive: a device's realize() method realizes its components, and device_set_realized() realizes its buses (which should in turn realize the devices on that bus, except bus_set_realized() doesn't implement that, yet). When realization of a component or bus fails, we need to roll back: unrealize everything we realized so far. If any of these unrealizes failed, the device would be left in an inconsistent state. Must not happen. device_set_realized() lets it happen: it ignores errors in the roll back code starting at label child_realize_fail. Since realization is recursive, unrealization must be recursive, too. But how could a partly failed unrealize be rolled back? We'd have to re-realize, which can fail. This design is fundamentally broken. device_set_realized() does not roll back at all. Instead, it keeps unrealizing, ignoring further errors. It can screw up even for a device with no buses: if the lone dc->unrealize() fails, it still unregisters vmstate, and calls listeners' unrealize() callback. bus_set_realized() does not roll back either. Instead, it stops unrealizing. Fortunately, no unrealize method can fail, as we'll see below. To fix the design error, drop parameter @errp from all the unrealize methods. Any unrealize method that uses @errp now needs an update. This leads us to unrealize() methods that can fail. Merely passing it to another unrealize method cannot cause failure, though. Here are the ones that do other things with @errp: * virtio_serial_device_unrealize() Fails when qbus_set_hotplug_handler() fails, but still does all the other work. On failure, the device would stay realized with its resources completely gone. Oops. Can't happen, because qbus_set_hotplug_handler() can't actually fail here. Pass &error_abort to qbus_set_hotplug_handler() instead. * hw/ppc/spapr_drc.c's unrealize() Fails when object_property_del() fails, but all the other work is already done. On failure, the device would stay realized with its vmstate registration gone. Oops. Can't happen, because object_property_del() can't actually fail here. Pass &error_abort to object_property_del() instead. * spapr_phb_unrealize() Fails and bails out when remove_drcs() fails, but other work is already done. On failure, the device would stay realized with some of its resources gone. Oops. remove_drcs() fails only when chassis_from_bus()'s object_property_get_uint() fails, and it can't here. Pass &error_abort to remove_drcs() instead. Therefore, no unrealize method can fail before this patch. device_set_realized()'s recursive unrealization via bus uses object_property_set_bool(). Can't drop @errp there, so pass &error_abort. We similarly unrealize with object_property_set_bool() elsewhere, always ignoring errors. Pass &error_abort instead. Several unrealize methods no longer handle errors from other unrealize methods: virtio_9p_device_unrealize(), virtio_input_device_unrealize(), scsi_qdev_unrealize(), ... Much of the deleted error handling looks wrong anyway. One unrealize methods no longer ignore such errors: usb_ehci_pci_exit(). Several realize methods no longer ignore errors when rolling back: v9fs_device_realize_common(), pci_qdev_unrealize(), spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(), virtio_device_realize(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 17:29:24 +02:00
static void sdhci_sysbus_unrealize(DeviceState *dev)
{
SDHCIState *s = SYSBUS_SDHCI(dev);
qdev: Unrealize must not fail Devices may have component devices and buses. Device realization may fail. Realization is recursive: a device's realize() method realizes its components, and device_set_realized() realizes its buses (which should in turn realize the devices on that bus, except bus_set_realized() doesn't implement that, yet). When realization of a component or bus fails, we need to roll back: unrealize everything we realized so far. If any of these unrealizes failed, the device would be left in an inconsistent state. Must not happen. device_set_realized() lets it happen: it ignores errors in the roll back code starting at label child_realize_fail. Since realization is recursive, unrealization must be recursive, too. But how could a partly failed unrealize be rolled back? We'd have to re-realize, which can fail. This design is fundamentally broken. device_set_realized() does not roll back at all. Instead, it keeps unrealizing, ignoring further errors. It can screw up even for a device with no buses: if the lone dc->unrealize() fails, it still unregisters vmstate, and calls listeners' unrealize() callback. bus_set_realized() does not roll back either. Instead, it stops unrealizing. Fortunately, no unrealize method can fail, as we'll see below. To fix the design error, drop parameter @errp from all the unrealize methods. Any unrealize method that uses @errp now needs an update. This leads us to unrealize() methods that can fail. Merely passing it to another unrealize method cannot cause failure, though. Here are the ones that do other things with @errp: * virtio_serial_device_unrealize() Fails when qbus_set_hotplug_handler() fails, but still does all the other work. On failure, the device would stay realized with its resources completely gone. Oops. Can't happen, because qbus_set_hotplug_handler() can't actually fail here. Pass &error_abort to qbus_set_hotplug_handler() instead. * hw/ppc/spapr_drc.c's unrealize() Fails when object_property_del() fails, but all the other work is already done. On failure, the device would stay realized with its vmstate registration gone. Oops. Can't happen, because object_property_del() can't actually fail here. Pass &error_abort to object_property_del() instead. * spapr_phb_unrealize() Fails and bails out when remove_drcs() fails, but other work is already done. On failure, the device would stay realized with some of its resources gone. Oops. remove_drcs() fails only when chassis_from_bus()'s object_property_get_uint() fails, and it can't here. Pass &error_abort to remove_drcs() instead. Therefore, no unrealize method can fail before this patch. device_set_realized()'s recursive unrealization via bus uses object_property_set_bool(). Can't drop @errp there, so pass &error_abort. We similarly unrealize with object_property_set_bool() elsewhere, always ignoring errors. Pass &error_abort instead. Several unrealize methods no longer handle errors from other unrealize methods: virtio_9p_device_unrealize(), virtio_input_device_unrealize(), scsi_qdev_unrealize(), ... Much of the deleted error handling looks wrong anyway. One unrealize methods no longer ignore such errors: usb_ehci_pci_exit(). Several realize methods no longer ignore errors when rolling back: v9fs_device_realize_common(), pci_qdev_unrealize(), spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(), virtio_device_realize(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 17:29:24 +02:00
sdhci_common_unrealize(s);
if (s->dma_mr) {
address_space_destroy(s->dma_as);
}
}
static void sdhci_sysbus_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_props(dc, sdhci_sysbus_properties);
dc->realize = sdhci_sysbus_realize;
dc->unrealize = sdhci_sysbus_unrealize;
sdhci_common_class_init(klass, data);
}
static const TypeInfo sdhci_sysbus_info = {
.name = TYPE_SYSBUS_SDHCI,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SDHCIState),
.instance_init = sdhci_sysbus_init,
.instance_finalize = sdhci_sysbus_finalize,
.class_init = sdhci_sysbus_class_init,
};
/* --- qdev bus master --- */
static void sdhci_bus_class_init(ObjectClass *klass, void *data)
{
SDBusClass *sbc = SD_BUS_CLASS(klass);
sbc->set_inserted = sdhci_set_inserted;
sbc->set_readonly = sdhci_set_readonly;
}
static const TypeInfo sdhci_bus_info = {
.name = TYPE_SDHCI_BUS,
.parent = TYPE_SD_BUS,
.instance_size = sizeof(SDBus),
.class_init = sdhci_bus_class_init,
};
/* --- qdev i.MX eSDHC --- */
#define USDHC_MIX_CTRL 0x48
#define USDHC_VENDOR_SPEC 0xc0
#define USDHC_IMX_FRC_SDCLK_ON (1 << 8)
#define USDHC_DLL_CTRL 0x60
#define USDHC_TUNING_CTRL 0xcc
#define USDHC_TUNE_CTRL_STATUS 0x68
#define USDHC_WTMK_LVL 0x44
/* Undocumented register used by guests working around erratum ERR004536 */
#define USDHC_UNDOCUMENTED_REG27 0x6c
#define USDHC_CTRL_4BITBUS (0x1 << 1)
#define USDHC_CTRL_8BITBUS (0x2 << 1)
#define USDHC_PRNSTS_SDSTB (1 << 3)
static uint64_t usdhc_read(void *opaque, hwaddr offset, unsigned size)
{
SDHCIState *s = SYSBUS_SDHCI(opaque);
uint32_t ret;
uint16_t hostctl1;
switch (offset) {
default:
return sdhci_read(opaque, offset, size);
case SDHC_HOSTCTL:
/*
* For a detailed explanation on the following bit
* manipulation code see comments in a similar part of
* usdhc_write()
*/
hostctl1 = SDHC_DMA_TYPE(s->hostctl1) << (8 - 3);
if (s->hostctl1 & SDHC_CTRL_8BITBUS) {
hostctl1 |= USDHC_CTRL_8BITBUS;
}
if (s->hostctl1 & SDHC_CTRL_4BITBUS) {
hostctl1 |= USDHC_CTRL_4BITBUS;
}
ret = hostctl1;
ret |= (uint32_t)s->blkgap << 16;
ret |= (uint32_t)s->wakcon << 24;
break;
case SDHC_PRNSTS:
/* Add SDSTB (SD Clock Stable) bit to PRNSTS */
ret = sdhci_read(opaque, offset, size) & ~USDHC_PRNSTS_SDSTB;
if (s->clkcon & SDHC_CLOCK_INT_STABLE) {
ret |= USDHC_PRNSTS_SDSTB;
}
break;
case USDHC_VENDOR_SPEC:
ret = s->vendor_spec;
break;
case USDHC_DLL_CTRL:
case USDHC_TUNE_CTRL_STATUS:
case USDHC_UNDOCUMENTED_REG27:
case USDHC_TUNING_CTRL:
case USDHC_MIX_CTRL:
case USDHC_WTMK_LVL:
ret = 0;
break;
}
return ret;
}
static void
usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
SDHCIState *s = SYSBUS_SDHCI(opaque);
uint8_t hostctl1;
uint32_t value = (uint32_t)val;
switch (offset) {
case USDHC_DLL_CTRL:
case USDHC_TUNE_CTRL_STATUS:
case USDHC_UNDOCUMENTED_REG27:
case USDHC_TUNING_CTRL:
case USDHC_WTMK_LVL:
break;
case USDHC_VENDOR_SPEC:
s->vendor_spec = value;
switch (s->vendor) {
case SDHCI_VENDOR_IMX:
if (value & USDHC_IMX_FRC_SDCLK_ON) {
s->prnsts &= ~SDHC_IMX_CLOCK_GATE_OFF;
} else {
s->prnsts |= SDHC_IMX_CLOCK_GATE_OFF;
}
break;
default:
break;
}
break;
case SDHC_HOSTCTL:
/*
* Here's What ESDHCI has at offset 0x28 (SDHC_HOSTCTL)
*
* 7 6 5 4 3 2 1 0
* |-----------+--------+--------+-----------+----------+---------|
* | Card | Card | Endian | DATA3 | Data | Led |
* | Detect | Detect | Mode | as Card | Transfer | Control |
* | Signal | Test | | Detection | Width | |
* | Selection | Level | | Pin | | |
* |-----------+--------+--------+-----------+----------+---------|
*
* and 0x29
*
* 15 10 9 8
* |----------+------|
* | Reserved | DMA |
* | | Sel. |
* | | |
* |----------+------|
*
* and here's what SDCHI spec expects those offsets to be:
*
* 0x28 (Host Control Register)
*
* 7 6 5 4 3 2 1 0
* |--------+--------+----------+------+--------+----------+---------|
* | Card | Card | Extended | DMA | High | Data | LED |
* | Detect | Detect | Data | Sel. | Speed | Transfer | Control |
* | Signal | Test | Transfer | | Enable | Width | |
* | Sel. | Level | Width | | | | |
* |--------+--------+----------+------+--------+----------+---------|
*
* and 0x29 (Power Control Register)
*
* |----------------------------------|
* | Power Control Register |
* | |
* | Description omitted, |
* | since it has no analog in ESDHCI |
* | |
* |----------------------------------|
*
* Since offsets 0x2A and 0x2B should be compatible between
* both IP specs we only need to reconcile least 16-bit of the
* word we've been given.
*/
/*
* First, save bits 7 6 and 0 since they are identical
*/
hostctl1 = value & (SDHC_CTRL_LED |
SDHC_CTRL_CDTEST_INS |
SDHC_CTRL_CDTEST_EN);
/*
* Second, split "Data Transfer Width" from bits 2 and 1 in to
* bits 5 and 1
*/
if (value & USDHC_CTRL_8BITBUS) {
hostctl1 |= SDHC_CTRL_8BITBUS;
}
if (value & USDHC_CTRL_4BITBUS) {
hostctl1 |= USDHC_CTRL_4BITBUS;
}
/*
* Third, move DMA select from bits 9 and 8 to bits 4 and 3
*/
hostctl1 |= SDHC_DMA_TYPE(value >> (8 - 3));
/*
* Now place the corrected value into low 16-bit of the value
* we are going to give standard SDHCI write function
*
* NOTE: This transformation should be the inverse of what can
* be found in drivers/mmc/host/sdhci-esdhc-imx.c in Linux
* kernel
*/
value &= ~UINT16_MAX;
value |= hostctl1;
value |= (uint16_t)s->pwrcon << 8;
sdhci_write(opaque, offset, value, size);
break;
case USDHC_MIX_CTRL:
/*
* So, when SD/MMC stack in Linux tries to write to "Transfer
* Mode Register", ESDHC i.MX quirk code will translate it
* into a write to ESDHC_MIX_CTRL, so we do the opposite in
* order to get where we started
*
* Note that Auto CMD23 Enable bit is located in a wrong place
* on i.MX, but since it is not used by QEMU we do not care.
*
* We don't want to call sdhci_write(.., SDHC_TRNMOD, ...)
* here because it will result in a call to
* sdhci_send_command(s) which we don't want.
*
*/
s->trnmod = value & UINT16_MAX;
break;
case SDHC_TRNMOD:
/*
* Similar to above, but this time a write to "Command
* Register" will be translated into a 4-byte write to
* "Transfer Mode register" where lower 16-bit of value would
* be set to zero. So what we do is fill those bits with
* cached value from s->trnmod and let the SDHCI
* infrastructure handle the rest
*/
sdhci_write(opaque, offset, val | s->trnmod, size);
break;
case SDHC_BLKSIZE:
/*
* ESDHCI does not implement "Host SDMA Buffer Boundary", and
* Linux driver will try to zero this field out which will
* break the rest of SDHCI emulation.
*
* Linux defaults to maximum possible setting (512K boundary)
* and it seems to be the only option that i.MX IP implements,
* so we artificially set it to that value.
*/
val |= 0x7 << 12;
/* FALLTHROUGH */
default:
sdhci_write(opaque, offset, val, size);
break;
}
}
static const MemoryRegionOps usdhc_mmio_ops = {
.read = usdhc_read,
.write = usdhc_write,
.valid = {
.min_access_size = 1,
.max_access_size = 4,
.unaligned = false
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void imx_usdhc_init(Object *obj)
{
SDHCIState *s = SYSBUS_SDHCI(obj);
s->io_ops = &usdhc_mmio_ops;
s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ;
}
static const TypeInfo imx_usdhc_info = {
.name = TYPE_IMX_USDHC,
.parent = TYPE_SYSBUS_SDHCI,
.instance_init = imx_usdhc_init,
};
/* --- qdev Samsung s3c --- */
#define S3C_SDHCI_CONTROL2 0x80
#define S3C_SDHCI_CONTROL3 0x84
#define S3C_SDHCI_CONTROL4 0x8c
static uint64_t sdhci_s3c_read(void *opaque, hwaddr offset, unsigned size)
{
uint64_t ret;
switch (offset) {
case S3C_SDHCI_CONTROL2:
case S3C_SDHCI_CONTROL3:
case S3C_SDHCI_CONTROL4:
/* ignore */
ret = 0;
break;
default:
ret = sdhci_read(opaque, offset, size);
break;
}
return ret;
}
static void sdhci_s3c_write(void *opaque, hwaddr offset, uint64_t val,
unsigned size)
{
switch (offset) {
case S3C_SDHCI_CONTROL2:
case S3C_SDHCI_CONTROL3:
case S3C_SDHCI_CONTROL4:
/* ignore */
break;
default:
sdhci_write(opaque, offset, val, size);
break;
}
}
static const MemoryRegionOps sdhci_s3c_mmio_ops = {
.read = sdhci_s3c_read,
.write = sdhci_s3c_write,
.valid = {
.min_access_size = 1,
.max_access_size = 4,
.unaligned = false
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void sdhci_s3c_init(Object *obj)
{
SDHCIState *s = SYSBUS_SDHCI(obj);
s->io_ops = &sdhci_s3c_mmio_ops;
}
static const TypeInfo sdhci_s3c_info = {
.name = TYPE_S3C_SDHCI ,
.parent = TYPE_SYSBUS_SDHCI,
.instance_init = sdhci_s3c_init,
};
static void sdhci_register_types(void)
{
type_register_static(&sdhci_sysbus_info);
type_register_static(&sdhci_bus_info);
type_register_static(&imx_usdhc_info);
type_register_static(&sdhci_s3c_info);
}
type_init(sdhci_register_types)