Skip to content

Commit

Permalink
ahci: Add test_hba_enable to ahci-test.
Browse files Browse the repository at this point in the history
This test engages the HBA functionality and initializes
values to sane defaults to allow for minimal HBA functionality.

Buffers are allocated and pointers are updated to allow minimal
I/O commands to complete as expected. Error registers and responses
are sanity checked for specification adherence.

Signed-off-by: John Snow <jsnow@redhat.com>
Message-id: 1408643079-30675-8-git-send-email-jsnow@redhat.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
  • Loading branch information
jnsnow authored and stefanhaRH committed Sep 22, 2014
1 parent c2f3029 commit dbc180e
Showing 1 changed file with 156 additions and 0 deletions.
156 changes: 156 additions & 0 deletions tests/ahci-test.c
Expand Up @@ -277,11 +277,17 @@ static uint32_t ahci_fingerprint;
#define AHCI_WRITE(OFST, VAL) qpci_io_writel(ahci, hba_base + (OFST), (VAL))
#define AHCI_RREG(regno) AHCI_READ(4 * (regno))
#define AHCI_WREG(regno, val) AHCI_WRITE(4 * (regno), (val))
#define AHCI_SET(regno, mask) AHCI_WREG((regno), AHCI_RREG(regno) | (mask))
#define AHCI_CLR(regno, mask) AHCI_WREG((regno), AHCI_RREG(regno) & ~(mask))

/*** IO macros for port-specific offsets inside of AHCI memory. ***/
#define PX_OFST(port, regno) (HBA_PORT_NUM_REG * (port) + AHCI_PORTS + (regno))
#define PX_RREG(port, regno) AHCI_RREG(PX_OFST((port), (regno)))
#define PX_WREG(port, regno, val) AHCI_WREG(PX_OFST((port), (regno)), (val))
#define PX_SET(port, reg, mask) PX_WREG((port), (reg), \
PX_RREG((port), (reg)) | (mask));
#define PX_CLR(port, reg, mask) PX_WREG((port), (reg), \
PX_RREG((port), (reg)) & ~(mask));

/*** Function Declarations ***/
static QPCIDevice *get_ahci_device(void);
Expand Down Expand Up @@ -432,6 +438,140 @@ static QPCIDevice *start_ahci_device(QPCIDevice *ahci, void **hba_base)
return ahci;
}

/**
* Test and initialize the AHCI's HBA memory areas.
* Initialize and start any ports with devices attached.
* Bring the HBA into the idle state.
*/
static void ahci_hba_enable(QPCIDevice *ahci, void *hba_base)
{
/* Bits of interest in this section:
* GHC.AE Global Host Control / AHCI Enable
* PxCMD.ST Port Command: Start
* PxCMD.SUD "Spin Up Device"
* PxCMD.POD "Power On Device"
* PxCMD.FRE "FIS Receive Enable"
* PxCMD.FR "FIS Receive Running"
* PxCMD.CR "Command List Running"
*/

g_assert(ahci != NULL);
g_assert(hba_base != NULL);

uint32_t reg, ports_impl, clb, fb;
uint16_t i;
uint8_t num_cmd_slots;

g_assert(hba_base != 0);

/* Set GHC.AE to 1 */
AHCI_SET(AHCI_GHC, AHCI_GHC_AE);
reg = AHCI_RREG(AHCI_GHC);
ASSERT_BIT_SET(reg, AHCI_GHC_AE);

/* Read CAP.NCS, how many command slots do we have? */
reg = AHCI_RREG(AHCI_CAP);
num_cmd_slots = ((reg & AHCI_CAP_NCS) >> ctzl(AHCI_CAP_NCS)) + 1;
g_test_message("Number of Command Slots: %u", num_cmd_slots);

/* Determine which ports are implemented. */
ports_impl = AHCI_RREG(AHCI_PI);

for (i = 0; ports_impl; ports_impl >>= 1, ++i) {
if (!(ports_impl & 0x01)) {
continue;
}

g_test_message("Initializing port %u", i);

reg = PX_RREG(i, AHCI_PX_CMD);
if (BITCLR(reg, AHCI_PX_CMD_ST | AHCI_PX_CMD_CR |
AHCI_PX_CMD_FRE | AHCI_PX_CMD_FR)) {
g_test_message("port is idle");
} else {
g_test_message("port needs to be idled");
PX_CLR(i, AHCI_PX_CMD, (AHCI_PX_CMD_ST | AHCI_PX_CMD_FRE));
/* The port has 500ms to disengage. */
usleep(500000);
reg = PX_RREG(i, AHCI_PX_CMD);
ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CR);
ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FR);
g_test_message("port is now idle");
/* The spec does allow for possibly needing a PORT RESET
* or HBA reset if we fail to idle the port. */
}

/* Allocate Memory for the Command List Buffer & FIS Buffer */
/* PxCLB space ... 0x20 per command, as in 4.2.2 p 36 */
clb = guest_alloc(guest_malloc, num_cmd_slots * 0x20);
g_test_message("CLB: 0x%08x", clb);
PX_WREG(i, AHCI_PX_CLB, clb);
g_assert_cmphex(clb, ==, PX_RREG(i, AHCI_PX_CLB));

/* PxFB space ... 0x100, as in 4.2.1 p 35 */
fb = guest_alloc(guest_malloc, 0x100);
g_test_message("FB: 0x%08x", fb);
PX_WREG(i, AHCI_PX_FB, fb);
g_assert_cmphex(fb, ==, PX_RREG(i, AHCI_PX_FB));

/* Clear PxSERR, PxIS, then IS.IPS[x] by writing '1's. */
PX_WREG(i, AHCI_PX_SERR, 0xFFFFFFFF);
PX_WREG(i, AHCI_PX_IS, 0xFFFFFFFF);
AHCI_WREG(AHCI_IS, (1 << i));

/* Verify Interrupts Cleared */
reg = PX_RREG(i, AHCI_PX_SERR);
g_assert_cmphex(reg, ==, 0);

reg = PX_RREG(i, AHCI_PX_IS);
g_assert_cmphex(reg, ==, 0);

reg = AHCI_RREG(AHCI_IS);
ASSERT_BIT_CLEAR(reg, (1 << i));

/* Enable All Interrupts: */
PX_WREG(i, AHCI_PX_IE, 0xFFFFFFFF);
reg = PX_RREG(i, AHCI_PX_IE);
g_assert_cmphex(reg, ==, ~((uint32_t)AHCI_PX_IE_RESERVED));

/* Enable the FIS Receive Engine. */
PX_SET(i, AHCI_PX_CMD, AHCI_PX_CMD_FRE);
reg = PX_RREG(i, AHCI_PX_CMD);
ASSERT_BIT_SET(reg, AHCI_PX_CMD_FR);

/* AHCI 1.3 spec: if !STS.BSY, !STS.DRQ and PxSSTS.DET indicates
* physical presence, a device is present and may be started. However,
* PxSERR.DIAG.X /may/ need to be cleared a priori. */
reg = PX_RREG(i, AHCI_PX_SERR);
if (BITSET(reg, AHCI_PX_SERR_DIAG_X)) {
PX_SET(i, AHCI_PX_SERR, AHCI_PX_SERR_DIAG_X);
}

reg = PX_RREG(i, AHCI_PX_TFD);
if (BITCLR(reg, AHCI_PX_TFD_STS_BSY | AHCI_PX_TFD_STS_DRQ)) {
reg = PX_RREG(i, AHCI_PX_SSTS);
if ((reg & AHCI_PX_SSTS_DET) == SSTS_DET_ESTABLISHED) {
/* Device Found: set PxCMD.ST := 1 */
PX_SET(i, AHCI_PX_CMD, AHCI_PX_CMD_ST);
ASSERT_BIT_SET(PX_RREG(i, AHCI_PX_CMD), AHCI_PX_CMD_CR);
g_test_message("Started Device %u", i);
} else if ((reg & AHCI_PX_SSTS_DET)) {
/* Device present, but in some unknown state. */
g_assert_not_reached();
}
}
}

/* Enable GHC.IE */
AHCI_SET(AHCI_GHC, AHCI_GHC_IE);
reg = AHCI_RREG(AHCI_GHC);
ASSERT_BIT_SET(reg, AHCI_GHC_IE);

/* TODO: The device should now be idling and waiting for commands.
* In the future, a small test-case to inspect the Register D2H FIS
* and clear the initial interrupts might be good. */
}

/*** Specification Adherence Tests ***/

/**
Expand Down Expand Up @@ -1038,6 +1178,21 @@ static void test_hba_spec(void)
ahci_shutdown(ahci);
}

/**
* Engage the HBA functionality of the AHCI PCI device,
* and bring it into a functional idle state.
*/
static void test_hba_enable(void)
{
QPCIDevice *ahci;
void *hba_base;

ahci = ahci_boot();
ahci_pci_enable(ahci, &hba_base);
ahci_hba_enable(ahci, hba_base);
ahci_shutdown(ahci);
}

/******************************************************************************/

int main(int argc, char **argv)
Expand Down Expand Up @@ -1091,6 +1246,7 @@ int main(int argc, char **argv)
qtest_add_func("/ahci/pci_spec", test_pci_spec);
qtest_add_func("/ahci/pci_enable", test_pci_enable);
qtest_add_func("/ahci/hba_spec", test_hba_spec);
qtest_add_func("/ahci/hba_enable", test_hba_enable);

ret = g_test_run();

Expand Down

0 comments on commit dbc180e

Please sign in to comment.