From: John Snow <jsnow@redhat.com>
To: Markus Armbruster <armbru@redhat.com>
Cc: kwolf@redhat.com, qemu-block@nongnu.org, zuban32s@gmail.com,
qemu-devel@nongnu.org, stefanha@redhat.com, pbonzini@redhat.com
Subject: Re: [Qemu-devel] [PATCH 3/4] ide-test: add cdrom pio test
Date: Thu, 10 Sep 2015 17:22:31 -0400 [thread overview]
Message-ID: <55F1F497.300@redhat.com> (raw)
In-Reply-To: <87d1xq7gq1.fsf@blackfin.pond.sub.org>
On 09/10/2015 05:42 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
>
>> Add a simple read test for ATAPI devices,
>> using the PIO mechanism.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> tests/ide-test.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 144 insertions(+)
>>
>> diff --git a/tests/ide-test.c b/tests/ide-test.c
>> index 4a07e3a..90524e3 100644
>> --- a/tests/ide-test.c
>> +++ b/tests/ide-test.c
>> @@ -45,6 +45,12 @@
>> #define IDE_BASE 0x1f0
>> #define IDE_PRIMARY_IRQ 14
>>
>> +#define ATAPI_BLOCK_SIZE 2048
>> +
>> +/* How many bytes to receive via ATAPI PIO at one time.
>> + * Must be less than 0xFFFF. */
>> +#define BYTE_COUNT_LIMIT 5120
>> +
>> enum {
>> reg_data = 0x0,
>> reg_nsectors = 0x2,
>> @@ -80,6 +86,7 @@ enum {
>> CMD_WRITE_DMA = 0xca,
>> CMD_FLUSH_CACHE = 0xe7,
>> CMD_IDENTIFY = 0xec,
>> + CMD_PACKET = 0xa0,
>>
>> CMDF_ABORT = 0x100,
>> CMDF_NO_BM = 0x200,
>> @@ -585,6 +592,140 @@ static void test_isa_retry_flush(const char *machine)
>> test_retry_flush("isapc");
>> }
>>
>> +typedef struct Read10CDB {
>> + uint8_t opcode;
>> + uint8_t flags;
>> + uint32_t lba;
>> + uint8_t reserved;
>> + uint16_t nblocks;
>> + uint8_t control;
>> + uint16_t padding;
>> +} __attribute__((__packed__)) Read10CDB;
>> +
>> +static void send_scsi_cdb_read10(uint32_t lba, uint16_t nblocks)
>> +{
>> + Read10CDB pkt = { .padding = 0 };
>> + int i;
>> +
>> + /* Construct SCSI CDB packet */
>> + pkt.opcode = 0x28;
>> + pkt.lba = cpu_to_be32(lba);
>> + pkt.nblocks = cpu_to_be16(nblocks);
>> +
>> + /* Send Packet */
>> + for (i = 0; i < sizeof(Read10CDB)/2; i++) {
>> + outw(IDE_BASE + reg_data, ((uint16_t *)&pkt)[i]);
>
> Requires pkt to be suitable aligned. It is.
>
>> + }
>> +}
>> +
>> +static void nsleep(int64_t nsecs)
>> +{
>> + const struct timespec val = { .tv_nsec = nsecs };
>> + nanosleep(&val, NULL);
>> + clock_set(nsecs);
>> +}
>> +
>> +static uint8_t ide_wait_clear(uint8_t flag)
>> +{
>> + int i;
>> + uint8_t data;
>> +
>> + /* Wait with a 5 second timeout */
>> + for (i = 0; i <= 12500000; i++) {
>> + data = inb(IDE_BASE + reg_status);
>> + if (!(data & flag)) {
>> + return data;
>> + }
>> + nsleep(400);
>> + }
>> + g_assert_not_reached();
>> + return 0xff;
>
> Unreachable code, not needed as long as g_assert_not_reached() is
> properly annotated noreturn.
>
Good point, bad habit.
>> +}
>> +
>> +static void cdrom_pio_impl(int nblocks)
>> +{
>> + FILE *fh;
>> + size_t patt_len = ATAPI_BLOCK_SIZE * MAX(16, nblocks);
>> + char *pattern = g_malloc(patt_len);
>> + size_t rxsize = ATAPI_BLOCK_SIZE * nblocks;
>> + char *rx = g_malloc0(rxsize);
>> + int i, j;
>> + uint8_t data;
>> + uint16_t limit;
>> +
>> + /* Prepopulate the CDROM with an interesting pattern */
>> + generate_pattern(pattern, patt_len, ATAPI_BLOCK_SIZE);
>> + fh = fopen(tmp_path, "w+");
>> + fwrite(pattern, ATAPI_BLOCK_SIZE, MAX(16, nblocks), fh);
>
> I guess I would've avoided repeating MAX(16, nblocks), but it'll do.
>
>> + fclose(fh);
>> +
>> + ide_test_start(
>> + "-drive file=%s,if=ide,media=cdrom,cache=writeback,format=raw", tmp_path);
>
> Legacy syntax. Okay.
>
"I expected better from you, John."
>> + qtest_irq_intercept_in(global_qtest, "ioapic");
>> +
>> + /* PACKET command on device 0 */
>> + outb(IDE_BASE + reg_device, 0);
>> + outb(IDE_BASE + reg_lba_middle, BYTE_COUNT_LIMIT & 0xFF);
>> + outb(IDE_BASE + reg_lba_high, (BYTE_COUNT_LIMIT >> 8 & 0xFF));
>> + outb(IDE_BASE + reg_command, CMD_PACKET);
>
> Ignorant question: why no reg_lba_low?
>
It's not used by the PACKET command. Most fields aren't, but
lba_middle/lba_high (lcyl and hcyl) are re-purposed to represent a 16
bit "byte count limit" field.
>> + /* HPD0: Check_Status_A State */
>> + nsleep(400);
>> + data = ide_wait_clear(BSY);
>
> Ignorant question: why do you need to wait 400ns before you wait?
>
Blindly following spec to a fault -- the purpose on real hardware is to
allow the drive a chance to set the BSY flag before we witness it being
reset back to zero.
QEMU of course will set and clear BSY synchronously before we ever make
it back, but I try to write these tests in a manner where they are
ignorant of QEMU's internals as much as I can, so you see some weird
timing stuff here and there.
Secretly I want to leave these tests generic enough to try and test them
with pass-through devices and real hardware someday to see how my
assertions match up against decidedly real hardware.
>> + /* HPD1: Send_Packet State */
>> + assert_bit_set(data, DRQ | DRDY);
>> + assert_bit_clear(data, ERR | DF | BSY);
>> +
>> + /* SCSI CDB (READ10) -- read n*2048 bytes from block 0 */
>> + send_scsi_cdb_read10(0, nblocks);
>> +
>> + /* HPD3: INTRQ_Wait */
>> + i = 0;
>> + do {
>> + data = get_irq(IDE_PRIMARY_IRQ);
>> + nsleep(400);
>> + i++;
>> + g_assert_cmpint(i, <=, 12500000);
>> + } while (!data);
>
> Similar to ide_wait_clear(). Why do you need to nsleep() after
> get_irq() returned non-zero?
>
Ran out of thinking fluid. Ugly loop.
>> +
>> + /* HPD2: Check_Status_B */
>> + data = ide_wait_clear(BSY);
>> + assert_bit_set(data, DRQ | DRDY);
>> + assert_bit_clear(data, ERR | DF | BSY);
>> +
>> + /* Read data back: occurs in bursts of 'BYTE_COUNT_LIMIT' bytes.
>> + * If BYTE_COUNT_LIMIT is odd, we transfer BYTE_COUNT_LIMIT - 1 bytes.
>> + * We allow an odd limit only when the remaining transfer size is
>> + * less than BYTE_COUNT_LIMIT.
>> + * For our purposes, we can only request even multiples, so do not
>> + * attempt to read remainders. */
>> + limit = BYTE_COUNT_LIMIT & ~1;
>
> Does nothing, BYTE_COUNT_LIMIT is 5120. Build-time assertion
> !(BYTE_COUNT_LIMIT & 1) would do.
>
Allowing for the possibility of tests to exercise this bizarre property
of the BCL in the future, though I think there'd be more work to do in
the loop below.
I intend to expand these tests bit-by-bit over time, so this is just
some evidence of where I'm thinking on that.
>> + for (i = 0; i < DIV_ROUND_UP(rxsize, limit); i++) {
>> + size_t r = (rxsize - (limit * i)) / 2;
>> + for (j = 0; j < MIN((limit / 2), r); j++) {
>> + ((uint16_t *)rx)[(i * (limit/2)) + j] = inw(IDE_BASE + reg_data);
>
> Would be more readable with uint16_t *rx. Only if it doesn't require
> casts elsewhere.
>
> I guess I would've tried a single loop instead of nesting two. But
> since this works, keep it.
>
It's to accommodate the DRQ blocks, and to allow myself the chance to
query the device between the distinct DRQ segments.
It could just be a flat loop and it'd work okay...
>> + }
>> + }
>> + data = ide_wait_clear(DRQ);
>> + assert_bit_set(data, DRDY);
>> + assert_bit_clear(data, DRQ | ERR | DF | BSY);
>> +
>> + g_assert_cmpint(memcmp(pattern, rx, rxsize), ==, 0);
>> + g_free(pattern);
>> + g_free(rx);
>> + test_bmdma_teardown();
>> +}
>> +
>> +static void test_cdrom_pio(void)
>> +{
>> + cdrom_pio_impl(1);
>> +}
>> +
>> +static void test_cdrom_pio_large(void)
>> +{
>> + /* Test a few loops of the PIO DRQ mechanism. */
>> + cdrom_pio_impl(BYTE_COUNT_LIMIT * 4 / ATAPI_BLOCK_SIZE);
>> +}
>> +
>> int main(int argc, char **argv)
>> {
>> const char *arch = qtest_get_arch();
>> @@ -628,6 +769,9 @@ int main(int argc, char **argv)
>> qtest_add_func("/ide/flush/retry_pci", test_pci_retry_flush);
>> qtest_add_func("/ide/flush/retry_isa", test_isa_retry_flush);
>>
>> + qtest_add_func("/ide/cdrom/pio", test_cdrom_pio);
>> + qtest_add_func("/ide/cdrom/pio_large", test_cdrom_pio_large);
>> +
>> ret = g_test_run();
>>
>> /* Cleanup */
I'll polish just a pinch more. Thanks for the sanity check.
--js
next prev parent reply other threads:[~2015-09-10 21:22 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-08-26 21:13 [Qemu-devel] [PATCH 0/4] ide: simple ATAPI tests John Snow
2015-08-26 21:13 ` [Qemu-devel] [PATCH 1/4] qtest/ahci: use generate_pattern everywhere John Snow
2015-08-26 21:13 ` [Qemu-devel] [PATCH 2/4] qtest/ahci: export generate_pattern John Snow
2015-09-10 9:18 ` Markus Armbruster
2015-09-10 21:40 ` John Snow
2015-08-26 21:13 ` [Qemu-devel] [PATCH 3/4] ide-test: add cdrom pio test John Snow
2015-09-10 9:42 ` Markus Armbruster
2015-09-10 21:22 ` John Snow [this message]
2015-08-26 21:13 ` [Qemu-devel] [PATCH 4/4] ide-test: add cdrom dma test John Snow
2015-09-09 16:37 ` [Qemu-devel] [PATCH 0/4] ide: simple ATAPI tests John Snow
2015-09-09 17:00 ` Paolo Bonzini
2015-09-09 17:04 ` John Snow
2015-09-10 9:59 ` Markus Armbruster
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=55F1F497.300@redhat.com \
--to=jsnow@redhat.com \
--cc=armbru@redhat.com \
--cc=kwolf@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
--cc=zuban32s@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).