summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/dev/ic/ahci.c117
-rw-r--r--sys/include/dev/ic/ahciregs.h24
-rw-r--r--sys/include/dev/ic/ahcivar.h2
3 files changed, 115 insertions, 28 deletions
diff --git a/sys/dev/ic/ahci.c b/sys/dev/ic/ahci.c
index 700fb05..d713a0e 100644
--- a/sys/dev/ic/ahci.c
+++ b/sys/dev/ic/ahci.c
@@ -33,6 +33,7 @@
#include <sys/syslog.h>
#include <sys/mmio.h>
#include <dev/pci/pci.h>
+#include <dev/timer.h>
#include <dev/ic/ahcivar.h>
#include <dev/ic/ahciregs.h>
@@ -40,24 +41,96 @@
#define pr_error(...) pr_trace(__VA_ARGS__)
static struct pci_device *ahci_dev;
+static struct timer tmr;
/*
- * Poll for at most 1 second, to see if GHC.HR is 0.
+ * Poll register to have 'bits' set/unset.
*
- * @memspace: A pointer the HBA MMIO space.
+ * @reg: Register to poll.
+ * @bits: Bits to be checked.
+ * @pollset: True to poll as set.
*/
static int
-poll_ghc_hr(struct hba_memspace *memspace)
+ahci_poll_reg(volatile uint32_t *reg, uint32_t bits, bool pollset)
{
- int count;
+ size_t usec_start, usec;
+ size_t elapsed_msec;
+ uint32_t val;
+ bool tmp;
- for (count = 0; count < 10000000; count++) {
- if ((mmio_read32(&(memspace->ghc)) & 1) == 0) {
- return 0;
+ usec_start = tmr.get_time_usec();
+
+ for (;;) {
+ val = mmio_read32(reg);
+ tmp = (pollset) ? ISSET(val, bits) : !ISSET(val, bits);
+
+ usec = tmr.get_time_usec();
+ elapsed_msec = (usec - usec_start) / 1000;
+
+ /* If tmp is set, the register updated in time */
+ if (tmp) {
+ break;
}
+
+ /* Exit with an error if we timeout */
+ if (elapsed_msec > AHCI_TIMEOUT) {
+ return -ETIME;
+ }
+ }
+
+ return val;
+}
+
+static int
+ahci_hba_reset(struct ahci_hba *hba)
+{
+ struct hba_memspace *abar = hba->io;
+ uint32_t tmp;
+ int error;
+
+ /*
+ * Begin the actual reset process, results in all
+ * HBA state and ports being reset.
+ *
+ * XXX: All ports will need be to brought back up
+ * through a COMRESET...
+ */
+ tmp = mmio_read32(&abar->ghc);
+ tmp |= AHCI_GHC_HR;
+ mmio_write32(&abar->ghc, tmp);
+
+ /*
+ * This should usually work with no problem but we cannot
+ * guarantee anything. Especially if it comes to quirky
+ * hardware. The GHC.HR bit should be flipped back by the
+ * HBA once the reset is complete.
+ */
+ error = ahci_poll_reg(&abar->ghc, AHCI_GHC_HR, false);
+ if (error < 0) {
+ pr_error("HBA reset failed\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int
+ahci_hba_init(struct ahci_hba *hba)
+{
+ int error;
+
+ /*
+ * God knows what state the HBA is in by the time
+ * BIOS/UEFI passes control to us... Because of this,
+ * we need to *reset* everything to ensure it is
+ * as we expect.
+ */
+ if ((error = ahci_hba_reset(hba)) < 0) {
+ return error;
}
- return 1;
+ pr_trace("Successfully performed a hard reset.\n");
+ return 0;
}
static int
@@ -85,6 +158,24 @@ ahci_init(void)
ahci_dev->bus, ahci_dev->device_id, ahci_dev->func,
ahci_dev->slot);
+ /* Try to request a general purpose timer */
+ if (req_timer(TIMER_GP, &tmr) != TMRR_SUCCESS) {
+ pr_error("Failed to fetch general purpose timer\n");
+ return -ENODEV;
+ }
+
+ /* Ensure it has get_time_usec() */
+ if (tmr.get_time_usec == NULL) {
+ pr_error("General purpose timer has no get_time_usec()\n");
+ return -ENODEV;
+ }
+
+ /* We also need msleep() */
+ if (tmr.msleep == NULL) {
+ pr_error("General purpose timer has no msleep()\n");
+ return -ENODEV;
+ }
+
/*
* Map the AHCI Base Address Register (ABAR) from the
* ahci_dev struct, so that we can perform MMIO and then issue
@@ -95,14 +186,8 @@ ahci_init(void)
return status;
}
- hba.io = (struct hba_memspace*) abar_vap;
- mmio_write32(&(hba.io->ghc), mmio_read32(&(hba.io->ghc)) | 1);
-
- if (poll_ghc_hr(hba.io) != 0) {
- return 1;
- }
-
- pr_trace("Successfully performed a hard reset.\n");
+ hba.io = (struct hba_memspace*)abar_vap;
+ ahci_hba_init(&hba);
return 0;
}
diff --git a/sys/include/dev/ic/ahciregs.h b/sys/include/dev/ic/ahciregs.h
index 319e35f..4a4dc65 100644
--- a/sys/include/dev/ic/ahciregs.h
+++ b/sys/include/dev/ic/ahciregs.h
@@ -31,7 +31,7 @@
#define _IC_AHCIREGS_H_
#include <sys/types.h>
-#include <sys/cdefs.h>
+#include <sys/param.h>
struct hba_port {
volatile uint64_t clb; /* Command list base (1k-byte aligned) */
@@ -71,9 +71,9 @@ struct hba_memspace {
};
/* Global host control bits */
-#define AHCI_GHC_AE __BIT(31) /* AHCI enable */
-#define AHCI_GHC_IE __BIT(1) /* Interrupt enable */
-#define AHCI_GHC_HR __BIT(0) /* HBA reset */
+#define AHCI_GHC_AE BIT(31) /* AHCI enable */
+#define AHCI_GHC_IE BIT(1) /* Interrupt enable */
+#define AHCI_GHC_HR BIT(0) /* HBA reset */
/* AHCI port signatures */
#define AHCI_SIG_ATA 0x00000101
@@ -97,24 +97,24 @@ struct hba_memspace {
* Port command and status bits
* See section 3.3.7 of the AHCI spec.
*/
-#define AHCI_PXCMD_ST __BIT(0) /* Start */
-#define AHCI_PXCMD_FRE __BIT(4) /* FIS Receive Enable */
-#define AHCI_PXCMD_FR __BIT(14) /* FIS Receive Running */
-#define AHCI_PXCMD_CR __BIT(15) /* Command List Running */
+#define AHCI_PXCMD_ST BIT(0) /* Start */
+#define AHCI_PXCMD_FRE BIT(4) /* FIS Receive Enable */
+#define AHCI_PXCMD_FR BIT(14) /* FIS Receive Running */
+#define AHCI_PXCMD_CR BIT(15) /* Command List Running */
/*
* Interrupt status bits
* See section 3.3.5 of the AHCI spec.
*/
-#define AHCI_PXIS_TFES __BIT(31)
+#define AHCI_PXIS_TFES BIT(31)
/*
* Task file data bits
* See section 3.3.8 of the AHCI spec.
*/
-#define AHCI_PXTFD_ERR __BIT(0)
-#define AHCI_PXTFD_DRQ __BIT(3)
-#define AHCI_PXTFD_BSY __BIT(7)
+#define AHCI_PXTFD_ERR BIT(0)
+#define AHCI_PXTFD_DRQ BIT(3)
+#define AHCI_PXTFD_BSY BIT(7)
/*
* Capability bits
diff --git a/sys/include/dev/ic/ahcivar.h b/sys/include/dev/ic/ahcivar.h
index 1efea70..0d307cd 100644
--- a/sys/include/dev/ic/ahcivar.h
+++ b/sys/include/dev/ic/ahcivar.h
@@ -36,4 +36,6 @@ struct ahci_hba {
struct hba_memspace *io;
};
+#define AHCI_TIMEOUT 500 /* In ms */
+
#endif /* !_IC_AHCIVAR_H_ */