xref: /openbmc/qemu/tests/qtest/fdc-test.c (revision 6804d230)
1 /*
2  * Floppy test cases.
3  *
4  * Copyright (c) 2012 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 "qemu/osdep.h"
26 
27 
28 #include "libqtest-single.h"
29 #include "qapi/qmp/qdict.h"
30 #include "qemu-common.h"
31 
32 /* TODO actually test the results and get rid of this */
33 #define qmp_discard_response(...) qobject_unref(qmp(__VA_ARGS__))
34 
35 #define TEST_IMAGE_SIZE 1440 * 1024
36 
37 #define FLOPPY_BASE 0x3f0
38 #define FLOPPY_IRQ 6
39 
40 enum {
41     reg_sra         = 0x0,
42     reg_srb         = 0x1,
43     reg_dor         = 0x2,
44     reg_msr         = 0x4,
45     reg_dsr         = 0x4,
46     reg_fifo        = 0x5,
47     reg_dir         = 0x7,
48 };
49 
50 enum {
51     CMD_SENSE_INT           = 0x08,
52     CMD_READ_ID             = 0x0a,
53     CMD_SEEK                = 0x0f,
54     CMD_VERIFY              = 0x16,
55     CMD_READ                = 0xe6,
56     CMD_RELATIVE_SEEK_OUT   = 0x8f,
57     CMD_RELATIVE_SEEK_IN    = 0xcf,
58 };
59 
60 enum {
61     BUSY    = 0x10,
62     NONDMA  = 0x20,
63     RQM     = 0x80,
64     DIO     = 0x40,
65 
66     DSKCHG  = 0x80,
67 };
68 
69 static char test_image[] = "/tmp/qtest.XXXXXX";
70 
71 #define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
72 #define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
73 
74 static uint8_t base = 0x70;
75 
76 enum {
77     CMOS_FLOPPY     = 0x10,
78 };
79 
80 static void floppy_send(uint8_t byte)
81 {
82     uint8_t msr;
83 
84     msr = inb(FLOPPY_BASE + reg_msr);
85     assert_bit_set(msr, RQM);
86     assert_bit_clear(msr, DIO);
87 
88     outb(FLOPPY_BASE + reg_fifo, byte);
89 }
90 
91 static uint8_t floppy_recv(void)
92 {
93     uint8_t msr;
94 
95     msr = inb(FLOPPY_BASE + reg_msr);
96     assert_bit_set(msr, RQM | DIO);
97 
98     return inb(FLOPPY_BASE + reg_fifo);
99 }
100 
101 /* pcn: Present Cylinder Number */
102 static void ack_irq(uint8_t *pcn)
103 {
104     uint8_t ret;
105 
106     g_assert(get_irq(FLOPPY_IRQ));
107     floppy_send(CMD_SENSE_INT);
108     floppy_recv();
109 
110     ret = floppy_recv();
111     if (pcn != NULL) {
112         *pcn = ret;
113     }
114 
115     g_assert(!get_irq(FLOPPY_IRQ));
116 }
117 
118 static uint8_t send_read_command(uint8_t cmd)
119 {
120     uint8_t drive = 0;
121     uint8_t head = 0;
122     uint8_t cyl = 0;
123     uint8_t sect_addr = 1;
124     uint8_t sect_size = 2;
125     uint8_t eot = 1;
126     uint8_t gap = 0x1b;
127     uint8_t gpl = 0xff;
128 
129     uint8_t msr = 0;
130     uint8_t st0;
131 
132     uint8_t ret = 0;
133 
134     floppy_send(cmd);
135     floppy_send(head << 2 | drive);
136     g_assert(!get_irq(FLOPPY_IRQ));
137     floppy_send(cyl);
138     floppy_send(head);
139     floppy_send(sect_addr);
140     floppy_send(sect_size);
141     floppy_send(eot);
142     floppy_send(gap);
143     floppy_send(gpl);
144 
145     uint8_t i = 0;
146     uint8_t n = 2;
147     for (; i < n; i++) {
148         msr = inb(FLOPPY_BASE + reg_msr);
149         if (msr == 0xd0) {
150             break;
151         }
152         sleep(1);
153     }
154 
155     if (i >= n) {
156         return 1;
157     }
158 
159     st0 = floppy_recv();
160     if (st0 != 0x40) {
161         ret = 1;
162     }
163 
164     floppy_recv();
165     floppy_recv();
166     floppy_recv();
167     floppy_recv();
168     floppy_recv();
169     floppy_recv();
170 
171     return ret;
172 }
173 
174 static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0)
175 {
176     uint8_t drive = 0;
177     uint8_t head = 0;
178     uint8_t cyl = 0;
179     uint8_t sect_addr = 1;
180     uint8_t sect_size = 2;
181     uint8_t eot = nb_sect;
182     uint8_t gap = 0x1b;
183     uint8_t gpl = 0xff;
184 
185     uint8_t msr = 0;
186     uint8_t st0;
187 
188     uint8_t ret = 0;
189 
190     floppy_send(CMD_READ);
191     floppy_send(head << 2 | drive);
192     g_assert(!get_irq(FLOPPY_IRQ));
193     floppy_send(cyl);
194     floppy_send(head);
195     floppy_send(sect_addr);
196     floppy_send(sect_size);
197     floppy_send(eot);
198     floppy_send(gap);
199     floppy_send(gpl);
200 
201     uint16_t i = 0;
202     uint8_t n = 2;
203     for (; i < n; i++) {
204         msr = inb(FLOPPY_BASE + reg_msr);
205         if (msr == (BUSY | NONDMA | DIO | RQM)) {
206             break;
207         }
208         sleep(1);
209     }
210 
211     if (i >= n) {
212         return 1;
213     }
214 
215     /* Non-DMA mode */
216     for (i = 0; i < 512 * 2 * nb_sect; i++) {
217         msr = inb(FLOPPY_BASE + reg_msr);
218         assert_bit_set(msr, BUSY | RQM | DIO);
219         inb(FLOPPY_BASE + reg_fifo);
220     }
221 
222     msr = inb(FLOPPY_BASE + reg_msr);
223     assert_bit_set(msr, BUSY | RQM | DIO);
224     g_assert(get_irq(FLOPPY_IRQ));
225 
226     st0 = floppy_recv();
227     if (st0 != expected_st0) {
228         ret = 1;
229     }
230 
231     floppy_recv();
232     floppy_recv();
233     floppy_recv();
234     floppy_recv();
235     floppy_recv();
236     g_assert(get_irq(FLOPPY_IRQ));
237     floppy_recv();
238 
239     /* Check that we're back in command phase */
240     msr = inb(FLOPPY_BASE + reg_msr);
241     assert_bit_clear(msr, BUSY | DIO);
242     assert_bit_set(msr, RQM);
243     g_assert(!get_irq(FLOPPY_IRQ));
244 
245     return ret;
246 }
247 
248 static void send_seek(int cyl)
249 {
250     int drive = 0;
251     int head = 0;
252 
253     floppy_send(CMD_SEEK);
254     floppy_send(head << 2 | drive);
255     g_assert(!get_irq(FLOPPY_IRQ));
256     floppy_send(cyl);
257     ack_irq(NULL);
258 }
259 
260 static uint8_t cmos_read(uint8_t reg)
261 {
262     outb(base + 0, reg);
263     return inb(base + 1);
264 }
265 
266 static void test_cmos(void)
267 {
268     uint8_t cmos;
269 
270     cmos = cmos_read(CMOS_FLOPPY);
271     g_assert(cmos == 0x40 || cmos == 0x50);
272 }
273 
274 static void test_no_media_on_start(void)
275 {
276     uint8_t dir;
277 
278     /* Media changed bit must be set all time after start if there is
279      * no media in drive. */
280     dir = inb(FLOPPY_BASE + reg_dir);
281     assert_bit_set(dir, DSKCHG);
282     dir = inb(FLOPPY_BASE + reg_dir);
283     assert_bit_set(dir, DSKCHG);
284     send_seek(1);
285     dir = inb(FLOPPY_BASE + reg_dir);
286     assert_bit_set(dir, DSKCHG);
287     dir = inb(FLOPPY_BASE + reg_dir);
288     assert_bit_set(dir, DSKCHG);
289 }
290 
291 static void test_read_without_media(void)
292 {
293     uint8_t ret;
294 
295     ret = send_read_command(CMD_READ);
296     g_assert(ret == 0);
297 }
298 
299 static void test_media_insert(void)
300 {
301     uint8_t dir;
302 
303     /* Insert media in drive. DSKCHK should not be reset until a step pulse
304      * is sent. */
305     qmp_discard_response("{'execute':'blockdev-change-medium', 'arguments':{"
306                          " 'id':'floppy0', 'filename': %s, 'format': 'raw' }}",
307                          test_image);
308 
309     dir = inb(FLOPPY_BASE + reg_dir);
310     assert_bit_set(dir, DSKCHG);
311     dir = inb(FLOPPY_BASE + reg_dir);
312     assert_bit_set(dir, DSKCHG);
313 
314     send_seek(0);
315     dir = inb(FLOPPY_BASE + reg_dir);
316     assert_bit_set(dir, DSKCHG);
317     dir = inb(FLOPPY_BASE + reg_dir);
318     assert_bit_set(dir, DSKCHG);
319 
320     /* Step to next track should clear DSKCHG bit. */
321     send_seek(1);
322     dir = inb(FLOPPY_BASE + reg_dir);
323     assert_bit_clear(dir, DSKCHG);
324     dir = inb(FLOPPY_BASE + reg_dir);
325     assert_bit_clear(dir, DSKCHG);
326 }
327 
328 static void test_media_change(void)
329 {
330     uint8_t dir;
331 
332     test_media_insert();
333 
334     /* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
335      * reset the bit. */
336     qmp_discard_response("{'execute':'eject', 'arguments':{"
337                          " 'id':'floppy0' }}");
338 
339     dir = inb(FLOPPY_BASE + reg_dir);
340     assert_bit_set(dir, DSKCHG);
341     dir = inb(FLOPPY_BASE + reg_dir);
342     assert_bit_set(dir, DSKCHG);
343 
344     send_seek(0);
345     dir = inb(FLOPPY_BASE + reg_dir);
346     assert_bit_set(dir, DSKCHG);
347     dir = inb(FLOPPY_BASE + reg_dir);
348     assert_bit_set(dir, DSKCHG);
349 
350     send_seek(1);
351     dir = inb(FLOPPY_BASE + reg_dir);
352     assert_bit_set(dir, DSKCHG);
353     dir = inb(FLOPPY_BASE + reg_dir);
354     assert_bit_set(dir, DSKCHG);
355 }
356 
357 static void test_sense_interrupt(void)
358 {
359     int drive = 0;
360     int head = 0;
361     int cyl = 0;
362     int ret = 0;
363 
364     floppy_send(CMD_SENSE_INT);
365     ret = floppy_recv();
366     g_assert(ret == 0x80);
367 
368     floppy_send(CMD_SEEK);
369     floppy_send(head << 2 | drive);
370     g_assert(!get_irq(FLOPPY_IRQ));
371     floppy_send(cyl);
372 
373     floppy_send(CMD_SENSE_INT);
374     ret = floppy_recv();
375     g_assert(ret == 0x20);
376     floppy_recv();
377 }
378 
379 static void test_relative_seek(void)
380 {
381     uint8_t drive = 0;
382     uint8_t head = 0;
383     uint8_t cyl = 1;
384     uint8_t pcn;
385 
386     /* Send seek to track 0 */
387     send_seek(0);
388 
389     /* Send relative seek to increase track by 1 */
390     floppy_send(CMD_RELATIVE_SEEK_IN);
391     floppy_send(head << 2 | drive);
392     g_assert(!get_irq(FLOPPY_IRQ));
393     floppy_send(cyl);
394 
395     ack_irq(&pcn);
396     g_assert(pcn == 1);
397 
398     /* Send relative seek to decrease track by 1 */
399     floppy_send(CMD_RELATIVE_SEEK_OUT);
400     floppy_send(head << 2 | drive);
401     g_assert(!get_irq(FLOPPY_IRQ));
402     floppy_send(cyl);
403 
404     ack_irq(&pcn);
405     g_assert(pcn == 0);
406 }
407 
408 static void test_read_id(void)
409 {
410     uint8_t drive = 0;
411     uint8_t head = 0;
412     uint8_t cyl;
413     uint8_t st0;
414     uint8_t msr;
415 
416     /* Seek to track 0 and check with READ ID */
417     send_seek(0);
418 
419     floppy_send(CMD_READ_ID);
420     g_assert(!get_irq(FLOPPY_IRQ));
421     floppy_send(head << 2 | drive);
422 
423     msr = inb(FLOPPY_BASE + reg_msr);
424     if (!get_irq(FLOPPY_IRQ)) {
425         assert_bit_set(msr, BUSY);
426         assert_bit_clear(msr, RQM);
427     }
428 
429     while (!get_irq(FLOPPY_IRQ)) {
430         /* qemu involves a timer with READ ID... */
431         clock_step(1000000000LL / 50);
432     }
433 
434     msr = inb(FLOPPY_BASE + reg_msr);
435     assert_bit_set(msr, BUSY | RQM | DIO);
436 
437     st0 = floppy_recv();
438     floppy_recv();
439     floppy_recv();
440     cyl = floppy_recv();
441     head = floppy_recv();
442     floppy_recv();
443     g_assert(get_irq(FLOPPY_IRQ));
444     floppy_recv();
445     g_assert(!get_irq(FLOPPY_IRQ));
446 
447     g_assert_cmpint(cyl, ==, 0);
448     g_assert_cmpint(head, ==, 0);
449     g_assert_cmpint(st0, ==, head << 2);
450 
451     /* Seek to track 8 on head 1 and check with READ ID */
452     head = 1;
453     cyl = 8;
454 
455     floppy_send(CMD_SEEK);
456     floppy_send(head << 2 | drive);
457     g_assert(!get_irq(FLOPPY_IRQ));
458     floppy_send(cyl);
459     g_assert(get_irq(FLOPPY_IRQ));
460     ack_irq(NULL);
461 
462     floppy_send(CMD_READ_ID);
463     g_assert(!get_irq(FLOPPY_IRQ));
464     floppy_send(head << 2 | drive);
465 
466     msr = inb(FLOPPY_BASE + reg_msr);
467     if (!get_irq(FLOPPY_IRQ)) {
468         assert_bit_set(msr, BUSY);
469         assert_bit_clear(msr, RQM);
470     }
471 
472     while (!get_irq(FLOPPY_IRQ)) {
473         /* qemu involves a timer with READ ID... */
474         clock_step(1000000000LL / 50);
475     }
476 
477     msr = inb(FLOPPY_BASE + reg_msr);
478     assert_bit_set(msr, BUSY | RQM | DIO);
479 
480     st0 = floppy_recv();
481     floppy_recv();
482     floppy_recv();
483     cyl = floppy_recv();
484     head = floppy_recv();
485     floppy_recv();
486     g_assert(get_irq(FLOPPY_IRQ));
487     floppy_recv();
488     g_assert(!get_irq(FLOPPY_IRQ));
489 
490     g_assert_cmpint(cyl, ==, 8);
491     g_assert_cmpint(head, ==, 1);
492     g_assert_cmpint(st0, ==, head << 2);
493 }
494 
495 static void test_read_no_dma_1(void)
496 {
497     uint8_t ret;
498 
499     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
500     send_seek(0);
501     ret = send_read_no_dma_command(1, 0x04);
502     g_assert(ret == 0);
503 }
504 
505 static void test_read_no_dma_18(void)
506 {
507     uint8_t ret;
508 
509     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
510     send_seek(0);
511     ret = send_read_no_dma_command(18, 0x04);
512     g_assert(ret == 0);
513 }
514 
515 static void test_read_no_dma_19(void)
516 {
517     uint8_t ret;
518 
519     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
520     send_seek(0);
521     ret = send_read_no_dma_command(19, 0x20);
522     g_assert(ret == 0);
523 }
524 
525 static void test_verify(void)
526 {
527     uint8_t ret;
528 
529     ret = send_read_command(CMD_VERIFY);
530     g_assert(ret == 0);
531 }
532 
533 /* success if no crash or abort */
534 static void fuzz_registers(void)
535 {
536     unsigned int i;
537 
538     for (i = 0; i < 1000; i++) {
539         uint8_t reg, val;
540 
541         reg = (uint8_t)g_test_rand_int_range(0, 8);
542         val = (uint8_t)g_test_rand_int_range(0, 256);
543 
544         outb(FLOPPY_BASE + reg, val);
545         inb(FLOPPY_BASE + reg);
546     }
547 }
548 
549 int main(int argc, char **argv)
550 {
551     int fd;
552     int ret;
553 
554     /* Create a temporary raw image */
555     fd = mkstemp(test_image);
556     g_assert(fd >= 0);
557     ret = ftruncate(fd, TEST_IMAGE_SIZE);
558     g_assert(ret == 0);
559     close(fd);
560 
561     /* Run the tests */
562     g_test_init(&argc, &argv, NULL);
563 
564     qtest_start("-device floppy,id=floppy0");
565     qtest_irq_intercept_in(global_qtest, "ioapic");
566     qtest_add_func("/fdc/cmos", test_cmos);
567     qtest_add_func("/fdc/no_media_on_start", test_no_media_on_start);
568     qtest_add_func("/fdc/read_without_media", test_read_without_media);
569     qtest_add_func("/fdc/media_change", test_media_change);
570     qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt);
571     qtest_add_func("/fdc/relative_seek", test_relative_seek);
572     qtest_add_func("/fdc/read_id", test_read_id);
573     qtest_add_func("/fdc/verify", test_verify);
574     qtest_add_func("/fdc/media_insert", test_media_insert);
575     qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1);
576     qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18);
577     qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19);
578     qtest_add_func("/fdc/fuzz-registers", fuzz_registers);
579 
580     ret = g_test_run();
581 
582     /* Cleanup */
583     qtest_end();
584     unlink(test_image);
585 
586     return ret;
587 }
588