-DELAY's will tsleep, so interrupts might run. fix poll loop to detect
+DELAY's might tsleep, so interrupts might run. fix poll loop to detect
completion via other interrupts.
Locking serialize_enter/exit. Lots of recursion. Needs help. Use
-lockmgr()?
-
-Hot plug/unplug SCSI bus rescan sequencing.
+lockmgr()? Needs to be converted to per-port locking, also.
Port multiplier support.
-ATAPI support.
-
Simulate various mode pages (serial number access and so forth).
-ESATA issues with MyBook (port fails after ident even at 1.5 GBits).
+------ Misc probe info --------
+
+<AHCI-PCI-SATA> port
+<S64A,NCQ,SSNTF,SALP,SAL,SCLO,PMD,SSC,PSC,CCCS,EMS>,
+6 ports, 32 tags/port, gen 1 (1.5Gbps) and 2 (3Gbps)
+
+ahci0: AHCI 1.2 capabilities 0xe3229f05
+<S64A,NCQ,SSNTF,SAL,SCLO,SPM,PMD>, 6 ports, 32 tags/port, gen 1 (1.5Gbps) and 2 (3Gbps)
+
+
+Chipsets supporting FBSS (FIS-Based Switching):
+ SB800
+ S5000 (w/ ESB2)
+ (add more)
#include "ahci.h"
+int ahci_port_init(struct ahci_port *ap);
int ahci_port_start(struct ahci_port *, int);
int ahci_port_stop(struct ahci_port *, int);
int ahci_port_clo(struct ahci_port *);
-int ahci_port_softreset(struct ahci_port *);
-int ahci_port_portreset(struct ahci_port *);
+int ahci_port_signature_detect(struct ahci_port *ap);
int ahci_load_prdt(struct ahci_ccb *);
void ahci_unload_prdt(struct ahci_ccb *);
static void ahci_load_prdt_callback(void *info, bus_dma_segment_t *segs,
/* Wait for all bits in _b to be set */
#define ahci_pwait_set(_ap, _r, _b) ahci_pwait_eq((_ap), (_r), (_b), (_b))
+/*
+ * Initialize the global AHCI hardware. This code does not set up any of
+ * its ports.
+ */
int
ahci_init(struct ahci_softc *sc)
{
return (0);
}
+/*
+ * Allocate and initialize an AHCI port.
+ */
int
ahci_port_alloc(struct ahci_softc *sc, u_int port)
{
/* Wait for ICC change to complete */
ahci_pwait_clr(ap, AHCI_PREG_CMD, AHCI_PREG_CMD_ICC);
- /* Reset port */
+ /*
+ * Do device-related port initialization. A failure here does not
+ * cause the port to be deallocated as we want to receive future
+ * hot-plug events.
+ */
+ ahci_port_init(ap);
+ return(0);
+freeport:
+ ahci_port_free(sc, port);
+reterr:
+ return (rc);
+}
+
+/*
+ * [re]initialize an idle port. No CCBs should be active.
+ *
+ * This function is called during the initial port allocation sequence
+ * and is also called on hot-plug insertion. We take no chances and
+ * use a portreset instead of a softreset.
+ *
+ * Returns 0 if a device is successfully detected.
+ */
+int
+ahci_port_init(struct ahci_port *ap)
+{
+ int rc;
+
+ /*
+ * Hard-reset the port.
+ */
rc = ahci_port_portreset(ap);
+
switch (rc) {
case ENODEV:
+ /*
+ * We had problems talking to the device on the port.
+ */
switch (ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) {
case AHCI_PREG_SSTS_DET_DEV_NE:
kprintf("%s: Device not communicating\n", PORTNAME(ap));
break;
case EBUSY:
- device_printf(sc->sc_dev,
- "device on port %d didn't come ready, "
- "TFD: 0x%b\n",
- port,
- ahci_pread(ap, AHCI_PREG_TFD), AHCI_PFMT_TFD_STS);
+ /*
+ * The device on the port is still telling us its busy.
+ *
+ * We try a softreset on the device.
+ */
+ kprintf("%s: Device on port did not come ready, TFD: 0x%b\n",
+ PORTNAME(ap),
+ ahci_pread(ap, AHCI_PREG_TFD), AHCI_PFMT_TFD_STS);
/* Try a soft reset to clear busy */
rc = ahci_port_softreset(ap);
if (rc) {
- device_printf(sc->sc_dev,
- "unable to communicate "
- "with device on port %d\n",
- port);
- goto freeport;
+ kprintf("%s: Unable to clear busy device\n",
+ PORTNAME(ap));
+ } else {
+ kprintf("%s: Successfully reset busy device\n",
+ PORTNAME(ap));
}
break;
* intact so we get hot-plug interrupts.
*/
if (rc == 0) {
- DPRINTF(AHCI_D_VERBOSE, "%s: detected device on port %d\n",
- device_get_name(sc->sc_dev), port);
if (ahci_port_start(ap, 0)) {
- device_printf(sc->sc_dev,
- "failed to start command DMA on port %d, "
- "disabling\n", port);
+ kprintf("%s: failed to start command DMA on port, "
+ "disabling\n", PORTNAME(ap));
rc = ENXIO; /* couldn't start port */
}
}
- /*
- * A missing or busy device is not fatal for the purposes of
- * port allocation. We still want to detect hot-plug
- * state changes.
- */
- if (rc == ENODEV || rc == EBUSY) {
- rc = 0;
- }
-
/* Flush interrupts for port */
ahci_pwrite(ap, AHCI_PREG_IS, ahci_pread(ap, AHCI_PREG_IS));
- ahci_write(sc, AHCI_REG_IS, 1 << port);
+ ahci_write(ap->ap_sc, AHCI_REG_IS, 1 << ap->ap_num);
/* Enable port interrupts */
ahci_pwrite(ap, AHCI_PREG_IE,
#else
AHCI_PREG_IE_SDBE | AHCI_PREG_IE_DHRE
#endif
- );
-
-freeport:
- if (rc != 0)
- ahci_port_free(sc, port);
-reterr:
- return (rc);
+ );
+ return(rc);
}
+/*
+ * De-initialize and detach a port.
+ */
void
ahci_port_free(struct ahci_softc *sc, u_int port)
{
sc->sc_ports[port] = NULL;
}
+/*
+ * Start high-level command processing on the port
+ */
int
ahci_port_start(struct ahci_port *ap, int fre_only)
{
return (0);
}
+/*
+ * Stop high-level command processing on a port
+ */
int
ahci_port_stop(struct ahci_port *ap, int stop_fis_rx)
{
return (0);
}
-/* AHCI command list override -> forcibly clear TFD.STS.{BSY,DRQ} */
+/*
+ * AHCI command list override -> forcibly clear TFD.STS.{BSY,DRQ}
+ */
int
ahci_port_clo(struct ahci_port *ap)
{
return (0);
}
-/* AHCI soft reset, Section 10.4.1 */
+/*
+ * AHCI soft reset, Section 10.4.1
+ *
+ * This function keeps port communications intact and attempts to generate
+ * a reset to the connected device.
+ */
int
ahci_port_softreset(struct ahci_port *ap)
{
goto err;
}
+ /*
+ * If the softreset is trying to clear a BSY condition after a
+ * normal portreset we assign the port type.
+ *
+ * If the softreset is being run first as part of the ccb error
+ * processing code then report if the device signature changed
+ * unexpectedly.
+ */
+ if (ap->ap_ata.ap_type == ATA_PORT_T_NONE) {
+ ap->ap_ata.ap_type = ahci_port_signature_detect(ap);
+ } else {
+ if (ahci_port_signature_detect(ap) != ap->ap_ata.ap_type) {
+ kprintf("%s: device signature unexpectedly changed\n",
+ PORTNAME(ap));
+ rc = EBUSY;
+ }
+ }
+
rc = 0;
err:
if (ccb != NULL) {
return (rc);
}
-/* AHCI port reset, Section 10.4.2 */
+/*
+ * AHCI port reset, Section 10.4.2
+ *
+ * This function does a hard reset of the port. Note that the device
+ * connected to the port could still end-up hung.
+ */
int
ahci_port_portreset(struct ahci_port *ap)
{
goto err;
}
- /*
- * Figure out if we are a ATAPI or DISK device
- */
- u_int32_t sig;
- sig = ahci_pread(ap, AHCI_PREG_SIG);
- if ((sig & 0xffff0000) == (SATA_SIGNATURE_ATAPI & 0xffff0000)) {
- ap->ap_ata.ap_type = ATA_PORT_T_ATAPI;
- } else {
- ap->ap_ata.ap_type = ATA_PORT_T_DISK;
- }
+ ap->ap_ata.ap_type = ahci_port_signature_detect(ap);
rc = 0;
err:
/* Restore preserved port state */
return (rc);
}
+/*
+ * Figure out what type of device is connected to the port, ATAPI or
+ * DISK.
+ */
+int
+ahci_port_signature_detect(struct ahci_port *ap)
+{
+ u_int32_t sig;
+
+ sig = ahci_pread(ap, AHCI_PREG_SIG);
+ if ((sig & 0xffff0000) == (SATA_SIGNATURE_ATAPI & 0xffff0000)) {
+ return(ATA_PORT_T_ATAPI);
+ } else {
+ return(ATA_PORT_T_DISK);
+ }
+}
+
+/*
+ * Load the DMA descriptor table for a CCB's buffer.
+ */
int
ahci_load_prdt(struct ahci_ccb *ccb)
{
if (ap->ap_ata.ap_type == ATA_PORT_T_NONE) {
kprintf("%s: HOTPLUG - Device added\n",
PORTNAME(ap));
- if (ahci_port_portreset(ap) == 0)
- ahci_cam_changed(ap);
+ if (ahci_port_init(ap) == 0)
+ ahci_cam_changed(ap, 1);
}
break;
default:
kprintf("%s: HOTPLUG - Device removed\n",
PORTNAME(ap));
ahci_port_portreset(ap);
- ahci_cam_changed(ap);
+ ahci_cam_changed(ap, 0);
}
break;
}
int ahci_init(struct ahci_softc *);
int ahci_port_alloc(struct ahci_softc *, u_int);
void ahci_port_free(struct ahci_softc *, u_int);
+int ahci_port_softreset(struct ahci_port *);
+int ahci_port_portreset(struct ahci_port *);
+
u_int32_t ahci_read(struct ahci_softc *, bus_size_t);
void ahci_write(struct ahci_softc *, bus_size_t, u_int32_t);
int ahci_wait_ne(struct ahci_softc *, bus_size_t, u_int32_t, u_int32_t);
void ahci_intr(void *);
int ahci_cam_attach(struct ahci_port *ap);
-void ahci_cam_changed(struct ahci_port *ap);
+void ahci_cam_changed(struct ahci_port *ap, int found);
void ahci_cam_detach(struct ahci_port *ap);
struct ata_xfer *ahci_ata_get_xfer(struct ahci_port *ap);
}
void
-ahci_cam_changed(struct ahci_port *ap)
+ahci_cam_changed(struct ahci_port *ap, int found)
{
+ struct cam_path *tmppath;
+
if (ap->ap_sim == NULL)
return;
- ahci_cam_probe(ap);
- ahci_cam_rescan(ap);
+ if (xpt_create_path(&tmppath, NULL, cam_sim_path(ap->ap_sim),
+ 0, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
+ return;
+ }
+ if (found) {
+ ahci_cam_probe(ap);
+ /*
+ * XXX calling AC_FOUND_DEVICE with inquiry data is
+ * basically a NOP. For now just tell CAM to
+ * rescan the bus.
+ */
+ xpt_async(AC_FOUND_DEVICE, tmppath, NULL);
+ ahci_cam_rescan(ap);
+ } else {
+ xpt_async(AC_LOST_DEVICE, tmppath, NULL);
+ }
+ xpt_free_path(tmppath);
}
void
/*
* Once the AHCI port has been attched we need to probe for a device or
* devices on the port and setup various options.
- *
- * XXX use this to allow re-probing and disconnect/reconnect
*/
-
static int
ahci_cam_probe(struct ahci_port *ap)
{
- int error;
-
- switch(ap->ap_ata.ap_type) {
- case ATA_PORT_T_ATAPI:
- error = ahci_cam_probe_atapi(ap);
- break;
- case ATA_PORT_T_DISK:
- error = ahci_cam_probe_disk(ap);
- break;
- default:
- error = EIO;
- break;
- }
- return (error);
-}
-
-static int
-ahci_cam_probe_disk(struct ahci_port *ap)
-{
- /*struct ahci_softc *sc = ap->ap_sc;*/
struct ata_xfer *xa;
u_int64_t capacity;
u_int64_t capacity_bytes;
int model_len;
int status;
+ int error;
int devncqdepth;
int i;
const char *wcstr;
const char *rastr;
+ const char *scstr;
+ const char *type;
+
+ if (ap->ap_ata.ap_type == ATA_PORT_T_NONE)
+ return (EIO);
/*
* Issue identify, saving the result
xa->data = &ap->ap_ata.ap_identify;
xa->datalen = sizeof(ap->ap_ata.ap_identify);
xa->fis->flags = ATA_H2D_FLAGS_CMD;
- xa->fis->command = ATA_C_IDENTIFY;
+ if (ap->ap_ata.ap_type == ATA_PORT_T_ATAPI) {
+ xa->fis->command = ATA_C_ATAPI_IDENTIFY;
+ type = "ATAPI";
+ } else {
+ xa->fis->command = ATA_C_IDENTIFY;
+ type = "DISK";
+ }
xa->fis->features = 0;
xa->fis->device = 0;
xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
status = ahci_ata_cmd(xa);
if (status != ATA_COMPLETE) {
- kprintf("%s: Detected DISK device but unable to IDENTIFY\n",
- PORTNAME(ap));
+ kprintf("%s: Detected %s device but unable to IDENTIFY\n",
+ PORTNAME(ap), type);
ahci_ata_put_xfer(xa);
return(EIO);
}
if (xa->state != ATA_S_COMPLETE) {
- kprintf("%s: Detected DISK device but unable to IDENTIFY "
+ kprintf("%s: Detected %s device but unable to IDENTIFY "
" xa->state=%d\n",
- PORTNAME(ap), xa->state);
+ PORTNAME(ap), type, xa->state);
ahci_ata_put_xfer(xa);
return(EIO);
}
devncqdepth = 0;
}
+ /*
+ * Make the model string a bit more presentable
+ */
for (model_len = 40; model_len; --model_len) {
if (ap->ap_ata.ap_identify.model[model_len-1] == ' ')
continue;
break;
}
+ /*
+ * Generate informatiive strings.
+ *
+ * NOTE: We do not automatically set write caching, lookahead,
+ * or the security state for ATAPI devices.
+ */
if (ap->ap_ata.ap_identify.cmdset82 & ATA_IDENTIFY_WRITECACHE) {
if (ap->ap_ata.ap_identify.features85 & ATA_IDENTIFY_WRITECACHE)
wcstr = "enabled";
+ else if (ap->ap_ata.ap_type == ATA_PORT_T_ATAPI)
+ wcstr = "disabled";
else
wcstr = "enabling";
} else {
if (ap->ap_ata.ap_identify.cmdset82 & ATA_IDENTIFY_LOOKAHEAD) {
if (ap->ap_ata.ap_identify.features85 & ATA_IDENTIFY_LOOKAHEAD)
rastr = "enabled";
+ else if (ap->ap_ata.ap_type == ATA_PORT_T_ATAPI)
+ rastr = "disabled";
else
rastr = "enabling";
} else {
rastr = "notsupp";
}
- kprintf("%s: Found DISK \"%*.*s %8.8s\" serial=\"%20.20s\"\n"
+ if (ap->ap_ata.ap_identify.cmdset82 & ATA_IDENTIFY_SECURITY) {
+ if (ap->ap_ata.ap_identify.securestatus & ATA_SECURE_FROZEN)
+ scstr = "frozen";
+ else if (ap->ap_ata.ap_type == ATA_PORT_T_ATAPI)
+ scstr = "unfrozen";
+ else
+ scstr = "freezing";
+ } else {
+ scstr = "notsupp";
+ }
+
+ kprintf("%s: Found %s \"%*.*s %8.8s\" serial=\"%20.20s\"\n"
"%s: tags=%d/%d satacaps=%04x satafeat=%04x "
"capacity=%lld.%02dMB\n"
- "%s: f85=%04x f86=%04x f87=%04x WC=%s RA=%s\n",
+ "%s: f85=%04x f86=%04x f87=%04x WC=%s RA=%s SEC=%s\n",
PORTNAME(ap),
+ type,
model_len, model_len,
ap->ap_ata.ap_identify.model,
ap->ap_ata.ap_identify.firmware,
ap->ap_ata.ap_identify.features86,
ap->ap_ata.ap_identify.features87,
wcstr,
- rastr
+ rastr,
+ scstr
);
/*
+ * Additional type-specific probing
+ */
+ switch(ap->ap_ata.ap_type) {
+ case ATA_PORT_T_DISK:
+ error = ahci_cam_probe_disk(ap);
+ break;
+ default:
+ error = ahci_cam_probe_atapi(ap);
+ break;
+ }
+ return (0);
+}
+
+/*
+ * DISK-specific probe after initial ident
+ */
+static int
+ahci_cam_probe_disk(struct ahci_port *ap)
+{
+ struct ata_xfer *xa;
+ int status;
+
+ /*
* Enable write cache if supported
+ *
+ * NOTE: "WD My Book" external disk devices have a very poor
+ * daughter board between the the ESATA and the HD. Sending
+ * any ATA_C_SET_FEATURES commands will break the hardware port
+ * with a fatal protocol error. However, this device also
+ * indicates that WRITECACHE is already on and READAHEAD is
+ * not supported so we avoid the issue.
*/
if ((ap->ap_ata.ap_identify.cmdset82 & ATA_IDENTIFY_WRITECACHE) &&
(ap->ap_ata.ap_identify.features85 & ATA_IDENTIFY_WRITECACHE) == 0) {
* checking if the device sends a command abort to tell us it doesn't
* support it
*/
- if (ap->ap_ata.ap_identify.cmdset82 & ATA_IDENTIFY_SECURITY) {
+ if ((ap->ap_ata.ap_identify.cmdset82 & ATA_IDENTIFY_SECURITY) &&
+ (ap->ap_ata.ap_identify.securestatus & ATA_SECURE_FROZEN) == 0) {
xa = ahci_ata_get_xfer(ap);
xa->complete = ahci_ata_dummy_done;
xa->fis->command = ATA_C_SEC_FREEZE_LOCK;
return (0);
}
+/*
+ * ATAPI-specific probe after initial ident
+ */
static int
ahci_cam_probe_atapi(struct ahci_port *ap)
{
- /*struct ahci_softc *sc = ap->ap_sc;*/
+ return(0);
+}
+
+#if 0
+ /*
+ * Keep this old code around for a little bit, it is another way
+ * to probe an ATAPI device by using a ATAPI (SCSI) INQUIRY
+ */
struct ata_xfer *xa;
int status;
int devncqdepth;
devncqdepth = 0;
-#if 0
- for (model_len = 40; model_len; --model_len) {
- if (ap->ap_ata.ap_identify.model[model_len-1] == ' ')
- continue;
- if (ap->ap_ata.ap_identify.model[model_len-1] == 0)
- continue;
- break;
- }
-#endif
-
kprintf("%s: Found ATAPI %s \"%8.8s %16.16s\" rev=\"%4.4s\"\n"
"%s: tags=%d/%d\n",
PORTNAME(ap),
devncqdepth, ap->ap_sc->sc_ncmds
);
kfree(inq_data, M_TEMP);
-
-#if 0
- /*
- * FREEZE LOCK the device so malicious users can't lock it on us.
- * As there is no harm in issuing this to devices that don't
- * support the security feature set we just send it, and don't bother
- * checking if the device sends a command abort to tell us it doesn't
- * support it
- */
- xa = ahci_ata_get_xfer(ap);
- xa->complete = ahci_ata_dummy_done;
- xa->datalen = 0;
- xa->fis->command = ATA_C_SEC_FREEZE_LOCK;
- xa->fis->flags = ATA_H2D_FLAGS_CMD;
- xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL;
- xa->timeout = hz;
- status = ahci_ata_cmd(xa);
- if (status == ATA_COMPLETE)
- ap->ap_ata.ap_features |= ATA_PORT_F_FRZLCK;
- ahci_ata_put_xfer(xa);
#endif
- return (0);
-}
-
/*
* Fix byte ordering so buffers can be accessed as
* strings.
}
/*
- * Initiate bus scan.
+ * Initiate a bus scan.
+ *
+ * An asynchronous bus scan is used to avoid reentrancy issues
*/
static void
ahci_cam_rescan_callback(struct cam_periph *periph, union ccb *ccb)
return;
xpt_setup_ccb(&ccb->ccb_h, path, 5); /* 5 = low priority */
- ccb->ccb_h.func_code = XPT_SCAN_BUS;
+ ccb->ccb_h.func_code = XPT_SCAN_BUS | XPT_FC_QUEUED;
ccb->ccb_h.cbfcnp = ahci_cam_rescan_callback;
ccb->crcn.flags = CAM_FLAG_NONE;
xpt_action(ccb);
xpt_done(ccb);
break;
case XPT_RESET_DEV:
- ccbh->status = CAM_REQ_INVALID;
+ lwkt_serialize_enter(&ap->ap_sc->sc_serializer);
+ ahci_port_softreset(ap);
+ lwkt_serialize_exit(&ap->ap_sc->sc_serializer);
+
+ ccbh->status = CAM_REQ_CMP;
xpt_done(ccb);
break;
case XPT_RESET_BUS:
- ccbh->status = CAM_REQ_INVALID;
+ lwkt_serialize_enter(&ap->ap_sc->sc_serializer);
+ ahci_port_portreset(ap);
+ ahci_port_softreset(ap);
+ lwkt_serialize_exit(&ap->ap_sc->sc_serializer);
+
+ xpt_async(AC_BUS_RESET, ap->ap_path, NULL);
+
+ ccbh->status = CAM_REQ_CMP;
xpt_done(ccb);
break;
case XPT_SET_TRAN_SETTINGS:
#define ATA_C_READ_FPDMA 0x60
#define ATA_C_WRITE_FPDMA 0x61
#define ATA_C_PACKET 0xa0
+#define ATA_C_ATAPI_IDENTIFY 0xa1
#define ATA_C_READDMA 0xc8
#define ATA_C_WRITEDMA 0xca
#define ATA_C_FLUSH_CACHE 0xe7
u_int16_t padding2[6];
u_int16_t rmsn; /* 127 */
u_int16_t securestatus; /* 128 */
+#define ATA_SECURE_LOCKED (1<<2)
+#define ATA_SECURE_FROZEN (1<<3)
u_int16_t vendor[31]; /* 129 */
u_int16_t padding3[16]; /* 160 */
u_int16_t curmedser[30]; /* 176 */