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 }
220