root / tests / ide-test.c @ f487b677
History | View | Annotate | Download (12.9 kB)
1 |
/*
|
---|---|
2 |
* IDE test cases
|
3 |
*
|
4 |
* Copyright (c) 2013 Kevin Wolf <kwolf@redhat.com>
|
5 |
*
|
6 |
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
7 |
* of this software and associated documentation files (the "Software"), to deal
|
8 |
* in the Software without restriction, including without limitation the rights
|
9 |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10 |
* copies of the Software, and to permit persons to whom the Software is
|
11 |
* furnished to do so, subject to the following conditions:
|
12 |
*
|
13 |
* The above copyright notice and this permission notice shall be included in
|
14 |
* all copies or substantial portions of the Software.
|
15 |
*
|
16 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
19 |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22 |
* THE SOFTWARE.
|
23 |
*/
|
24 |
|
25 |
#include <stdint.h> |
26 |
#include <string.h> |
27 |
#include <stdio.h> |
28 |
|
29 |
#include <glib.h> |
30 |
|
31 |
#include "libqtest.h" |
32 |
#include "libqos/pci-pc.h" |
33 |
#include "libqos/malloc-pc.h" |
34 |
|
35 |
#include "qemu-common.h" |
36 |
#include "hw/pci/pci_ids.h" |
37 |
#include "hw/pci/pci_regs.h" |
38 |
|
39 |
#define TEST_IMAGE_SIZE 64 * 1024 * 1024 |
40 |
|
41 |
#define IDE_PCI_DEV 1 |
42 |
#define IDE_PCI_FUNC 1 |
43 |
|
44 |
#define IDE_BASE 0x1f0 |
45 |
#define IDE_PRIMARY_IRQ 14 |
46 |
|
47 |
enum {
|
48 |
reg_data = 0x0,
|
49 |
reg_nsectors = 0x2,
|
50 |
reg_lba_low = 0x3,
|
51 |
reg_lba_middle = 0x4,
|
52 |
reg_lba_high = 0x5,
|
53 |
reg_device = 0x6,
|
54 |
reg_status = 0x7,
|
55 |
reg_command = 0x7,
|
56 |
}; |
57 |
|
58 |
enum {
|
59 |
BSY = 0x80,
|
60 |
DRDY = 0x40,
|
61 |
DF = 0x20,
|
62 |
DRQ = 0x08,
|
63 |
ERR = 0x01,
|
64 |
}; |
65 |
|
66 |
enum {
|
67 |
DEV = 0x10,
|
68 |
LBA = 0x40,
|
69 |
}; |
70 |
|
71 |
enum {
|
72 |
bmreg_cmd = 0x0,
|
73 |
bmreg_status = 0x2,
|
74 |
bmreg_prdt = 0x4,
|
75 |
}; |
76 |
|
77 |
enum {
|
78 |
CMD_READ_DMA = 0xc8,
|
79 |
CMD_WRITE_DMA = 0xca,
|
80 |
CMD_FLUSH_CACHE = 0xe7,
|
81 |
CMD_IDENTIFY = 0xec,
|
82 |
|
83 |
CMDF_ABORT = 0x100,
|
84 |
}; |
85 |
|
86 |
enum {
|
87 |
BM_CMD_START = 0x1,
|
88 |
BM_CMD_WRITE = 0x8, /* write = from device to memory */ |
89 |
}; |
90 |
|
91 |
enum {
|
92 |
BM_STS_ACTIVE = 0x1,
|
93 |
BM_STS_ERROR = 0x2,
|
94 |
BM_STS_INTR = 0x4,
|
95 |
}; |
96 |
|
97 |
enum {
|
98 |
PRDT_EOT = 0x80000000,
|
99 |
}; |
100 |
|
101 |
#define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
|
102 |
#define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0) |
103 |
|
104 |
static QPCIBus *pcibus = NULL; |
105 |
static QGuestAllocator *guest_malloc;
|
106 |
|
107 |
static char tmp_path[] = "/tmp/qtest.XXXXXX"; |
108 |
|
109 |
static void ide_test_start(const char *cmdline_fmt, ...) |
110 |
{ |
111 |
va_list ap; |
112 |
char *cmdline;
|
113 |
|
114 |
va_start(ap, cmdline_fmt); |
115 |
cmdline = g_strdup_vprintf(cmdline_fmt, ap); |
116 |
va_end(ap); |
117 |
|
118 |
qtest_start(cmdline); |
119 |
qtest_irq_intercept_in(global_qtest, "ioapic");
|
120 |
guest_malloc = pc_alloc_init(); |
121 |
} |
122 |
|
123 |
static void ide_test_quit(void) |
124 |
{ |
125 |
qtest_quit(global_qtest); |
126 |
} |
127 |
|
128 |
static QPCIDevice *get_pci_device(uint16_t *bmdma_base)
|
129 |
{ |
130 |
QPCIDevice *dev; |
131 |
uint16_t vendor_id, device_id; |
132 |
|
133 |
if (!pcibus) {
|
134 |
pcibus = qpci_init_pc(); |
135 |
} |
136 |
|
137 |
/* Find PCI device and verify it's the right one */
|
138 |
dev = qpci_device_find(pcibus, QPCI_DEVFN(IDE_PCI_DEV, IDE_PCI_FUNC)); |
139 |
g_assert(dev != NULL);
|
140 |
|
141 |
vendor_id = qpci_config_readw(dev, PCI_VENDOR_ID); |
142 |
device_id = qpci_config_readw(dev, PCI_DEVICE_ID); |
143 |
g_assert(vendor_id == PCI_VENDOR_ID_INTEL); |
144 |
g_assert(device_id == PCI_DEVICE_ID_INTEL_82371SB_1); |
145 |
|
146 |
/* Map bmdma BAR */
|
147 |
*bmdma_base = (uint16_t)(uintptr_t) qpci_iomap(dev, 4);
|
148 |
|
149 |
qpci_device_enable(dev); |
150 |
|
151 |
return dev;
|
152 |
} |
153 |
|
154 |
static void free_pci_device(QPCIDevice *dev) |
155 |
{ |
156 |
/* libqos doesn't have a function for this, so free it manually */
|
157 |
g_free(dev); |
158 |
} |
159 |
|
160 |
typedef struct PrdtEntry { |
161 |
uint32_t addr; |
162 |
uint32_t size; |
163 |
} QEMU_PACKED PrdtEntry; |
164 |
|
165 |
#define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
|
166 |
#define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0) |
167 |
|
168 |
static int send_dma_request(int cmd, uint64_t sector, int nb_sectors, |
169 |
PrdtEntry *prdt, int prdt_entries)
|
170 |
{ |
171 |
QPCIDevice *dev; |
172 |
uint16_t bmdma_base; |
173 |
uintptr_t guest_prdt; |
174 |
size_t len; |
175 |
bool from_dev;
|
176 |
uint8_t status; |
177 |
int flags;
|
178 |
|
179 |
dev = get_pci_device(&bmdma_base); |
180 |
|
181 |
flags = cmd & ~0xff;
|
182 |
cmd &= 0xff;
|
183 |
|
184 |
switch (cmd) {
|
185 |
case CMD_READ_DMA:
|
186 |
from_dev = true;
|
187 |
break;
|
188 |
case CMD_WRITE_DMA:
|
189 |
from_dev = false;
|
190 |
break;
|
191 |
default:
|
192 |
g_assert_not_reached(); |
193 |
} |
194 |
|
195 |
/* Select device 0 */
|
196 |
outb(IDE_BASE + reg_device, 0 | LBA);
|
197 |
|
198 |
/* Stop any running transfer, clear any pending interrupt */
|
199 |
outb(bmdma_base + bmreg_cmd, 0);
|
200 |
outb(bmdma_base + bmreg_status, BM_STS_INTR); |
201 |
|
202 |
/* Setup PRDT */
|
203 |
len = sizeof(*prdt) * prdt_entries;
|
204 |
guest_prdt = guest_alloc(guest_malloc, len); |
205 |
memwrite(guest_prdt, prdt, len); |
206 |
outl(bmdma_base + bmreg_prdt, guest_prdt); |
207 |
|
208 |
/* ATA DMA command */
|
209 |
outb(IDE_BASE + reg_nsectors, nb_sectors); |
210 |
|
211 |
outb(IDE_BASE + reg_lba_low, sector & 0xff);
|
212 |
outb(IDE_BASE + reg_lba_middle, (sector >> 8) & 0xff); |
213 |
outb(IDE_BASE + reg_lba_high, (sector >> 16) & 0xff); |
214 |
|
215 |
outb(IDE_BASE + reg_command, cmd); |
216 |
|
217 |
/* Start DMA transfer */
|
218 |
outb(bmdma_base + bmreg_cmd, BM_CMD_START | (from_dev ? BM_CMD_WRITE : 0));
|
219 |
|
220 |
if (flags & CMDF_ABORT) {
|
221 |
outb(bmdma_base + bmreg_cmd, 0);
|
222 |
} |
223 |
|
224 |
/* Wait for the DMA transfer to complete */
|
225 |
do {
|
226 |
status = inb(bmdma_base + bmreg_status); |
227 |
} while ((status & (BM_STS_ACTIVE | BM_STS_INTR)) == BM_STS_ACTIVE);
|
228 |
|
229 |
g_assert_cmpint(get_irq(IDE_PRIMARY_IRQ), ==, !!(status & BM_STS_INTR)); |
230 |
|
231 |
/* Check IDE status code */
|
232 |
assert_bit_set(inb(IDE_BASE + reg_status), DRDY); |
233 |
assert_bit_clear(inb(IDE_BASE + reg_status), BSY | DRQ); |
234 |
|
235 |
/* Reading the status register clears the IRQ */
|
236 |
g_assert(!get_irq(IDE_PRIMARY_IRQ)); |
237 |
|
238 |
/* Stop DMA transfer if still active */
|
239 |
if (status & BM_STS_ACTIVE) {
|
240 |
outb(bmdma_base + bmreg_cmd, 0);
|
241 |
} |
242 |
|
243 |
free_pci_device(dev); |
244 |
|
245 |
return status;
|
246 |
} |
247 |
|
248 |
static void test_bmdma_simple_rw(void) |
249 |
{ |
250 |
uint8_t status; |
251 |
uint8_t *buf; |
252 |
uint8_t *cmpbuf; |
253 |
size_t len = 512;
|
254 |
uintptr_t guest_buf = guest_alloc(guest_malloc, len); |
255 |
|
256 |
PrdtEntry prdt[] = { |
257 |
{ |
258 |
.addr = cpu_to_le32(guest_buf), |
259 |
.size = cpu_to_le32(len | PRDT_EOT), |
260 |
}, |
261 |
}; |
262 |
|
263 |
buf = g_malloc(len); |
264 |
cmpbuf = g_malloc(len); |
265 |
|
266 |
/* Write 0x55 pattern to sector 0 */
|
267 |
memset(buf, 0x55, len);
|
268 |
memwrite(guest_buf, buf, len); |
269 |
|
270 |
status = send_dma_request(CMD_WRITE_DMA, 0, 1, prdt, ARRAY_SIZE(prdt)); |
271 |
g_assert_cmphex(status, ==, BM_STS_INTR); |
272 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
273 |
|
274 |
/* Write 0xaa pattern to sector 1 */
|
275 |
memset(buf, 0xaa, len);
|
276 |
memwrite(guest_buf, buf, len); |
277 |
|
278 |
status = send_dma_request(CMD_WRITE_DMA, 1, 1, prdt, ARRAY_SIZE(prdt)); |
279 |
g_assert_cmphex(status, ==, BM_STS_INTR); |
280 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
281 |
|
282 |
/* Read and verify 0x55 pattern in sector 0 */
|
283 |
memset(cmpbuf, 0x55, len);
|
284 |
|
285 |
status = send_dma_request(CMD_READ_DMA, 0, 1, prdt, ARRAY_SIZE(prdt)); |
286 |
g_assert_cmphex(status, ==, BM_STS_INTR); |
287 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
288 |
|
289 |
memread(guest_buf, buf, len); |
290 |
g_assert(memcmp(buf, cmpbuf, len) == 0);
|
291 |
|
292 |
/* Read and verify 0xaa pattern in sector 1 */
|
293 |
memset(cmpbuf, 0xaa, len);
|
294 |
|
295 |
status = send_dma_request(CMD_READ_DMA, 1, 1, prdt, ARRAY_SIZE(prdt)); |
296 |
g_assert_cmphex(status, ==, BM_STS_INTR); |
297 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
298 |
|
299 |
memread(guest_buf, buf, len); |
300 |
g_assert(memcmp(buf, cmpbuf, len) == 0);
|
301 |
|
302 |
|
303 |
g_free(buf); |
304 |
g_free(cmpbuf); |
305 |
} |
306 |
|
307 |
static void test_bmdma_short_prdt(void) |
308 |
{ |
309 |
uint8_t status; |
310 |
|
311 |
PrdtEntry prdt[] = { |
312 |
{ |
313 |
.addr = 0,
|
314 |
.size = cpu_to_le32(0x10 | PRDT_EOT),
|
315 |
}, |
316 |
}; |
317 |
|
318 |
/* Normal request */
|
319 |
status = send_dma_request(CMD_READ_DMA, 0, 1, |
320 |
prdt, ARRAY_SIZE(prdt)); |
321 |
g_assert_cmphex(status, ==, 0);
|
322 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
323 |
|
324 |
/* Abort the request before it completes */
|
325 |
status = send_dma_request(CMD_READ_DMA | CMDF_ABORT, 0, 1, |
326 |
prdt, ARRAY_SIZE(prdt)); |
327 |
g_assert_cmphex(status, ==, 0);
|
328 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
329 |
} |
330 |
|
331 |
static void test_bmdma_long_prdt(void) |
332 |
{ |
333 |
uint8_t status; |
334 |
|
335 |
PrdtEntry prdt[] = { |
336 |
{ |
337 |
.addr = 0,
|
338 |
.size = cpu_to_le32(0x1000 | PRDT_EOT),
|
339 |
}, |
340 |
}; |
341 |
|
342 |
/* Normal request */
|
343 |
status = send_dma_request(CMD_READ_DMA, 0, 1, |
344 |
prdt, ARRAY_SIZE(prdt)); |
345 |
g_assert_cmphex(status, ==, BM_STS_ACTIVE | BM_STS_INTR); |
346 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
347 |
|
348 |
/* Abort the request before it completes */
|
349 |
status = send_dma_request(CMD_READ_DMA | CMDF_ABORT, 0, 1, |
350 |
prdt, ARRAY_SIZE(prdt)); |
351 |
g_assert_cmphex(status, ==, BM_STS_INTR); |
352 |
assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); |
353 |
} |
354 |
|
355 |
static void test_bmdma_setup(void) |
356 |
{ |
357 |
ide_test_start( |
358 |
"-vnc none "
|
359 |
"-drive file=%s,if=ide,serial=%s,cache=writeback "
|
360 |
"-global ide-hd.ver=%s",
|
361 |
tmp_path, "testdisk", "version"); |
362 |
} |
363 |
|
364 |
static void test_bmdma_teardown(void) |
365 |
{ |
366 |
ide_test_quit(); |
367 |
} |
368 |
|
369 |
static void string_cpu_to_be16(uint16_t *s, size_t bytes) |
370 |
{ |
371 |
g_assert((bytes & 1) == 0); |
372 |
bytes /= 2;
|
373 |
|
374 |
while (bytes--) {
|
375 |
*s = cpu_to_be16(*s); |
376 |
s++; |
377 |
} |
378 |
} |
379 |
|
380 |
static void test_identify(void) |
381 |
{ |
382 |
uint8_t data; |
383 |
uint16_t buf[256];
|
384 |
int i;
|
385 |
int ret;
|
386 |
|
387 |
ide_test_start( |
388 |
"-vnc none "
|
389 |
"-drive file=%s,if=ide,serial=%s,cache=writeback "
|
390 |
"-global ide-hd.ver=%s",
|
391 |
tmp_path, "testdisk", "version"); |
392 |
|
393 |
/* IDENTIFY command on device 0*/
|
394 |
outb(IDE_BASE + reg_device, 0);
|
395 |
outb(IDE_BASE + reg_command, CMD_IDENTIFY); |
396 |
|
397 |
/* Read in the IDENTIFY buffer and check registers */
|
398 |
data = inb(IDE_BASE + reg_device); |
399 |
g_assert_cmpint(data & DEV, ==, 0);
|
400 |
|
401 |
for (i = 0; i < 256; i++) { |
402 |
data = inb(IDE_BASE + reg_status); |
403 |
assert_bit_set(data, DRDY | DRQ); |
404 |
assert_bit_clear(data, BSY | DF | ERR); |
405 |
|
406 |
((uint16_t*) buf)[i] = inw(IDE_BASE + reg_data); |
407 |
} |
408 |
|
409 |
data = inb(IDE_BASE + reg_status); |
410 |
assert_bit_set(data, DRDY); |
411 |
assert_bit_clear(data, BSY | DF | ERR | DRQ); |
412 |
|
413 |
/* Check serial number/version in the buffer */
|
414 |
string_cpu_to_be16(&buf[10], 20); |
415 |
ret = memcmp(&buf[10], "testdisk ", 20); |
416 |
g_assert(ret == 0);
|
417 |
|
418 |
string_cpu_to_be16(&buf[23], 8); |
419 |
ret = memcmp(&buf[23], "version ", 8); |
420 |
g_assert(ret == 0);
|
421 |
|
422 |
/* Write cache enabled bit */
|
423 |
assert_bit_set(buf[85], 0x20); |
424 |
|
425 |
ide_test_quit(); |
426 |
} |
427 |
|
428 |
static void test_flush(void) |
429 |
{ |
430 |
uint8_t data; |
431 |
|
432 |
ide_test_start( |
433 |
"-vnc none "
|
434 |
"-drive file=blkdebug::%s,if=ide,cache=writeback",
|
435 |
tmp_path); |
436 |
|
437 |
/* Delay the completion of the flush request until we explicitly do it */
|
438 |
qmp("{'execute':'human-monitor-command', 'arguments': { "
|
439 |
"'command-line': 'qemu-io ide0-hd0 \"break flush_to_os A\"'} }");
|
440 |
|
441 |
/* FLUSH CACHE command on device 0*/
|
442 |
outb(IDE_BASE + reg_device, 0);
|
443 |
outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE); |
444 |
|
445 |
/* Check status while request is in flight*/
|
446 |
data = inb(IDE_BASE + reg_status); |
447 |
assert_bit_set(data, BSY | DRDY); |
448 |
assert_bit_clear(data, DF | ERR | DRQ); |
449 |
|
450 |
/* Complete the command */
|
451 |
qmp("{'execute':'human-monitor-command', 'arguments': { "
|
452 |
"'command-line': 'qemu-io ide0-hd0 \"resume A\"'} }");
|
453 |
|
454 |
/* Check registers */
|
455 |
data = inb(IDE_BASE + reg_device); |
456 |
g_assert_cmpint(data & DEV, ==, 0);
|
457 |
|
458 |
do {
|
459 |
data = inb(IDE_BASE + reg_status); |
460 |
} while (data & BSY);
|
461 |
|
462 |
assert_bit_set(data, DRDY); |
463 |
assert_bit_clear(data, BSY | DF | ERR | DRQ); |
464 |
|
465 |
ide_test_quit(); |
466 |
} |
467 |
|
468 |
int main(int argc, char **argv) |
469 |
{ |
470 |
const char *arch = qtest_get_arch(); |
471 |
int fd;
|
472 |
int ret;
|
473 |
|
474 |
/* Check architecture */
|
475 |
if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) { |
476 |
g_test_message("Skipping test for non-x86\n");
|
477 |
return 0; |
478 |
} |
479 |
|
480 |
/* Create a temporary raw image */
|
481 |
fd = mkstemp(tmp_path); |
482 |
g_assert(fd >= 0);
|
483 |
ret = ftruncate(fd, TEST_IMAGE_SIZE); |
484 |
g_assert(ret == 0);
|
485 |
close(fd); |
486 |
|
487 |
/* Run the tests */
|
488 |
g_test_init(&argc, &argv, NULL);
|
489 |
|
490 |
qtest_add_func("/ide/identify", test_identify);
|
491 |
|
492 |
qtest_add_func("/ide/bmdma/setup", test_bmdma_setup);
|
493 |
qtest_add_func("/ide/bmdma/simple_rw", test_bmdma_simple_rw);
|
494 |
qtest_add_func("/ide/bmdma/short_prdt", test_bmdma_short_prdt);
|
495 |
qtest_add_func("/ide/bmdma/long_prdt", test_bmdma_long_prdt);
|
496 |
qtest_add_func("/ide/bmdma/teardown", test_bmdma_teardown);
|
497 |
|
498 |
qtest_add_func("/ide/flush", test_flush);
|
499 |
|
500 |
ret = g_test_run(); |
501 |
|
502 |
/* Cleanup */
|
503 |
unlink(tmp_path); |
504 |
|
505 |
return ret;
|
506 |
} |