qemu-patch-raspberry4/tests/ahci-test.c
John Snow 96d6d3bad9 ahci: add test_pci_enable to ahci-test.
This adds a test wherein we engage the PCI AHCI
device and ensure that the memory region for the
HBA functionality is now accessible.

Under Q35 environments, additional PCI configuration
is performed to ensure that the HBA functionality
will become usable.

Signed-off-by: John Snow <jsnow@redhat.com>
Message-id: 1408643079-30675-5-git-send-email-jsnow@redhat.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
2014-09-22 11:39:40 +01:00

543 lines
16 KiB
C

/*
* 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;
}