| /* |
| * AHCI test cases |
| * |
| * Copyright (c) 2014 John Snow <jsnow@redhat.com> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| |
| #include <stdint.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <getopt.h> |
| #include <glib.h> |
| |
| #include "libqtest.h" |
| #include "libqos/pci-pc.h" |
| #include "libqos/malloc-pc.h" |
| |
| #include "qemu-common.h" |
| #include "qemu/host-utils.h" |
| |
| #include "hw/pci/pci_ids.h" |
| #include "hw/pci/pci_regs.h" |
| |
| /* Test-specific defines. */ |
| #define TEST_IMAGE_SIZE (64 * 1024 * 1024) |
| |
| /*** Supplementary PCI Config Space IDs & Masks ***/ |
| #define PCI_DEVICE_ID_INTEL_Q35_AHCI (0x2922) |
| #define PCI_MSI_FLAGS_RESERVED (0xFF00) |
| #define PCI_PM_CTRL_RESERVED (0xFC) |
| #define PCI_BCC(REG32) ((REG32) >> 24) |
| #define PCI_PI(REG32) (((REG32) >> 8) & 0xFF) |
| #define PCI_SCC(REG32) (((REG32) >> 16) & 0xFF) |
| |
| /*** Recognized AHCI Device Types ***/ |
| #define AHCI_INTEL_ICH9 (PCI_DEVICE_ID_INTEL_Q35_AHCI << 16 | \ |
| PCI_VENDOR_ID_INTEL) |
| |
| /*** Globals ***/ |
| static QGuestAllocator *guest_malloc; |
| static QPCIBus *pcibus; |
| static char tmp_path[] = "/tmp/qtest.XXXXXX"; |
| static bool ahci_pedantic; |
| static uint32_t ahci_fingerprint; |
| |
| /*** Macro Utilities ***/ |
| #define ASSERT_BIT_SET(data, mask) g_assert_cmphex((data) & (mask), ==, (mask)) |
| #define ASSERT_BIT_CLEAR(data, mask) g_assert_cmphex((data) & (mask), ==, 0) |
| |
| /*** Function Declarations ***/ |
| static QPCIDevice *get_ahci_device(void); |
| static QPCIDevice *start_ahci_device(QPCIDevice *dev, void **hba_base); |
| static void free_ahci_device(QPCIDevice *dev); |
| static void ahci_test_pci_spec(QPCIDevice *ahci); |
| static void ahci_test_pci_caps(QPCIDevice *ahci, uint16_t header, |
| uint8_t offset); |
| static void ahci_test_satacap(QPCIDevice *ahci, uint8_t offset); |
| static void ahci_test_msicap(QPCIDevice *ahci, uint8_t offset); |
| static void ahci_test_pmcap(QPCIDevice *ahci, uint8_t offset); |
| |
| /*** Utilities ***/ |
| |
| /** |
| * Locate, verify, and return a handle to the AHCI device. |
| */ |
| static QPCIDevice *get_ahci_device(void) |
| { |
| QPCIDevice *ahci; |
| |
| pcibus = qpci_init_pc(); |
| |
| /* Find the AHCI PCI device and verify it's the right one. */ |
| ahci = qpci_device_find(pcibus, QPCI_DEVFN(0x1F, 0x02)); |
| g_assert(ahci != NULL); |
| |
| ahci_fingerprint = qpci_config_readl(ahci, PCI_VENDOR_ID); |
| |
| switch (ahci_fingerprint) { |
| case AHCI_INTEL_ICH9: |
| break; |
| default: |
| /* Unknown device. */ |
| g_assert_not_reached(); |
| } |
| |
| return ahci; |
| } |
| |
| static void free_ahci_device(QPCIDevice *ahci) |
| { |
| /* libqos doesn't have a function for this, so free it manually */ |
| g_free(ahci); |
| |
| if (pcibus) { |
| qpci_free_pc(pcibus); |
| pcibus = NULL; |
| } |
| } |
| |
| /*** Test Setup & Teardown ***/ |
| |
| /** |
| * Launch QEMU with the given command line, |
| * and then set up interrupts and our guest malloc interface. |
| */ |
| static void qtest_boot(const char *cmdline_fmt, ...) |
| { |
| va_list ap; |
| char *cmdline; |
| |
| va_start(ap, cmdline_fmt); |
| cmdline = g_strdup_vprintf(cmdline_fmt, ap); |
| va_end(ap); |
| |
| qtest_start(cmdline); |
| qtest_irq_intercept_in(global_qtest, "ioapic"); |
| guest_malloc = pc_alloc_init(); |
| |
| g_free(cmdline); |
| } |
| |
| /** |
| * Tear down the QEMU instance. |
| */ |
| static void qtest_shutdown(void) |
| { |
| g_free(guest_malloc); |
| guest_malloc = NULL; |
| qtest_end(); |
| } |
| |
| /** |
| * Start a Q35 machine and bookmark a handle to the AHCI device. |
| */ |
| static QPCIDevice *ahci_boot(void) |
| { |
| qtest_boot("-drive if=none,id=drive0,file=%s,cache=writeback,serial=%s" |
| " -M q35 " |
| "-device ide-hd,drive=drive0 " |
| "-global ide-hd.ver=%s", |
| tmp_path, "testdisk", "version"); |
| |
| /* Verify that we have an AHCI device present. */ |
| return get_ahci_device(); |
| } |
| |
| /** |
| * Clean up the PCI device, then terminate the QEMU instance. |
| */ |
| static void ahci_shutdown(QPCIDevice *ahci) |
| { |
| free_ahci_device(ahci); |
| qtest_shutdown(); |
| } |
| |
| /*** Logical Device Initialization ***/ |
| |
| /** |
| * Start the PCI device and sanity-check default operation. |
| */ |
| static void ahci_pci_enable(QPCIDevice *ahci, void **hba_base) |
| { |
| uint8_t reg; |
| |
| start_ahci_device(ahci, hba_base); |
| |
| switch (ahci_fingerprint) { |
| case AHCI_INTEL_ICH9: |
| /* ICH9 has a register at PCI 0x92 that |
| * acts as a master port enabler mask. */ |
| reg = qpci_config_readb(ahci, 0x92); |
| reg |= 0x3F; |
| qpci_config_writeb(ahci, 0x92, reg); |
| ASSERT_BIT_SET(qpci_config_readb(ahci, 0x92), 0x3F); |
| break; |
| } |
| |
| } |
| |
| /** |
| * Map BAR5/ABAR, and engage the PCI device. |
| */ |
| static QPCIDevice *start_ahci_device(QPCIDevice *ahci, void **hba_base) |
| { |
| /* Map AHCI's ABAR (BAR5) */ |
| *hba_base = qpci_iomap(ahci, 5, NULL); |
| |
| /* turns on pci.cmd.iose, pci.cmd.mse and pci.cmd.bme */ |
| qpci_device_enable(ahci); |
| |
| return ahci; |
| } |
| |
| /*** Specification Adherence Tests ***/ |
| |
| /** |
| * Implementation for test_pci_spec. Ensures PCI configuration space is sane. |
| */ |
| static void ahci_test_pci_spec(QPCIDevice *ahci) |
| { |
| uint8_t datab; |
| uint16_t data; |
| uint32_t datal; |
| |
| /* Most of these bits should start cleared until we turn them on. */ |
| data = qpci_config_readw(ahci, PCI_COMMAND); |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_MEMORY); |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_MASTER); |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_SPECIAL); /* Reserved */ |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_VGA_PALETTE); /* Reserved */ |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_PARITY); |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_WAIT); /* Reserved */ |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_SERR); |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_FAST_BACK); |
| ASSERT_BIT_CLEAR(data, PCI_COMMAND_INTX_DISABLE); |
| ASSERT_BIT_CLEAR(data, 0xF800); /* Reserved */ |
| |
| data = qpci_config_readw(ahci, PCI_STATUS); |
| ASSERT_BIT_CLEAR(data, 0x01 | 0x02 | 0x04); /* Reserved */ |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_INTERRUPT); |
| ASSERT_BIT_SET(data, PCI_STATUS_CAP_LIST); /* must be set */ |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_UDF); /* Reserved */ |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_PARITY); |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_SIG_TARGET_ABORT); |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_REC_TARGET_ABORT); |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_REC_MASTER_ABORT); |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_SIG_SYSTEM_ERROR); |
| ASSERT_BIT_CLEAR(data, PCI_STATUS_DETECTED_PARITY); |
| |
| /* RID occupies the low byte, CCs occupy the high three. */ |
| datal = qpci_config_readl(ahci, PCI_CLASS_REVISION); |
| if (ahci_pedantic) { |
| /* AHCI 1.3 specifies that at-boot, the RID should reset to 0x00, |
| * Though in practice this is likely seldom true. */ |
| ASSERT_BIT_CLEAR(datal, 0xFF); |
| } |
| |
| /* BCC *must* equal 0x01. */ |
| g_assert_cmphex(PCI_BCC(datal), ==, 0x01); |
| if (PCI_SCC(datal) == 0x01) { |
| /* IDE */ |
| ASSERT_BIT_SET(0x80000000, datal); |
| ASSERT_BIT_CLEAR(0x60000000, datal); |
| } else if (PCI_SCC(datal) == 0x04) { |
| /* RAID */ |
| g_assert_cmphex(PCI_PI(datal), ==, 0); |
| } else if (PCI_SCC(datal) == 0x06) { |
| /* AHCI */ |
| g_assert_cmphex(PCI_PI(datal), ==, 0x01); |
| } else { |
| g_assert_not_reached(); |
| } |
| |
| datab = qpci_config_readb(ahci, PCI_CACHE_LINE_SIZE); |
| g_assert_cmphex(datab, ==, 0); |
| |
| datab = qpci_config_readb(ahci, PCI_LATENCY_TIMER); |
| g_assert_cmphex(datab, ==, 0); |
| |
| /* Only the bottom 7 bits must be off. */ |
| datab = qpci_config_readb(ahci, PCI_HEADER_TYPE); |
| ASSERT_BIT_CLEAR(datab, 0x7F); |
| |
| /* BIST is optional, but the low 7 bits must always start off regardless. */ |
| datab = qpci_config_readb(ahci, PCI_BIST); |
| ASSERT_BIT_CLEAR(datab, 0x7F); |
| |
| /* BARS 0-4 do not have a boot spec, but ABAR/BAR5 must be clean. */ |
| datal = qpci_config_readl(ahci, PCI_BASE_ADDRESS_5); |
| g_assert_cmphex(datal, ==, 0); |
| |
| qpci_config_writel(ahci, PCI_BASE_ADDRESS_5, 0xFFFFFFFF); |
| datal = qpci_config_readl(ahci, PCI_BASE_ADDRESS_5); |
| /* ABAR must be 32-bit, memory mapped, non-prefetchable and |
| * must be >= 512 bytes. To that end, bits 0-8 must be off. */ |
| ASSERT_BIT_CLEAR(datal, 0xFF); |
| |
| /* Capability list MUST be present, */ |
| datal = qpci_config_readl(ahci, PCI_CAPABILITY_LIST); |
| /* But these bits are reserved. */ |
| ASSERT_BIT_CLEAR(datal, ~0xFF); |
| g_assert_cmphex(datal, !=, 0); |
| |
| /* Check specification adherence for capability extenstions. */ |
| data = qpci_config_readw(ahci, datal); |
| |
| switch (ahci_fingerprint) { |
| case AHCI_INTEL_ICH9: |
| /* Intel ICH9 Family Datasheet 14.1.19 p.550 */ |
| g_assert_cmphex((data & 0xFF), ==, PCI_CAP_ID_MSI); |
| break; |
| default: |
| /* AHCI 1.3, Section 2.1.14 -- CAP must point to PMCAP. */ |
| g_assert_cmphex((data & 0xFF), ==, PCI_CAP_ID_PM); |
| } |
| |
| ahci_test_pci_caps(ahci, data, (uint8_t)datal); |
| |
| /* Reserved. */ |
| datal = qpci_config_readl(ahci, PCI_CAPABILITY_LIST + 4); |
| g_assert_cmphex(datal, ==, 0); |
| |
| /* IPIN might vary, but ILINE must be off. */ |
| datab = qpci_config_readb(ahci, PCI_INTERRUPT_LINE); |
| g_assert_cmphex(datab, ==, 0); |
| } |
| |
| /** |
| * Test PCI capabilities for AHCI specification adherence. |
| */ |
| static void ahci_test_pci_caps(QPCIDevice *ahci, uint16_t header, |
| uint8_t offset) |
| { |
| uint8_t cid = header & 0xFF; |
| uint8_t next = header >> 8; |
| |
| g_test_message("CID: %02x; next: %02x", cid, next); |
| |
| switch (cid) { |
| case PCI_CAP_ID_PM: |
| ahci_test_pmcap(ahci, offset); |
| break; |
| case PCI_CAP_ID_MSI: |
| ahci_test_msicap(ahci, offset); |
| break; |
| case PCI_CAP_ID_SATA: |
| ahci_test_satacap(ahci, offset); |
| break; |
| |
| default: |
| g_test_message("Unknown CAP 0x%02x", cid); |
| } |
| |
| if (next) { |
| ahci_test_pci_caps(ahci, qpci_config_readw(ahci, next), next); |
| } |
| } |
| |
| /** |
| * Test SATA PCI capabilitity for AHCI specification adherence. |
| */ |
| static void ahci_test_satacap(QPCIDevice *ahci, uint8_t offset) |
| { |
| uint16_t dataw; |
| uint32_t datal; |
| |
| g_test_message("Verifying SATACAP"); |
| |
| /* Assert that the SATACAP version is 1.0, And reserved bits are empty. */ |
| dataw = qpci_config_readw(ahci, offset + 2); |
| g_assert_cmphex(dataw, ==, 0x10); |
| |
| /* Grab the SATACR1 register. */ |
| datal = qpci_config_readw(ahci, offset + 4); |
| |
| switch (datal & 0x0F) { |
| case 0x04: /* BAR0 */ |
| case 0x05: /* BAR1 */ |
| case 0x06: |
| case 0x07: |
| case 0x08: |
| case 0x09: /* BAR5 */ |
| case 0x0F: /* Immediately following SATACR1 in PCI config space. */ |
| break; |
| default: |
| /* Invalid BARLOC for the Index Data Pair. */ |
| g_assert_not_reached(); |
| } |
| |
| /* Reserved. */ |
| g_assert_cmphex((datal >> 24), ==, 0x00); |
| } |
| |
| /** |
| * Test MSI PCI capability for AHCI specification adherence. |
| */ |
| static void ahci_test_msicap(QPCIDevice *ahci, uint8_t offset) |
| { |
| uint16_t dataw; |
| uint32_t datal; |
| |
| g_test_message("Verifying MSICAP"); |
| |
| dataw = qpci_config_readw(ahci, offset + PCI_MSI_FLAGS); |
| ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_ENABLE); |
| ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_QSIZE); |
| ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_RESERVED); |
| |
| datal = qpci_config_readl(ahci, offset + PCI_MSI_ADDRESS_LO); |
| g_assert_cmphex(datal, ==, 0); |
| |
| if (dataw & PCI_MSI_FLAGS_64BIT) { |
| g_test_message("MSICAP is 64bit"); |
| datal = qpci_config_readl(ahci, offset + PCI_MSI_ADDRESS_HI); |
| g_assert_cmphex(datal, ==, 0); |
| dataw = qpci_config_readw(ahci, offset + PCI_MSI_DATA_64); |
| g_assert_cmphex(dataw, ==, 0); |
| } else { |
| g_test_message("MSICAP is 32bit"); |
| dataw = qpci_config_readw(ahci, offset + PCI_MSI_DATA_32); |
| g_assert_cmphex(dataw, ==, 0); |
| } |
| } |
| |
| /** |
| * Test Power Management PCI capability for AHCI specification adherence. |
| */ |
| static void ahci_test_pmcap(QPCIDevice *ahci, uint8_t offset) |
| { |
| uint16_t dataw; |
| |
| g_test_message("Verifying PMCAP"); |
| |
| dataw = qpci_config_readw(ahci, offset + PCI_PM_PMC); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_PME_CLOCK); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_RESERVED); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_D1); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_D2); |
| |
| dataw = qpci_config_readw(ahci, offset + PCI_PM_CTRL); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_STATE_MASK); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_RESERVED); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_DATA_SEL_MASK); |
| ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_DATA_SCALE_MASK); |
| } |
| |
| /******************************************************************************/ |
| /* Test Interfaces */ |
| /******************************************************************************/ |
| |
| /** |
| * Basic sanity test to boot a machine, find an AHCI device, and shutdown. |
| */ |
| static void test_sanity(void) |
| { |
| QPCIDevice *ahci; |
| ahci = ahci_boot(); |
| ahci_shutdown(ahci); |
| } |
| |
| /** |
| * Ensure that the PCI configuration space for the AHCI device is in-line with |
| * the AHCI 1.3 specification for initial values. |
| */ |
| static void test_pci_spec(void) |
| { |
| QPCIDevice *ahci; |
| ahci = ahci_boot(); |
| ahci_test_pci_spec(ahci); |
| ahci_shutdown(ahci); |
| } |
| |
| /** |
| * Engage the PCI AHCI device and sanity check the response. |
| * Perform additional PCI config space bringup for the HBA. |
| */ |
| static void test_pci_enable(void) |
| { |
| QPCIDevice *ahci; |
| void *hba_base; |
| ahci = ahci_boot(); |
| ahci_pci_enable(ahci, &hba_base); |
| ahci_shutdown(ahci); |
| } |
| |
| /******************************************************************************/ |
| |
| int main(int argc, char **argv) |
| { |
| const char *arch; |
| int fd; |
| int ret; |
| int c; |
| |
| static struct option long_options[] = { |
| {"pedantic", no_argument, 0, 'p' }, |
| {0, 0, 0, 0}, |
| }; |
| |
| /* Should be first to utilize g_test functionality, So we can see errors. */ |
| g_test_init(&argc, &argv, NULL); |
| |
| while (1) { |
| c = getopt_long(argc, argv, "", long_options, NULL); |
| if (c == -1) { |
| break; |
| } |
| switch (c) { |
| case -1: |
| break; |
| case 'p': |
| ahci_pedantic = 1; |
| break; |
| default: |
| fprintf(stderr, "Unrecognized ahci_test option.\n"); |
| g_assert_not_reached(); |
| } |
| } |
| |
| /* Check architecture */ |
| arch = qtest_get_arch(); |
| if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) { |
| g_test_message("Skipping test for non-x86"); |
| return 0; |
| } |
| |
| /* Create a temporary raw image */ |
| fd = mkstemp(tmp_path); |
| g_assert(fd >= 0); |
| ret = ftruncate(fd, TEST_IMAGE_SIZE); |
| g_assert(ret == 0); |
| close(fd); |
| |
| /* Run the tests */ |
| qtest_add_func("/ahci/sanity", test_sanity); |
| qtest_add_func("/ahci/pci_spec", test_pci_spec); |
| qtest_add_func("/ahci/pci_enable", test_pci_enable); |
| |
| ret = g_test_run(); |
| |
| /* Cleanup */ |
| unlink(tmp_path); |
| |
| return ret; |
| } |