| 1 | /* |
| 2 | * QTests for NPCM7xx SD-3.0 / MMC-4.51 Host Controller |
| 3 | * |
| 4 | * Copyright (c) 2022 Google LLC |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify it |
| 7 | * under the terms of the GNU General Public License as published by the |
| 8 | * Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * for more details. |
| 15 | */ |
| 16 | |
| 17 | #include "qemu/osdep.h" |
| 18 | #include "hw/sd/npcm7xx_sdhci.h" |
| 19 | |
| 20 | #include "libqtest.h" |
| 21 | #include "libqtest-single.h" |
| 22 | #include "libqos/sdhci-cmd.h" |
| 23 | |
| 24 | #define NPCM7XX_REG_SIZE 0x100 |
| 25 | #define NPCM7XX_MMC_BA 0xF0842000 |
| 26 | #define NPCM7XX_BLK_SIZE 512 |
| 27 | #define NPCM7XX_TEST_IMAGE_SIZE (1 << 20) |
| 28 | |
| 29 | char *sd_path; |
| 30 | |
| 31 | static QTestState *setup_sd_card(void) |
| 32 | { |
| 33 | uint16_t rca; |
| 34 | |
| 35 | QTestState *qts = qtest_initf( |
| 36 | "-machine kudo-bmc " |
| 37 | "-device sd-card,drive=drive0 " |
| 38 | "-drive id=drive0,if=none,file=%s,format=raw,auto-read-only=off", |
| 39 | sd_path); |
| 40 | |
| 41 | qtest_writew(qts, NPCM7XX_MMC_BA + SDHC_SWRST, SDHC_RESET_ALL); |
| 42 | qtest_writew(qts, NPCM7XX_MMC_BA + SDHC_CLKCON, |
| 43 | SDHC_CLOCK_SDCLK_EN | SDHC_CLOCK_INT_STABLE | |
| 44 | SDHC_CLOCK_INT_EN); |
| 45 | sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0, 0, SDHC_APP_CMD); |
| 46 | sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0x41200000, 0, (41 << 8)); |
| 47 | sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0, 0, SDHC_ALL_SEND_CID); |
| 48 | sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, 0, 0, SDHC_SEND_RELATIVE_ADDR |
| 49 | | SDHC_CMD_RESPONSE); |
| 50 | rca = qtest_readl(qts, NPCM7XX_MMC_BA + SDHC_RSPREG0) >> 16; |
| 51 | sdhci_cmd_regs(qts, NPCM7XX_MMC_BA, 0, 0, rca << 16, 0, |
| 52 | SDHC_SELECT_DESELECT_CARD); |
| 53 | |
| 54 | return qts; |
| 55 | } |
| 56 | |
| 57 | static void write_sdread(QTestState *qts, const char *msg) |
| 58 | { |
| 59 | int fd, ret; |
| 60 | size_t len = strlen(msg); |
| 61 | char *rmsg = g_malloc(len); |
| 62 | |
| 63 | /* write message to sd */ |
| 64 | fd = open(sd_path, O_WRONLY); |
| 65 | g_assert(fd >= 0); |
| 66 | ret = write(fd, msg, len); |
| 67 | close(fd); |
| 68 | g_assert(ret == len); |
| 69 | |
| 70 | /* read message using sdhci */ |
| 71 | ret = sdhci_read_cmd(qts, NPCM7XX_MMC_BA, rmsg, len); |
| 72 | g_assert(ret == len); |
| 73 | g_assert(!memcmp(rmsg, msg, len)); |
| 74 | |
| 75 | g_free(rmsg); |
| 76 | } |
| 77 | |
| 78 | /* Check MMC can read values from sd */ |
| 79 | static void test_read_sd(void) |
| 80 | { |
| 81 | QTestState *qts = setup_sd_card(); |
| 82 | |
| 83 | write_sdread(qts, "hello world"); |
| 84 | write_sdread(qts, "goodbye"); |
| 85 | |
| 86 | qtest_quit(qts); |
| 87 | } |
| 88 | |
| 89 | static void sdwrite_read(QTestState *qts, const char *msg) |
| 90 | { |
| 91 | int fd, ret; |
| 92 | size_t len = strlen(msg); |
| 93 | char *rmsg = g_malloc(len); |
| 94 | |
| 95 | /* write message using sdhci */ |
| 96 | sdhci_write_cmd(qts, NPCM7XX_MMC_BA, msg, len, NPCM7XX_BLK_SIZE); |
| 97 | |
| 98 | /* read message from sd */ |
| 99 | fd = open(sd_path, O_RDONLY); |
| 100 | g_assert(fd >= 0); |
| 101 | ret = read(fd, rmsg, len); |
| 102 | close(fd); |
| 103 | g_assert(ret == len); |
| 104 | |
| 105 | g_assert(!memcmp(rmsg, msg, len)); |
| 106 | |
| 107 | g_free(rmsg); |
| 108 | } |
| 109 | |
| 110 | /* Check MMC can write values to sd */ |
| 111 | static void test_write_sd(void) |
| 112 | { |
| 113 | QTestState *qts = setup_sd_card(); |
| 114 | |
| 115 | sdwrite_read(qts, "hello world"); |
| 116 | sdwrite_read(qts, "goodbye"); |
| 117 | |
| 118 | qtest_quit(qts); |
| 119 | } |
| 120 | |
| 121 | /* Check SDHCI has correct default values. */ |
| 122 | static void test_reset(void) |
| 123 | { |
| 124 | QTestState *qts = qtest_init("-machine kudo-bmc"); |
| 125 | uint64_t addr = NPCM7XX_MMC_BA; |
| 126 | uint64_t end_addr = addr + NPCM7XX_REG_SIZE; |
| 127 | uint16_t prstvals_resets[] = {NPCM7XX_PRSTVALS_0_RESET, |
| 128 | NPCM7XX_PRSTVALS_1_RESET, |
| 129 | 0, |
| 130 | NPCM7XX_PRSTVALS_3_RESET, |
| 131 | 0, |
| 132 | 0}; |
| 133 | int i; |
| 134 | uint32_t mask; |
| 135 | |
| 136 | while (addr < end_addr) { |
| 137 | switch (addr - NPCM7XX_MMC_BA) { |
| 138 | case SDHC_PRNSTS: |
| 139 | /* |
| 140 | * ignores bits 20 to 24: they are changed when reading registers |
| 141 | */ |
| 142 | mask = 0x1f00000; |
| 143 | g_assert_cmphex(qtest_readl(qts, addr) | mask, ==, |
| 144 | NPCM7XX_PRSNTS_RESET | mask); |
| 145 | addr += 4; |
| 146 | break; |
| 147 | case SDHC_BLKGAP: |
| 148 | g_assert_cmphex(qtest_readb(qts, addr), ==, NPCM7XX_BLKGAP_RESET); |
| 149 | addr += 1; |
| 150 | break; |
| 151 | case SDHC_CAPAB: |
| 152 | g_assert_cmphex(qtest_readq(qts, addr), ==, NPCM7XX_CAPAB_RESET); |
| 153 | addr += 8; |
| 154 | break; |
| 155 | case SDHC_MAXCURR: |
| 156 | g_assert_cmphex(qtest_readq(qts, addr), ==, NPCM7XX_MAXCURR_RESET); |
| 157 | addr += 8; |
| 158 | break; |
| 159 | case SDHC_HCVER: |
| 160 | g_assert_cmphex(qtest_readw(qts, addr), ==, NPCM7XX_HCVER_RESET); |
| 161 | addr += 2; |
| 162 | break; |
| 163 | case NPCM7XX_PRSTVALS: |
| 164 | for (i = 0; i < NPCM7XX_PRSTVALS_SIZE; ++i) { |
| 165 | g_assert_cmphex(qtest_readw(qts, addr + 2 * i), ==, |
| 166 | prstvals_resets[i]); |
| 167 | } |
| 168 | addr += NPCM7XX_PRSTVALS_SIZE * 2; |
| 169 | break; |
| 170 | default: |
| 171 | g_assert_cmphex(qtest_readb(qts, addr), ==, 0); |
| 172 | addr += 1; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | qtest_quit(qts); |
| 177 | } |
| 178 | |
| 179 | static void drive_destroy(void) |
| 180 | { |
| 181 | unlink(sd_path); |
| 182 | g_free(sd_path); |
| 183 | } |
| 184 | |
| 185 | static void drive_create(void) |
| 186 | { |
| 187 | int fd, ret; |
| 188 | GError *error = NULL; |
| 189 | |
| 190 | /* Create a temporary raw image */ |
| 191 | fd = g_file_open_tmp("sdhci_XXXXXX", &sd_path, &error); |
| 192 | if (fd == -1) { |
| 193 | fprintf(stderr, "unable to create sdhci file: %s\n", error->message); |
| 194 | g_error_free(error); |
| 195 | } |
| 196 | g_assert(sd_path != NULL); |
| 197 | |
| 198 | ret = ftruncate(fd, NPCM7XX_TEST_IMAGE_SIZE); |
| 199 | g_assert_cmpint(ret, ==, 0); |
| 200 | g_message("%s", sd_path); |
| 201 | close(fd); |
| 202 | } |
| 203 | |
| 204 | int main(int argc, char **argv) |
| 205 | { |
| 206 | int ret; |
| 207 | |
| 208 | drive_create(); |
| 209 | |
| 210 | g_test_init(&argc, &argv, NULL); |
| 211 | |
| 212 | qtest_add_func("npcm7xx_sdhci/reset", test_reset); |
| 213 | qtest_add_func("npcm7xx_sdhci/write_sd", test_write_sd); |
| 214 | qtest_add_func("npcm7xx_sdhci/read_sd", test_read_sd); |
| 215 | |
| 216 | ret = g_test_run(); |
| 217 | drive_destroy(); |
| 218 | return ret; |
| 219 | } |