#include <sys/types.h>
#include "ata_common.h"
#include "atapi.h"
static int atapi_start_cmd(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp,
ata_pkt_t *ata_pktp);
static void atapi_send_cdb(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp);
static void atapi_start_dma(ata_ctl_t *ata_ctlp, ata_drv_t *ata_drvp,
ata_pkt_t *ata_pktp);
static void atapi_pio_data_in(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp);
static void atapi_pio_data_out(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp);
static void atapi_status(ata_ctl_t *ata_ctlp, ata_pkt_t *ata_pktp,
uchar_t status, int dma_complete);
static void atapi_fsm_error(ata_ctl_t *ata_ctlp, uchar_t state,
uchar_t event);
static void
atapi_fsm_error(
ata_ctl_t *ata_ctlp,
uchar_t state,
uchar_t event)
{
ADBG_ERROR(("atapi protocol error: 0x%p 0x%x 0x%x\n",
(void *)ata_ctlp->ac_data, state, event));
}
uchar_t atapi_PioAction[ATAPI_NSTATES][ATAPI_NEVENTS] = {
{ A_NADA, A_NADA, A_NADA, A_NADA, A_NADA, A_NADA, A_NADA, A_NADA },
{ A_NADA, A_NADA, A_NADA, A_CDB, A_NADA, A_NADA, A_RE, A_NADA },
{ A_REX, A_OUT, A_NADA, A_NADA, A_IDLE, A_IN, A_RE, A_UNK },
{ A_REX, A_UNK, A_IDLE, A_UNK, A_IDLE, A_IN, A_RE, A_UNK },
{ A_REX, A_OUT, A_IDLE, A_UNK, A_IDLE, A_UNK, A_RE, A_UNK },
{ A_REX, A_UNK, A_UNK, A_UNK, A_UNK, A_UNK, A_RE, A_UNK }
};
uchar_t atapi_PioNextState[ATAPI_NSTATES][ATAPI_NEVENTS] = {
{ S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE, S_IDLE},
{ S_CDB, S_CDB, S_CDB, S_CDB, S_CDB, S_CDB, S_IDLE, S_X },
{ S_IDLE, S_OUT, S_CDB, S_CDB, S_CDB, S_IN, S_IDLE, S_X },
{ S_IDLE, S_X, S_IN, S_X, S_IN, S_IN, S_IDLE, S_X },
{ S_IDLE, S_OUT, S_OUT, S_X, S_OUT, S_X, S_IDLE, S_X },
{ S_IDLE, S_DMA, S_DMA, S_DMA, S_DMA, S_DMA, S_IDLE, S_DMA }
};
static int
atapi_start_cmd(
ata_ctl_t *ata_ctlp,
ata_drv_t *ata_drvp,
ata_pkt_t *ata_pktp)
{
ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1;
ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2;
if (ata_ctlp->ac_timing_flags & AC_BSY_WAIT) {
if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2,
0, ATS_BSY, 5000000)) {
ADBG_WARN(("atapi_start: BSY too long!\n"));
ata_pktp->ap_flags |= AP_ERROR;
return (ATA_FSM_RC_BUSY);
}
}
ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_pktp->ap_hd);
ata_nsecwait(400);
if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, 0, ATS_BSY, 5000000)) {
ADBG_ERROR(("atapi_start_cmd: drive select failed\n"));
return (ATA_FSM_RC_BUSY);
}
ddi_put8(io_hdl2, ata_ctlp->ac_devctl, ATDC_D3);
ddi_put8(io_hdl1, ata_ctlp->ac_lcyl, ata_pktp->ap_lwcyl);
ddi_put8(io_hdl1, ata_ctlp->ac_hcyl, ata_pktp->ap_hicyl);
ddi_put8(io_hdl1, ata_ctlp->ac_sect, ata_pktp->ap_sec);
ddi_put8(io_hdl1, ata_ctlp->ac_count, ata_pktp->ap_count);
if (ata_pktp->ap_pciide_dma) {
ASSERT((ata_pktp->ap_flags & (AP_READ | AP_WRITE)) != 0);
ddi_put8(io_hdl1, ata_ctlp->ac_feature, ATF_ATAPI_DMA);
ata_pciide_dma_setup(ata_ctlp, ata_pktp->ap_sg_list,
ata_pktp->ap_sg_cnt);
} else {
ddi_put8(io_hdl1, ata_ctlp->ac_feature, 0);
}
ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ata_pktp->ap_cmd);
ata_nsecwait(400);
if (!(ata_drvp->ad_flags & AD_NO_CDB_INTR)) {
return (ATA_FSM_RC_OKAY);
}
if (ata_wait3(io_hdl2, ata_ctlp->ac_ioaddr2,
ATS_DRQ, ATS_BSY,
ATS_ERR, ATS_BSY,
ATS_DF, ATS_BSY,
4000000)) {
return (ATA_FSM_RC_INTR);
}
ADBG_WARN(("atapi_start_cmd: 0x%x status 0x%x error 0x%x\n",
ata_pktp->ap_cmd,
ddi_get8(io_hdl2, ata_ctlp->ac_altstatus),
ddi_get8(io_hdl1, ata_ctlp->ac_error)));
return (ATA_FSM_RC_INTR);
}
static void
atapi_send_cdb(
ata_ctl_t *ata_ctlp,
ata_pkt_t *ata_pktp)
{
ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1;
int padding;
ADBG_TRACE(("atapi_send_cdb entered\n"));
ddi_rep_put16(io_hdl1, (ushort_t *)ata_pktp->ap_cdbp, ata_ctlp->ac_data,
ata_pktp->ap_cdb_len >> 1, DDI_DEV_NO_AUTOINCR);
padding = ata_pktp->ap_cdb_pad;
while (padding) {
ddi_put16(io_hdl1, ata_ctlp->ac_data, 0);
padding--;
}
ata_nsecwait(400);
#ifdef ATA_DEBUG_XXX
{
uchar_t *cp = ata_pktp->ap_cdbp;
ADBG_TRANSPORT(("\tatapi scsi cmd (%d bytes):\n ",
ata_pktp->ap_cdb_len));
ADBG_TRANSPORT(("\t\t 0x%x 0x%x 0x%x 0x%x\n",
cp[0], cp[1], cp[2], cp[3]));
ADBG_TRANSPORT(("\t\t 0x%x 0x%x 0x%x 0x%x\n",
cp[4], cp[5], cp[6], cp[7]));
ADBG_TRANSPORT(("\t\t 0x%x 0x%x 0x%x 0x%x\n",
cp[8], cp[9], cp[10], cp[11]));
}
#endif
ata_pktp->ap_flags |= AP_SENT_CMD;
}
static void
atapi_start_dma(
ata_ctl_t *ata_ctlp,
ata_drv_t *ata_drvp,
ata_pkt_t *ata_pktp)
{
uchar_t rd_wr;
if (ata_pktp->ap_flags & AP_READ)
rd_wr = PCIIDE_BMICX_RWCON_WRITE_TO_MEMORY;
else
rd_wr = PCIIDE_BMICX_RWCON_READ_FROM_MEMORY;
ata_pciide_dma_start(ata_ctlp, rd_wr);
}
static void
atapi_pio_data_in(
ata_ctl_t *ata_ctlp,
ata_pkt_t *ata_pktp)
{
ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1;
int drive_bytes;
int xfer_bytes;
int xfer_words;
ata_pktp->ap_flags |= AP_XFERRED_DATA;
drive_bytes = ((int)ddi_get8(io_hdl1, ata_ctlp->ac_hcyl) << 8)
+ ddi_get8(io_hdl1, ata_ctlp->ac_lcyl);
xfer_bytes = min(ata_pktp->ap_resid, drive_bytes);
ASSERT(xfer_bytes >= 0);
xfer_words = xfer_bytes / 2;
if (xfer_words) {
int byte_count = xfer_words * 2;
ddi_rep_get16(io_hdl1, (ushort_t *)ata_pktp->ap_v_addr,
ata_ctlp->ac_data, xfer_words, DDI_DEV_NO_AUTOINCR);
ata_pktp->ap_v_addr += byte_count;
drive_bytes -= byte_count;
}
if (xfer_bytes & 1) {
ushort_t tmp_word;
tmp_word = ddi_get16(io_hdl1, ata_ctlp->ac_data);
*ata_pktp->ap_v_addr++ = tmp_word & 0xff;
drive_bytes -= 2;
}
ata_pktp->ap_resid -= xfer_bytes;
ADBG_TRANSPORT(("atapi_pio_data_in: read 0x%x bytes\n", xfer_bytes));
if (drive_bytes > 0) {
ADBG_TRANSPORT(("atapi_pio_data_in: dump 0x%x bytes\n",
drive_bytes));
for (; drive_bytes > 0; drive_bytes -= 2)
(void) ddi_get16(io_hdl1, ata_ctlp->ac_data);
}
ata_nsecwait(400);
}
static void
atapi_pio_data_out(
ata_ctl_t *ata_ctlp,
ata_pkt_t *ata_pktp)
{
ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1;
int drive_bytes;
int xfer_bytes;
int xfer_words;
ata_pktp->ap_flags |= AP_XFERRED_DATA;
drive_bytes = ((int)ddi_get8(io_hdl1, ata_ctlp->ac_hcyl) << 8)
+ ddi_get8(io_hdl1, ata_ctlp->ac_lcyl);
xfer_bytes = min(ata_pktp->ap_resid, drive_bytes);
xfer_words = xfer_bytes / 2;
if (xfer_words) {
int byte_count = xfer_words * 2;
ddi_rep_put16(io_hdl1, (ushort_t *)ata_pktp->ap_v_addr,
ata_ctlp->ac_data, xfer_words, DDI_DEV_NO_AUTOINCR);
ata_pktp->ap_v_addr += byte_count;
}
if (xfer_bytes & 1) {
ushort_t tmp_word;
tmp_word = *ata_pktp->ap_v_addr++;
ddi_put16(io_hdl1, ata_ctlp->ac_data, tmp_word);
}
ata_pktp->ap_resid -= xfer_bytes;
ADBG_TRANSPORT(("atapi_pio_data_out: wrote 0x%x bytes\n", xfer_bytes));
ata_nsecwait(400);
}
static void
atapi_status(
ata_ctl_t *ata_ctlp,
ata_pkt_t *ata_pktp,
uchar_t status,
int dma_completion)
{
ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1;
ata_pktp->ap_flags |= AP_GOT_STATUS;
if (status & (ATS_DF | ATS_ERR)) {
ata_pktp->ap_flags |= AP_ERROR;
}
if (ata_pktp->ap_flags & AP_ERROR) {
ata_pktp->ap_status = status;
ata_pktp->ap_error = ddi_get8(io_hdl1, ata_ctlp->ac_error);
}
if (dma_completion && !(ata_pktp->ap_flags & AP_TRAN_ERROR)) {
ata_pktp->ap_flags |= AP_XFERRED_DATA;
ata_pktp->ap_resid = 0;
}
}
static void
atapi_device_reset(
ata_ctl_t *ata_ctlp,
ata_drv_t *ata_drvp)
{
ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1;
ddi_acc_handle_t io_hdl2 = ata_ctlp->ac_iohandle2;
ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_drvp->ad_drive_bits);
ata_nsecwait(400);
ddi_put8(io_hdl1, ata_ctlp->ac_cmd, ATC_DEVICE_RESET);
ata_nsecwait(400);
ddi_put8(io_hdl1, ata_ctlp->ac_drvhd, ata_drvp->ad_drive_bits);
ata_nsecwait(400);
if (!ata_wait(io_hdl2, ata_ctlp->ac_ioaddr2, 0, ATS_BSY, 6 * 1000000)) {
ADBG_WARN(("atapi_device_reset: still busy\n"));
}
}
void
atapi_fsm_reset(ata_ctl_t *ata_ctlp)
{
ata_drv_t *ata_drvp;
int drive;
for (drive = 0; drive <= 1; drive++) {
ata_drvp = CTL2DRV(ata_ctlp, drive, 0);
if (ata_drvp && ATAPIDRV(ata_drvp)) {
ata_drvp->ad_state = S_IDLE;
atapi_device_reset(ata_ctlp, ata_drvp);
}
}
}
int
atapi_fsm_start(
ata_ctl_t *ata_ctlp,
ata_drv_t *ata_drvp,
ata_pkt_t *ata_pktp)
{
int rc;
ADBG_TRACE(("atapi_start entered\n"));
ADBG_TRANSPORT(("atapi_start: pkt = 0x%p\n", ata_pktp));
if (ata_drvp->ad_state != S_IDLE) {
ADBG_ERROR(("atapi_fsm_start not idle 0x%x\n",
ata_drvp->ad_state));
return (ATA_FSM_RC_BUSY);
} else {
ata_drvp->ad_state = S_CMD;
}
rc = atapi_start_cmd(ata_ctlp, ata_drvp, ata_pktp);
switch (rc) {
case ATA_FSM_RC_OKAY:
break;
case ATA_FSM_RC_INTR:
break;
case ATA_FSM_RC_FINI:
break;
case ATA_FSM_RC_BUSY:
ata_drvp->ad_state = S_IDLE;
return (ATA_FSM_RC_BUSY);
}
return (rc);
}
int
atapi_fsm_intr(
ata_ctl_t *ata_ctlp,
ata_drv_t *ata_drvp,
ata_pkt_t *ata_pktp)
{
ddi_acc_handle_t io_hdl1 = ata_ctlp->ac_iohandle1;
uchar_t status;
uchar_t intr_reason;
uchar_t state;
uchar_t event;
uchar_t action;
state = ata_drvp->ad_state;
switch (state) {
case S_DMA:
ASSERT(ata_pktp->ap_pciide_dma == TRUE);
ata_pciide_dma_stop(ata_ctlp);
case S_IDLE:
case S_CMD:
case S_CDB:
case S_IN:
case S_OUT:
break;
}
status = ata_get_status_clear_intr(ata_ctlp, ata_pktp);
if (status & ATS_BSY)
return (ATA_FSM_RC_BUSY);
intr_reason = ddi_get8(io_hdl1, ata_ctlp->ac_count);
event = ATAPI_EVENT(status, intr_reason);
action = atapi_PioAction[state][event];
ata_drvp->ad_state = atapi_PioNextState[state][event];
switch (action) {
default:
case A_UNK:
ADBG_WARN(("atapi_fsm_intr: Unsupported intr\n"));
break;
case A_NADA:
drv_usecwait(100);
break;
case A_CDB:
atapi_send_cdb(ata_ctlp, ata_pktp);
if (ata_pktp->ap_pciide_dma) {
atapi_start_dma(ata_ctlp, ata_drvp, ata_pktp);
ata_drvp->ad_state = S_DMA;
}
break;
case A_IN:
if (!(ata_pktp->ap_flags & AP_READ)) {
atapi_fsm_error(ata_ctlp, state, event);
drv_usecwait(100);
break;
}
if (!ata_pktp->ap_pciide_dma) {
atapi_pio_data_in(ata_ctlp, ata_pktp);
}
break;
case A_OUT:
if (!(ata_pktp->ap_flags & AP_WRITE)) {
atapi_fsm_error(ata_ctlp, state, event);
drv_usecwait(100);
break;
}
if (!ata_pktp->ap_pciide_dma) {
atapi_pio_data_out(ata_ctlp, ata_pktp);
}
break;
case A_IDLE:
if (!ata_drvp->ad_bogus_drq) {
ata_drvp->ad_bogus_drq = TRUE;
atapi_fsm_error(ata_ctlp, state, event);
}
drv_usecwait(100);
break;
case A_RE:
atapi_status(ata_ctlp, ata_pktp, status,
(state == S_DMA) ? TRUE : FALSE);
return (ATA_FSM_RC_FINI);
case A_REX:
if (!ata_drvp->ad_nec_bad_status) {
ata_drvp->ad_nec_bad_status = TRUE;
atapi_fsm_error(ata_ctlp, state, event);
drv_usecwait(100);
}
atapi_status(ata_ctlp, ata_pktp, status,
(state == S_DMA) ? TRUE : FALSE);
return (ATA_FSM_RC_FINI);
}
return (ATA_FSM_RC_OKAY);
}