qemu/hw/sd/sdhci.c
Bin Meng cffb446e8f 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: d7dfca0807 ("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-22 16:56:22 +01:00

1871 lines
59 KiB
C

/*
* 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);
static void sdhci_send_command(SDHCIState *s)
{
SDRequest request;
uint8_t response[16];
int rlen;
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 {
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 && (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
*/
#define BLOCK_SIZE_MASK (4 * KiB - 1)
/* 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++) {
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++) {
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);
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);
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);
} else {
dma_memory_read(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt);
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));
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));
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);
dma_memory_read(s->dma_as, entry_addr + 2, &dscr->length, 2);
dscr->length = le16_to_cpu(dscr->length);
dma_memory_read(s->dma_as, entry_addr + 4, &dscr->addr, 8);
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;
ADMADescr dscr = {};
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;
}
dma_memory_write(s->dma_as, dscr.addr,
&s->fifo_buffer[begin],
s->data_count - begin);
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;
}
dma_memory_read(s->dma_as, dscr.addr,
&s->fifo_buffer[begin],
s->data_count - begin);
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;
}
}
}
}
}
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:
if (!TRANSFERRING_DATA(s->prnsts)) {
s->sdmasysad = (s->sdmasysad & mask) | value;
MASKED_WRITE(s->sdmasysad, mask, value);
/* Writing to last byte of sdmasysad might trigger transfer */
if (!(mask & 0xFF000000) && s->blkcnt && s->blksize &&
SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) {
if (s->trnmod & SDHC_TRNS_MULTI) {
sdhci_sdma_transfer_multi_blocks(s);
} else {
sdhci_sdma_transfer_single_block(s);
}
}
}
break;
case SDHC_BLKSIZE:
if (!TRANSFERRING_DATA(s->prnsts)) {
uint16_t blksize = s->blksize;
MASKED_WRITE(s->blksize, mask, extract32(value, 0, 12));
MASKED_WRITE(s->blkcnt, mask >> 16, value >> 16);
/* Limit block size to the maximum buffer size */
if (extract32(s->blksize, 0, 12) > s->buf_maxsz) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: Size 0x%x is larger than "
"the maximum buffer 0x%x\n", __func__, s->blksize,
s->buf_maxsz);
s->blksize = deposit32(s->blksize, 0, 12, s->buf_maxsz);
}
/*
* If the block size is programmed to a different value from
* the previous one, reset the data pointer of s->fifo_buffer[]
* so that s->fifo_buffer[] can be filled in using the new block
* size in the next transfer.
*/
if (blksize != s->blksize) {
s->data_count = 0;
}
}
break;
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;
}
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_ops = {
.read = sdhci_read,
.write = sdhci_write,
.valid = {
.min_access_size = 1,
.max_access_size = 4,
.unaligned = false
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void sdhci_init_readonly_registers(SDHCIState *s, Error **errp)
{
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);
sdhci_check_capareg(s, errp);
if (*errp) {
return;
}
}
/* --- qdev common --- */
void sdhci_initfn(SDHCIState *s)
{
qbus_create_inplace(&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_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)
{
ERRP_GUARD();
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);
}
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 = (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 = (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*[]) {
&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)
{
ERRP_GUARD();
SDHCIState *s = SYSBUS_SDHCI(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
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);
}
static void sdhci_sysbus_unrealize(DeviceState *dev)
{
SDHCIState *s = SYSBUS_SDHCI(dev);
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 --- */
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 |= ESDHC_CTRL_8BITBUS;
}
if (s->hostctl1 & SDHC_CTRL_4BITBUS) {
hostctl1 |= ESDHC_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) & ~ESDHC_PRNSTS_SDSTB;
if (s->clkcon & SDHC_CLOCK_INT_STABLE) {
ret |= ESDHC_PRNSTS_SDSTB;
}
break;
case ESDHC_VENDOR_SPEC:
ret = s->vendor_spec;
break;
case ESDHC_DLL_CTRL:
case ESDHC_TUNE_CTRL_STATUS:
case ESDHC_UNDOCUMENTED_REG27:
case ESDHC_TUNING_CTRL:
case ESDHC_MIX_CTRL:
case ESDHC_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 ESDHC_DLL_CTRL:
case ESDHC_TUNE_CTRL_STATUS:
case ESDHC_UNDOCUMENTED_REG27:
case ESDHC_TUNING_CTRL:
case ESDHC_WTMK_LVL:
break;
case ESDHC_VENDOR_SPEC:
s->vendor_spec = value;
switch (s->vendor) {
case SDHCI_VENDOR_IMX:
if (value & ESDHC_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 & ESDHC_CTRL_8BITBUS) {
hostctl1 |= SDHC_CTRL_8BITBUS;
}
if (value & ESDHC_CTRL_4BITBUS) {
hostctl1 |= ESDHC_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 ESDHC_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 becuase 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)