sdhci-test.c 5.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * QTest testcase for SDHCI controllers
 *
 * Written by Philippe Mathieu-Daudé <f4bug@amsat.org>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 * SPDX-License-Identifier: GPL-2.0-or-later
 */
#include "qemu/osdep.h"
#include "hw/registerfields.h"
#include "libqtest.h"
#include "libqos/pci-pc.h"
#include "hw/pci/pci.h"

#define SDHC_CAPAB                      0x40
17
FIELD(SDHC_CAPAB, BASECLKFREQ,               8, 8); /* since v2 */
18
FIELD(SDHC_CAPAB, SDMA,                     22, 1);
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
#define SDHC_HCVER                      0xFE

static const struct sdhci_t {
    const char *arch, *machine;
    struct {
        uintptr_t addr;
        uint8_t version;
        uint8_t baseclock;
        struct {
            bool sdma;
            uint64_t reg;
        } capab;
    } sdhci;
    struct {
        uint16_t vendor_id, device_id;
    } pci;
} models[] = {
    /* PC via PCI */
    { "x86_64", "pc",
        {-1,         2, 0,  {1, 0x057834b4} },
        .pci = { PCI_VENDOR_ID_REDHAT, PCI_DEVICE_ID_REDHAT_SDHCI } },

    /* Exynos4210 */
    { "arm",    "smdkc210",
        {0x12510000, 2, 0,  {1, 0x5e80080} } },
};

typedef struct QSDHCI {
    struct {
        QPCIBus *bus;
        QPCIDevice *dev;
    } pci;
    union {
        QPCIBar mem_bar;
        uint64_t addr;
    };
} QSDHCI;

57 58 59 60 61 62 63 64 65 66 67 68 69
static uint16_t sdhci_readw(QSDHCI *s, uint32_t reg)
{
    uint16_t val;

    if (s->pci.dev) {
        val = qpci_io_readw(s->pci.dev, s->mem_bar, reg);
    } else {
        val = qtest_readw(global_qtest, s->addr + reg);
    }

    return val;
}

70 71 72 73 74 75 76 77 78 79 80 81 82
static uint64_t sdhci_readq(QSDHCI *s, uint32_t reg)
{
    uint64_t val;

    if (s->pci.dev) {
        val = qpci_io_readq(s->pci.dev, s->mem_bar, reg);
    } else {
        val = qtest_readq(global_qtest, s->addr + reg);
    }

    return val;
}

83 84 85 86 87 88 89 90 91
static void sdhci_writeq(QSDHCI *s, uint32_t reg, uint64_t val)
{
    if (s->pci.dev) {
        qpci_io_writeq(s->pci.dev, s->mem_bar, reg, val);
    } else {
        qtest_writeq(global_qtest, s->addr + reg, val);
    }
}

92 93 94 95 96 97 98 99 100 101
static void check_specs_version(QSDHCI *s, uint8_t version)
{
    uint32_t v;

    v = sdhci_readw(s, SDHC_HCVER);
    v &= 0xff;
    v += 1;
    g_assert_cmpuint(v, ==, version);
}

102 103 104 105 106 107 108 109
static void check_capab_capareg(QSDHCI *s, uint64_t expec_capab)
{
    uint64_t capab;

    capab = sdhci_readq(s, SDHC_CAPAB);
    g_assert_cmphex(capab, ==, expec_capab);
}

110 111 112 113 114 115 116 117 118 119 120 121 122 123
static void check_capab_readonly(QSDHCI *s)
{
    const uint64_t vrand = 0x123456789abcdef;
    uint64_t capab0, capab1;

    capab0 = sdhci_readq(s, SDHC_CAPAB);
    g_assert_cmpuint(capab0, !=, vrand);

    sdhci_writeq(s, SDHC_CAPAB, vrand);
    capab1 = sdhci_readq(s, SDHC_CAPAB);
    g_assert_cmpuint(capab1, !=, vrand);
    g_assert_cmpuint(capab1, ==, capab0);
}

124 125 126 127 128 129 130 131 132 133 134 135
static void check_capab_baseclock(QSDHCI *s, uint8_t expec_freq)
{
    uint64_t capab, capab_freq;

    if (!expec_freq) {
        return;
    }
    capab = sdhci_readq(s, SDHC_CAPAB);
    capab_freq = FIELD_EX64(capab, SDHC_CAPAB, BASECLKFREQ);
    g_assert_cmpuint(capab_freq, ==, expec_freq);
}

136 137 138 139 140 141 142 143 144
static void check_capab_sdma(QSDHCI *s, bool supported)
{
    uint64_t capab, capab_sdma;

    capab = sdhci_readq(s, SDHC_CAPAB);
    capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
    g_assert_cmpuint(capab_sdma, ==, supported);
}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
static QSDHCI *machine_start(const struct sdhci_t *test)
{
    QSDHCI *s = g_new0(QSDHCI, 1);

    if (test->pci.vendor_id) {
        /* PCI */
        uint16_t vendor_id, device_id;
        uint64_t barsize;

        global_qtest = qtest_startf("-machine %s -device sdhci-pci",
                                    test->machine);

        s->pci.bus = qpci_init_pc(NULL);

        /* Find PCI device and verify it's the right one */
        s->pci.dev = qpci_device_find(s->pci.bus, QPCI_DEVFN(4, 0));
        g_assert_nonnull(s->pci.dev);
        vendor_id = qpci_config_readw(s->pci.dev, PCI_VENDOR_ID);
        device_id = qpci_config_readw(s->pci.dev, PCI_DEVICE_ID);
        g_assert(vendor_id == test->pci.vendor_id);
        g_assert(device_id == test->pci.device_id);
        s->mem_bar = qpci_iomap(s->pci.dev, 0, &barsize);
        qpci_device_enable(s->pci.dev);
    } else {
        /* SysBus */
        global_qtest = qtest_startf("-machine %s", test->machine);
        s->addr = test->sdhci.addr;
    }

    return s;
}

static void machine_stop(QSDHCI *s)
{
    g_free(s->pci.dev);
    qtest_quit(global_qtest);
}

static void test_machine(const void *data)
{
    const struct sdhci_t *test = data;
    QSDHCI *s;

    s = machine_start(test);

190
    check_specs_version(s, test->sdhci.version);
191
    check_capab_capareg(s, test->sdhci.capab.reg);
192
    check_capab_readonly(s);
193
    check_capab_sdma(s, test->sdhci.capab.sdma);
194
    check_capab_baseclock(s, test->sdhci.baseclock);
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

    machine_stop(s);
}

int main(int argc, char *argv[])
{
    const char *arch = qtest_get_arch();
    char *name;
    int i;

    g_test_init(&argc, &argv, NULL);
    for (i = 0; i < ARRAY_SIZE(models); i++) {
        if (strcmp(arch, models[i].arch)) {
            continue;
        }
        name = g_strdup_printf("sdhci/%s", models[i].machine);
        qtest_add_data_func(name, &models[i], test_machine);
        g_free(name);
    }

    return g_test_run();
}