qemu/tests/sdhci-test.c
<<
>>
Prefs
   1/*
   2 * QTest testcase for SDHCI controllers
   3 *
   4 * Written by Philippe Mathieu-Daudé <f4bug@amsat.org>
   5 *
   6 * This work is licensed under the terms of the GNU GPL, version 2 or later.
   7 * See the COPYING file in the top-level directory.
   8 * SPDX-License-Identifier: GPL-2.0-or-later
   9 */
  10#include "qemu/osdep.h"
  11#include "hw/registerfields.h"
  12#include "libqtest.h"
  13#include "libqos/pci-pc.h"
  14#include "hw/pci/pci.h"
  15
  16#define SDHC_CAPAB                      0x40
  17FIELD(SDHC_CAPAB, BASECLKFREQ,               8, 8); /* since v2 */
  18FIELD(SDHC_CAPAB, SDMA,                     22, 1);
  19FIELD(SDHC_CAPAB, SDR,                      32, 3); /* since v3 */
  20FIELD(SDHC_CAPAB, DRIVER,                   36, 3); /* since v3 */
  21#define SDHC_HCVER                      0xFE
  22
  23static const struct sdhci_t {
  24    const char *arch, *machine;
  25    struct {
  26        uintptr_t addr;
  27        uint8_t version;
  28        uint8_t baseclock;
  29        struct {
  30            bool sdma;
  31            uint64_t reg;
  32        } capab;
  33    } sdhci;
  34    struct {
  35        uint16_t vendor_id, device_id;
  36    } pci;
  37} models[] = {
  38    /* PC via PCI */
  39    { "x86_64", "pc",
  40        {-1,         2, 0,  {1, 0x057834b4} },
  41        .pci = { PCI_VENDOR_ID_REDHAT, PCI_DEVICE_ID_REDHAT_SDHCI } },
  42
  43    /* Exynos4210 */
  44    { "arm",    "smdkc210",
  45        {0x12510000, 2, 0,  {1, 0x5e80080} } },
  46
  47    /* i.MX 6 */
  48    { "arm",    "sabrelite",
  49        {0x02190000, 3, 0,  {1, 0x057834b4} } },
  50
  51    /* BCM2835 */
  52    { "arm",    "raspi2",
  53        {0x3f300000, 3, 52, {0, 0x052134b4} } },
  54
  55    /* Zynq-7000 */
  56    { "arm",    "xilinx-zynq-a9",   /* Datasheet: UG585 (v1.12.1) */
  57        {0xe0100000, 2, 0,  {1, 0x69ec0080} } },
  58
  59    /* ZynqMP */
  60    { "aarch64", "xlnx-zcu102",     /* Datasheet: UG1085 (v1.7) */
  61        {0xff160000, 3, 0,  {1, 0x280737ec6481} } },
  62
  63};
  64
  65typedef struct QSDHCI {
  66    struct {
  67        QPCIBus *bus;
  68        QPCIDevice *dev;
  69    } pci;
  70    union {
  71        QPCIBar mem_bar;
  72        uint64_t addr;
  73    };
  74} QSDHCI;
  75
  76static uint16_t sdhci_readw(QSDHCI *s, uint32_t reg)
  77{
  78    uint16_t val;
  79
  80    if (s->pci.dev) {
  81        val = qpci_io_readw(s->pci.dev, s->mem_bar, reg);
  82    } else {
  83        val = qtest_readw(global_qtest, s->addr + reg);
  84    }
  85
  86    return val;
  87}
  88
  89static uint64_t sdhci_readq(QSDHCI *s, uint32_t reg)
  90{
  91    uint64_t val;
  92
  93    if (s->pci.dev) {
  94        val = qpci_io_readq(s->pci.dev, s->mem_bar, reg);
  95    } else {
  96        val = qtest_readq(global_qtest, s->addr + reg);
  97    }
  98
  99    return val;
 100}
 101
 102static void sdhci_writeq(QSDHCI *s, uint32_t reg, uint64_t val)
 103{
 104    if (s->pci.dev) {
 105        qpci_io_writeq(s->pci.dev, s->mem_bar, reg, val);
 106    } else {
 107        qtest_writeq(global_qtest, s->addr + reg, val);
 108    }
 109}
 110
 111static void check_specs_version(QSDHCI *s, uint8_t version)
 112{
 113    uint32_t v;
 114
 115    v = sdhci_readw(s, SDHC_HCVER);
 116    v &= 0xff;
 117    v += 1;
 118    g_assert_cmpuint(v, ==, version);
 119}
 120
 121static void check_capab_capareg(QSDHCI *s, uint64_t expec_capab)
 122{
 123    uint64_t capab;
 124
 125    capab = sdhci_readq(s, SDHC_CAPAB);
 126    g_assert_cmphex(capab, ==, expec_capab);
 127}
 128
 129static void check_capab_readonly(QSDHCI *s)
 130{
 131    const uint64_t vrand = 0x123456789abcdef;
 132    uint64_t capab0, capab1;
 133
 134    capab0 = sdhci_readq(s, SDHC_CAPAB);
 135    g_assert_cmpuint(capab0, !=, vrand);
 136
 137    sdhci_writeq(s, SDHC_CAPAB, vrand);
 138    capab1 = sdhci_readq(s, SDHC_CAPAB);
 139    g_assert_cmpuint(capab1, !=, vrand);
 140    g_assert_cmpuint(capab1, ==, capab0);
 141}
 142
 143static void check_capab_baseclock(QSDHCI *s, uint8_t expec_freq)
 144{
 145    uint64_t capab, capab_freq;
 146
 147    if (!expec_freq) {
 148        return;
 149    }
 150    capab = sdhci_readq(s, SDHC_CAPAB);
 151    capab_freq = FIELD_EX64(capab, SDHC_CAPAB, BASECLKFREQ);
 152    g_assert_cmpuint(capab_freq, ==, expec_freq);
 153}
 154
 155static void check_capab_sdma(QSDHCI *s, bool supported)
 156{
 157    uint64_t capab, capab_sdma;
 158
 159    capab = sdhci_readq(s, SDHC_CAPAB);
 160    capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
 161    g_assert_cmpuint(capab_sdma, ==, supported);
 162}
 163
 164static void check_capab_v3(QSDHCI *s, uint8_t version)
 165{
 166    uint64_t capab, capab_v3;
 167
 168    if (version < 3) {
 169        /* before v3 those fields are RESERVED */
 170        capab = sdhci_readq(s, SDHC_CAPAB);
 171        capab_v3 = FIELD_EX64(capab, SDHC_CAPAB, SDR);
 172        g_assert_cmpuint(capab_v3, ==, 0);
 173        capab_v3 = FIELD_EX64(capab, SDHC_CAPAB, DRIVER);
 174        g_assert_cmpuint(capab_v3, ==, 0);
 175    }
 176}
 177
 178static QSDHCI *machine_start(const struct sdhci_t *test)
 179{
 180    QSDHCI *s = g_new0(QSDHCI, 1);
 181
 182    if (test->pci.vendor_id) {
 183        /* PCI */
 184        uint16_t vendor_id, device_id;
 185        uint64_t barsize;
 186
 187        global_qtest = qtest_startf("-machine %s -device sdhci-pci",
 188                                    test->machine);
 189
 190        s->pci.bus = qpci_init_pc(global_qtest, NULL);
 191
 192        /* Find PCI device and verify it's the right one */
 193        s->pci.dev = qpci_device_find(s->pci.bus, QPCI_DEVFN(4, 0));
 194        g_assert_nonnull(s->pci.dev);
 195        vendor_id = qpci_config_readw(s->pci.dev, PCI_VENDOR_ID);
 196        device_id = qpci_config_readw(s->pci.dev, PCI_DEVICE_ID);
 197        g_assert(vendor_id == test->pci.vendor_id);
 198        g_assert(device_id == test->pci.device_id);
 199        s->mem_bar = qpci_iomap(s->pci.dev, 0, &barsize);
 200        qpci_device_enable(s->pci.dev);
 201    } else {
 202        /* SysBus */
 203        global_qtest = qtest_startf("-machine %s", test->machine);
 204        s->addr = test->sdhci.addr;
 205    }
 206
 207    return s;
 208}
 209
 210static void machine_stop(QSDHCI *s)
 211{
 212    qpci_free_pc(s->pci.bus);
 213    g_free(s->pci.dev);
 214    qtest_quit(global_qtest);
 215    g_free(s);
 216}
 217
 218static void test_machine(const void *data)
 219{
 220    const struct sdhci_t *test = data;
 221    QSDHCI *s;
 222
 223    s = machine_start(test);
 224
 225    check_specs_version(s, test->sdhci.version);
 226    check_capab_capareg(s, test->sdhci.capab.reg);
 227    check_capab_readonly(s);
 228    check_capab_v3(s, test->sdhci.version);
 229    check_capab_sdma(s, test->sdhci.capab.sdma);
 230    check_capab_baseclock(s, test->sdhci.baseclock);
 231
 232    machine_stop(s);
 233}
 234
 235int main(int argc, char *argv[])
 236{
 237    const char *arch = qtest_get_arch();
 238    char *name;
 239    int i;
 240
 241    g_test_init(&argc, &argv, NULL);
 242    for (i = 0; i < ARRAY_SIZE(models); i++) {
 243        if (strcmp(arch, models[i].arch)) {
 244            continue;
 245        }
 246        name = g_strdup_printf("sdhci/%s", models[i].machine);
 247        qtest_add_data_func(name, &models[i], test_machine);
 248        g_free(name);
 249    }
 250
 251    return g_test_run();
 252}
 253