ahci: Fix delays with large numbers of empty ports

* Properly flag missing devices
* Do away with shifts and define ssts and sctl masks
* Tested working on 6 different systems with a
  combination of drive configurations.
* Empty media on AHCI devices still cause port change
  storms. (the issue that was attempted fixed in
  5584c22fdd)
This commit is contained in:
Alexander von Gluck IV 2015-10-14 00:06:27 -05:00
parent 37e5a03660
commit 2f6a9c685a
2 changed files with 61 additions and 54 deletions

View File

@ -80,23 +80,26 @@ enum {
}; };
// Device Detection Initialization // Serial ATA Status and control
#define SATA_CONTROL_DET_SHIFT 0 #define HBA_PORT_IPM_MASK 0x00000f00 // Interface Power Management
#define SATA_CONTROL_DET_MASK 0x0000000f #define SSTS_PORT_IPM_ACTIVE 0x00000100 // active state
#define SSTS_PORT_IPM_PARTIAL 0x00000200 // partial state
#define SSTS_PORT_IPM_SLUMBER 0x00000600 // slumber power management state
#define SSTS_PORT_IPM_DEVSLEEP 0x00000800 // devsleep power management state
#define SCTL_PORT_IPM_NORES 0x00000000 // no power restrictions
#define SCTL_PORT_IPM_NOPART 0x00000100 // no transitions to partial
#define SCTL_PORT_IPM_NOSLUM 0x00000200 // no transitions to slumber
#define DET_NO_INITIALIZATION 0x0 #define HBA_PORT_SPD_MASK 0x000000f0 // Interface speed
#define DET_INITIALIZATION 0x1
// Speed Allowed #define HBA_PORT_DET_MASK 0x0000000f // Device Detection
#define SATA_CONTROL_SPD_SHIFT 4 #define SSTS_PORT_DET_NODEV 0x00000000 // no device detected
#define SATA_CONTROL_SPD_MASK 0x000000f0 #define SSTS_PORT_DET_NOPHY 0x00000001 // device present but PHY not est.
#define SSTS_PORT_DET_PRESENT 0x00000003 // device present and PHY est.
// Interface Power Management Transitions Allowed #define SSTS_PORT_DET_OFFLINE 0x00000004 // device offline due to disabled
#define SATA_CONTROL_IPM_SHIFT 8 #define SCTL_PORT_DET_NOINIT 0x00000000 // no initalization request
#define SATA_CONTROL_IPM_MASK 0x00000f00 #define SCTL_PORT_DET_INIT 0x00000001 // perform interface initalization
#define SCTL_PORT_DET_DISABLE 0x00000004 // disable phy
#define IPM_TRANSITIONS_TO_PARTIAL_DISABLED 0x1
#define IPM_TRANSITIONS_TO_SLUMBER_DISABLED 0x2
// Device signatures // Device signatures
#define SATA_SIG_ATA 0x00000101 // SATA drive #define SATA_SIG_ATA 0x00000101 // SATA drive

View File

@ -117,9 +117,7 @@ AHCIPort::Init1()
// prdt follows after command table // prdt follows after command table
// disable transitions to partial or slumber state // disable transitions to partial or slumber state
fRegs->sctl = (fRegs->sctl & ~SATA_CONTROL_IPM_MASK) fRegs->sctl |= (SCTL_PORT_IPM_NOPART | SCTL_PORT_IPM_NOSLUM);
| (IPM_TRANSITIONS_TO_PARTIAL_DISABLED
| IPM_TRANSITIONS_TO_SLUMBER_DISABLED) << SATA_CONTROL_IPM_SHIFT;
// clear IRQ status bits // clear IRQ status bits
fRegs->is = fRegs->is; fRegs->is = fRegs->is;
@ -166,18 +164,13 @@ AHCIPort::Init2()
TRACE("is 0x%08" B_PRIx32 "\n", fRegs->is); TRACE("is 0x%08" B_PRIx32 "\n", fRegs->is);
TRACE("cmd 0x%08" B_PRIx32 "\n", fRegs->cmd); TRACE("cmd 0x%08" B_PRIx32 "\n", fRegs->cmd);
TRACE("ssts 0x%08" B_PRIx32 "\n", fRegs->ssts); TRACE("ssts 0x%08" B_PRIx32 "\n", fRegs->ssts);
TRACE("sctl.ipm 0x%02" B_PRIx32 "\n", TRACE("sctl.ipm 0x%02" B_PRIx32 "\n", fRegs->sctl & HBA_PORT_IPM_MASK);
(fRegs->sctl & SATA_CONTROL_IPM_MASK) >> SATA_CONTROL_IPM_SHIFT); TRACE("sctl.spd 0x%02" B_PRIx32 "\n", fRegs->sctl & HBA_PORT_SPD_MASK);
TRACE("sctl.spd 0x%02" B_PRIx32 "\n", TRACE("sctl.det 0x%02" B_PRIx32 "\n", fRegs->sctl & HBA_PORT_DET_MASK);
(fRegs->sctl & SATA_CONTROL_SPD_MASK) >> SATA_CONTROL_SPD_SHIFT);
TRACE("sctl.det 0x%02" B_PRIx32 "\n",
(fRegs->sctl & SATA_CONTROL_DET_MASK) >> SATA_CONTROL_DET_SHIFT);
TRACE("serr 0x%08" B_PRIx32 "\n", fRegs->serr); TRACE("serr 0x%08" B_PRIx32 "\n", fRegs->serr);
TRACE("sact 0x%08" B_PRIx32 "\n", fRegs->sact); TRACE("sact 0x%08" B_PRIx32 "\n", fRegs->sact);
TRACE("tfd 0x%08" B_PRIx32 "\n", fRegs->tfd); TRACE("tfd 0x%08" B_PRIx32 "\n", fRegs->tfd);
fDevicePresent = (fRegs->ssts & 0xf) == 0x3;
TRACE("%s: port %d, device %s\n", __func__, fIndex, TRACE("%s: port %d, device %s\n", __func__, fIndex,
fDevicePresent ? "present" : "absent"); fDevicePresent ? "present" : "absent");
@ -229,12 +222,12 @@ AHCIPort::ResetDevice()
return; return;
} }
if (wait_until_set(&fRegs->ssts, 0x1, 100000) < B_OK) if (wait_until_set(&fRegs->ssts, SSTS_PORT_DET_NODEV, 100000) < B_OK)
TRACE("AHCIPort::ResetDevice port %d no device detected\n", fIndex); TRACE("AHCIPort::ResetDevice port %d no device detected\n", fIndex);
_ClearErrorRegister(); _ClearErrorRegister();
if (fRegs->ssts & 1) { if (fRegs->ssts & SSTS_PORT_DET_NOPHY) {
if (wait_until_set(&fRegs->ssts, 0x3, 500000) < B_OK) { if (wait_until_set(&fRegs->ssts, 0x3, 500000) < B_OK) {
TRACE("AHCIPort::ResetDevice port %d device present but no phy " TRACE("AHCIPort::ResetDevice port %d device present but no phy "
"communication\n", fIndex); "communication\n", fIndex);
@ -328,26 +321,20 @@ AHCIPort::PortReset()
return B_ERROR; return B_ERROR;
} }
fRegs->sctl = (fRegs->sctl & ~SATA_CONTROL_DET_MASK) fRegs->sctl |= SCTL_PORT_DET_INIT;
| DET_INITIALIZATION << SATA_CONTROL_DET_SHIFT;
FlushPostedWrites(); FlushPostedWrites();
spin(1100); spin(1100);
// You must wait 1ms at minimum // You must wait 1ms at minimum
fRegs->sctl = (fRegs->sctl & ~SATA_CONTROL_DET_MASK) fRegs->sctl = (fRegs->sctl & ~HBA_PORT_DET_MASK) | SCTL_PORT_DET_NOINIT;
| DET_NO_INITIALIZATION << SATA_CONTROL_DET_SHIFT;
FlushPostedWrites(); FlushPostedWrites();
if (wait_until_set(&fRegs->ssts, 0x3, 500000) < B_OK) { if (wait_until_set(&fRegs->ssts, SSTS_PORT_DET_PRESENT, 500000) < B_OK) {
TRACE("AHCIPort::PortReset port %d device present but no phy " TRACE("%s: port %d: no device detected\n", __func__, fIndex);
"communication\n", fIndex); fDevicePresent = false;
return B_ERROR; return B_OK;
} }
//if ((fRegs->tfd & 0xff) == 0x7f) {
// TRACE("AHCIPort::PostReset port %d: no device\n", fIndex);
// return B_OK;
//}
Enable(); Enable();
return PostReset(); return PostReset();
@ -366,9 +353,26 @@ AHCIPort::PostReset()
return B_ERROR; return B_ERROR;
} }
switch (fRegs->ssts & HBA_PORT_SPD_MASK) {
case 0x10:
TRACE("%s: port %d link speed 1.5Gb/s\n", __func__, fIndex);
break;
case 0x20:
TRACE("%s: port %d link speed 3.0Gb/s\n", __func__, fIndex);
break;
case 0x30:
TRACE("%s: port %d link speed 6.0Gb/s\n", __func__, fIndex);
break;
default:
TRACE("%s: port %d link speed unrestricted\n", __func__, fIndex);
break;
}
wait_until_clear(&fRegs->tfd, ATA_STATUS_BUSY, 31000000); wait_until_clear(&fRegs->tfd, ATA_STATUS_BUSY, 31000000);
fDevicePresent = (fRegs->ssts & HBA_PORT_DET_MASK) == SSTS_PORT_DET_PRESENT;
fIsATAPI = fRegs->sig == SATA_SIG_ATAPI; fIsATAPI = fRegs->sig == SATA_SIG_ATAPI;
if (fIsATAPI) if (fIsATAPI)
fRegs->cmd |= PORT_CMD_ATAPI; fRegs->cmd |= PORT_CMD_ATAPI;
else else
@ -440,12 +444,9 @@ AHCIPort::InterruptErrorHandler(uint32 is)
B_PRIx32 ", is 0x%08" B_PRIx32 ", ci 0x%08" B_PRIx32 "\n", fIndex, B_PRIx32 ", is 0x%08" B_PRIx32 ", ci 0x%08" B_PRIx32 "\n", fIndex,
fCommandsActive, is, ci); fCommandsActive, is, ci);
TRACE("ssts 0x%08" B_PRIx32 "\n", fRegs->ssts); TRACE("ssts 0x%08" B_PRIx32 "\n", fRegs->ssts);
TRACE("sctl.ipm 0x%02" B_PRIx32 "\n", TRACE("sctl.ipm 0x%02" B_PRIx32 "\n", fRegs->sctl & HBA_PORT_IPM_MASK);
(fRegs->sctl & SATA_CONTROL_IPM_MASK) >> SATA_CONTROL_IPM_SHIFT); TRACE("sctl.spd 0x%02" B_PRIx32 "\n", fRegs->sctl & HBA_PORT_SPD_MASK);
TRACE("sctl.spd 0x%02" B_PRIx32 "\n", TRACE("sctl.det 0x%02" B_PRIx32 "\n", fRegs->sctl & HBA_PORT_DET_MASK);
(fRegs->sctl & SATA_CONTROL_SPD_MASK) >> SATA_CONTROL_SPD_SHIFT);
TRACE("sctl.det 0x%02" B_PRIx32 "\n",
(fRegs->sctl & SATA_CONTROL_DET_MASK) >> SATA_CONTROL_DET_SHIFT);
TRACE("serr 0x%08" B_PRIx32 "\n", fRegs->serr); TRACE("serr 0x%08" B_PRIx32 "\n", fRegs->serr);
TRACE("sact 0x%08" B_PRIx32 "\n", fRegs->sact); TRACE("sact 0x%08" B_PRIx32 "\n", fRegs->sact);
} }
@ -488,18 +489,21 @@ AHCIPort::InterruptErrorHandler(uint32 is)
} }
if (is & PORT_INT_PRC) { if (is & PORT_INT_PRC) {
TRACE("PhyReady Change\n"); TRACE("PhyReady Change\n");
// fSoftReset = true; //fSoftReset = true;
} }
if (is & PORT_INT_PC) { if (is & PORT_INT_PC) {
TRACE("Port Connect Change\n"); TRACE("Port Connect Change\n");
// TODO: check if the COMINIT is actually unsolicited! // Unsolicited when we had a port connect change without us requesting
// Spec v1.3, §6.2.2.3 Recovery of Unsolicited COMINIT // Spec v1.3, §6.2.2.3 Recovery of Unsolicited COMINIT
// Spec v1.3.1, §7.4 Interaction of command list and port change status
// TODO: Issue COMRESET ?
//PortReset();
//HBAReset(); ???
// clear error bits to clear PxSERR.DIAG.X // TODO: This isn't enough
//if ((fRegs->sctl & SCTL_PORT_DET_INIT) == 0) {
// TRACE("%s: unsolicited port connect change\n", __func__);
// fSoftReset = true;
// fError = true;
//}
// XXX: This shouldn't be needed here... but we can loop without it
_ClearErrorRegister(); _ClearErrorRegister();
} }
if (is & PORT_INT_UF) { if (is & PORT_INT_UF) {