#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: edc_mca.c,v 1.55 2022/09/25 17:21:18 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/endian.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
#include <sys/syslog.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/rndsource.h>
#include <sys/bus.h>
#include <sys/intr.h>
#include <dev/mca/mcareg.h>
#include <dev/mca/mcavar.h>
#include <dev/mca/mcadevs.h>
#include <dev/mca/edcreg.h>
#include <dev/mca/edvar.h>
#include <dev/mca/edcvar.h>
#include "locators.h"
#define EDC_ATTN_MAXTRIES 10000
#define EDC_MAX_CMD_RES_LEN 8
struct edc_mca_softc {
device_t sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_dma_tag_t sc_dmat;
bus_dmamap_t sc_dmamap_xfer;
void *sc_ih;
int sc_flags;
#define DASD_QUIET 0x01
#define DASD_MAXDEVS 8
struct ed_softc *sc_ed[DASD_MAXDEVS];
int sc_maxdevs;
volatile int sc_stat;
#define STAT_START 0
#define STAT_ERROR 1
#define STAT_DONE 2
volatile int sc_resblk;
u_int16_t status_block[EDC_MAX_CMD_RES_LEN];
};
int edc_mca_probe(device_t, cfdata_t, void *);
void edc_mca_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(edc_mca, sizeof(struct edc_mca_softc),
edc_mca_probe, edc_mca_attach, NULL, NULL);
static int edc_intr(void *);
static void edc_dump_status_block(struct edc_mca_softc *,
u_int16_t *, int);
static int edc_do_attn(struct edc_mca_softc *, int, int, int);
static void edc_cmd_wait(struct edc_mca_softc *, int, int);
static void edcworker(void *);
int
edc_mca_probe(device_t parent, cfdata_t match, void *aux)
{
struct mca_attach_args *ma = aux;
switch (ma->ma_id) {
case MCA_PRODUCT_IBM_ESDIC:
case MCA_PRODUCT_IBM_ESDIC_IG:
return (1);
default:
return (0);
}
}
void
edc_mca_attach(device_t parent, device_t self, void *aux)
{
struct edc_mca_softc *sc = device_private(self);
struct mca_attach_args *ma = aux;
struct ed_attach_args eda;
int pos2, pos3, pos4;
int irq, drq, iobase;
const char *typestr;
int devno, error;
int locs[EDCCF_NLOCS];
sc->sc_dev = self;
pos2 = mca_conf_read(ma->ma_mc, ma->ma_slot, 2);
pos3 = mca_conf_read(ma->ma_mc, ma->ma_slot, 3);
pos4 = mca_conf_read(ma->ma_mc, ma->ma_slot, 4);
switch (ma->ma_id) {
case MCA_PRODUCT_IBM_ESDIC:
typestr = "IBM ESDI Fixed Disk Controller";
break;
case MCA_PRODUCT_IBM_ESDIC_IG:
typestr = "IBM Integ. ESDI Fixed Disk & Controller";
break;
default:
typestr = NULL;
break;
}
irq = ESDIC_IRQ;
iobase = (pos2 & IO_IS_ALT) ? ESDIC_IOALT : ESDIC_IOPRM;
drq = (pos2 & DRQ_MASK) >> 2;
aprint_naive("\n");
aprint_normal(": slot %d irq %d drq %d: %s\n", ma->ma_slot+1,
irq, drq, typestr);
#ifdef DIAGNOSTIC
if (drq == 2 || drq >= 8) {
aprint_error_dev(sc->sc_dev,
"invalid DMA Arbitration Level %d\n", drq);
return;
}
#endif
aprint_normal_dev(self, "Fairness %s, Release %s, ",
(pos2 & FAIRNESS_ENABLE) ? "On" : "Off",
(pos4 & RELEASE_1) ? "6ms"
: ((pos4 & RELEASE_2) ? "3ms" : "Immediate")
);
if ((pos4 & PACING_CTRL_DISABLE) == 0) {
static const char * const pacint[] =
{ "disabled", "16ms", "24ms", "31ms"};
aprint_normal("DMA burst pacing interval %s\n",
pacint[(pos3 & PACING_INT_MASK) >> 4]);
} else
aprint_normal("DMA pacing control disabled\n");
sc->sc_iot = ma->ma_iot;
if (bus_space_map(sc->sc_iot, iobase,
ESDIC_REG_NPORTS, 0, &sc->sc_ioh)) {
aprint_error_dev(sc->sc_dev, "couldn't map registers\n");
return;
}
sc->sc_ih = mca_intr_establish(ma->ma_mc, irq, IPL_BIO, edc_intr, sc);
if (sc->sc_ih == NULL) {
aprint_error_dev(sc->sc_dev,
"couldn't establish interrupt handler\n");
return;
}
sc->sc_dmat = ma->ma_dmat;
if ((error = mca_dmamap_create(sc->sc_dmat, MAXPHYS,
BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW | MCABUS_DMA_16BIT,
&sc->sc_dmamap_xfer, drq)) != 0){
aprint_error_dev(sc->sc_dev,
"couldn't create DMA map - error %d\n", error);
return;
}
if (ma->ma_id == MCA_PRODUCT_IBM_ESDIC_IG)
sc->sc_maxdevs = 1;
else
sc->sc_maxdevs = 2;
if (bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR) & BSR_BUSY) {
aprint_normal_dev(self, "controller busy, "
"performing hardware reset ...\n");
bus_space_write_1(sc->sc_iot, sc->sc_ioh, BCR,
BCR_INT_ENABLE|BCR_RESET);
} else {
edc_do_attn(sc, ATN_RESET_ATTACHMENT, DASD_DEVNO_CONTROLLER,0);
}
while (bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR) & BSR_BUSY) {
if (bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR) & BSR_INTR)
edc_intr(sc);
delay(100);
}
sc->sc_flags |= DASD_QUIET;
for (devno = 0; devno < sc->sc_maxdevs; devno++) {
eda.edc_drive = devno;
locs[EDCCF_DRIVE] = devno;
sc->sc_ed[devno] = device_private(
config_found(self, &eda, NULL,
CFARGS(.submatch = config_stdsubmatch,
.locators = locs)));
if (sc->sc_ed[devno]
&& (sc->sc_ed[devno]->sc_flags & EDF_INIT) == 0)
sc->sc_ed[devno] = NULL;
}
sc->sc_flags &= ~DASD_QUIET;
for (devno = 0; devno < sc->sc_maxdevs; devno++) {
if (sc->sc_ed[devno])
break;
}
if (devno == sc->sc_maxdevs) {
aprint_error("%s: disabling controller (no drives attached)\n",
device_xname(sc->sc_dev));
mca_intr_disestablish(ma->ma_mc, sc->sc_ih);
return;
}
config_pending_incr(self);
if ((error = kthread_create(PRI_NONE, 0, NULL, edcworker, sc, NULL,
"%s", device_xname(sc->sc_dev)))) {
aprint_error_dev(sc->sc_dev,
"cannot spawn worker thread: errno=%d\n", error);
panic("edc_mca_attach");
}
}
void
edc_add_disk(struct edc_mca_softc *sc, struct ed_softc *ed)
{
sc->sc_ed[ed->sc_devno] = ed;
}
static int
edc_intr(void *arg)
{
struct edc_mca_softc *sc = arg;
u_int8_t isr, intr_id;
u_int16_t sifr;
int cmd = -1, devno;
if ((bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR) & BSR_INTR) == 0)
return (0);
isr = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ISR);
intr_id = isr & ISR_INTR_ID_MASK;
#ifdef EDC_DEBUG
if (intr_id == 0 || intr_id == 2 || intr_id == 4) {
aprint_error_dev(sc->sc_dev, "bogus interrupt id %d\n",
(int) intr_id);
return (0);
}
#endif
devno = (isr & 0xe0) >> 5;
if (bus_space_read_1(sc->sc_iot, sc->sc_ioh,BSR) & BSR_SIFR_FULL) {
size_t len;
int i;
sifr = le16toh(bus_space_read_2(sc->sc_iot, sc->sc_ioh, SIFR));
len = (sifr & 0xff00) >> 8;
#ifdef DEBUG
if (len > EDC_MAX_CMD_RES_LEN)
panic("%s: maximum Status Length exceeded: %d > %d",
device_xname(sc->sc_dev),
len, EDC_MAX_CMD_RES_LEN);
#endif
cmd = sifr & SIFR_CMD_MASK;
sc->status_block[0] = sifr;
for(i=1; i < len; i++) {
while((bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR)
& BSR_SIFR_FULL) == 0)
;
sc->status_block[i] = le16toh(
bus_space_read_2(sc->sc_iot, sc->sc_ioh, SIFR));
}
if (i < EDC_MAX_CMD_RES_LEN) {
memset(&sc->status_block[i], 0,
(EDC_MAX_CMD_RES_LEN-i)*sizeof(u_int16_t));
}
}
switch (intr_id) {
case ISR_DATA_TRANSFER_RDY:
bus_space_write_1(sc->sc_iot, sc->sc_ioh, BCR,
BCR_INT_ENABLE|BCR_DMA_ENABLE);
break;
case ISR_COMPLETED:
case ISR_COMPLETED_WITH_ECC:
case ISR_COMPLETED_RETRIES:
case ISR_COMPLETED_WARNING:
if (cmd == CMD_GET_DEV_CONF && sc->sc_ed[devno]) {
memcpy(sc->sc_ed[devno]->sense_data, sc->status_block,
sizeof(sc->sc_ed[devno]->sense_data));
}
sc->sc_stat = STAT_DONE;
break;
case ISR_RESET_COMPLETED:
case ISR_ABORT_COMPLETED:
break;
case ISR_ATTN_ERROR:
panic("%s: dev %d: attention error",
device_xname(sc->sc_dev),
devno);
break;
default:
if ((sc->sc_flags & DASD_QUIET) == 0)
edc_dump_status_block(sc, sc->status_block, intr_id);
sc->sc_stat = STAT_ERROR;
break;
}
if (intr_id != ISR_DATA_TRANSFER_RDY && intr_id != ISR_ATTN_ERROR)
edc_do_attn(sc, ATN_END_INT, devno, intr_id);
if (intr_id != ISR_DATA_TRANSFER_RDY) {
if (cmd == CMD_READ_DATA || cmd == CMD_WRITE_DATA)
sc->sc_resblk = sc->status_block[SB_RESBLKCNT_IDX];
wakeup(sc);
}
return (1);
}
static int
edc_do_attn(struct edc_mca_softc *sc, int attn_type, int devno, int intr_id)
{
int tries;
bus_space_write_1(sc->sc_iot, sc->sc_ioh, BCR, 0);
if (intr_id != ISR_RESET_COMPLETED) {
#ifdef EDC_DEBUG
if (attn_type == ATN_CMD_REQ
&& (bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR)
& BSR_INT_PENDING))
panic("%s: edc int pending", device_xname(sc->sc_dev));
#endif
for(tries=1; tries < EDC_ATTN_MAXTRIES; tries++) {
if ((bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR)
& BSR_BUSY) == 0)
break;
}
if (tries == EDC_ATTN_MAXTRIES) {
printf("%s: edc_do_attn: timeout waiting for "
"attachment to become available\n",
device_xname(sc->sc_ed[devno]->sc_dev));
return (EIO);
}
}
bus_space_write_1(sc->sc_iot, sc->sc_ioh, ATN, attn_type | (devno<<5));
bus_space_write_1(sc->sc_iot, sc->sc_ioh, BCR, BCR_INT_ENABLE);
return (0);
}
static void
edc_cmd_wait(struct edc_mca_softc *sc, int secs, int poll)
{
int val;
if (!poll) {
int s;
s = splbio();
if (sc->sc_stat != STAT_DONE)
(void) tsleep(sc, PRIBIO, "edcwcmd", secs * hz);
splx(s);
}
while((val = bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR))
& BSR_CMD_INPROGRESS) {
if (poll && (val & BSR_INTR))
edc_intr(sc);
}
}
int
edc_run_cmd(struct edc_mca_softc *sc, int cmd, int devno,
u_int16_t cmd_args[], int cmd_len, int poll)
{
int i, error, tries;
u_int16_t cmd0;
sc->sc_stat = STAT_START;
if ((error = edc_do_attn(sc, ATN_CMD_REQ, devno, 0)))
return (error);
cmd0 = ((cmd_len == 4) ? (CIFR_LONG_CMD) : 0)
| (devno << 5)
| (cmd_args[0] << 8) | cmd;
cmd_args[0] = cmd0;
for(i=0; i < cmd_len; i++) {
bus_space_write_2(sc->sc_iot, sc->sc_ioh, CIFR,
htole16(cmd_args[i]));
tries = 0;
for(; (bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR)
& BSR_CIFR_FULL) && tries < 10000 ; tries++)
delay(poll ? 1000 : 1);
;
if (tries == 10000
&& bus_space_read_1(sc->sc_iot, sc->sc_ioh, BSR)
& BSR_CIFR_FULL) {
aprint_error_dev(sc->sc_dev,
"device too slow to accept command %d\n", cmd);
return (EIO);
}
}
edc_cmd_wait(sc, 15, poll);
return ((sc->sc_stat != STAT_DONE) ? EIO : 0);
}
#ifdef EDC_DEBUG
static const char * const edc_commands[] = {
"Invalid Command",
"Read Data",
"Write Data",
"Read Verify",
"Write with Verify",
"Seek",
"Park Head",
"Get Command Complete Status",
"Get Device Status",
"Get Device Configuration",
"Get POS Information",
"Translate RBA",
"Write Attachment Buffer",
"Read Attachment Buffer",
"Run Diagnostic Test",
"Get Diagnostic Status Block",
"Get MFG Header",
"Format Unit",
"Format Prepare",
"Set MAX RBA",
"Set Power Saving Mode",
"Power Conservation Command",
};
static const char * const edc_cmd_status[256] = {
"Reserved",
"Command completed successfully",
"Reserved",
"Command completed successfully with ECC applied",
"Reserved",
"Command completed successfully with retries",
"Format Command partially completed",
"Command completed successfully with ECC and retries",
"Command completed with Warning",
"Aborted",
"Reset completed",
"Data Transfer Ready",
"Command terminated with failure",
"DMA Error",
"Command Block Error",
"Attention Error (Illegal Attention Code)",
};
static const char * const edc_cmd_error[256] = {
"No Error",
"Invalid parameter in the command block",
"Reserved",
"Command not supported",
"Command Aborted per request",
"Reserved",
"Command rejected",
"Format Rejected",
"Format Error (Primary Map is not readable)",
"Format Error (Secondary map is not readable)",
"Format Error (Diagnostic Failure)",
"Format Warning (Secondary Map Overflow)",
"Reserved"
"Format Error (Host Checksum Error)",
"Reserved",
"Format Warning (Push table overflow)",
"Format Warning (More pushes than allowed)",
"Reserved",
"Format Warning (Error during verifying)",
"Invalid device number for the command",
};
static const char * const edc_dev_errors[] = {
"No Error",
"Seek Fault",
"Interface Fault (Parity, Attn, or Cmd Complete Error)",
"Block not found (ID not found)",
"Block not found (AM not found)",
"Data ECC Error (hard error)",
"ID CRC Error",
"RBA Out of Range",
"Reserved",
"Defective Block",
"Reserved",
"Selection Error",
"Reserved",
"Write Fault",
"No index or sector pulse",
"Device Not Ready",
"Seek Error",
"Bad Format",
"Volume Overflow",
"No Data AM Found",
"Block not found (No ID AM or ID CRC error occurred)",
"Reserved",
"Reserved",
"No ID found on track (ID search)",
};
#endif
static void
edc_dump_status_block(struct edc_mca_softc *sc, u_int16_t *status_block,
int intr_id)
{
#ifdef EDC_DEBUG
printf("%s: Command: %s, Status: %s (intr %d)\n",
device_xname(sc->sc_dev),
edc_commands[status_block[0] & 0x1f],
edc_cmd_status[SB_GET_CMD_STATUS(status_block)],
intr_id
);
#else
printf("%s: Command: %d, Status: %d (intr %d)\n",
device_xname(sc->sc_dev),
status_block[0] & 0x1f,
SB_GET_CMD_STATUS(status_block),
intr_id
);
#endif
printf("%s: # left blocks: %u, last processed RBA: %u\n",
device_xname(sc->sc_dev),
status_block[SB_RESBLKCNT_IDX],
(status_block[5] << 16) | status_block[4]);
if (intr_id == ISR_COMPLETED_WARNING) {
#ifdef EDC_DEBUG
aprint_error_dev(sc->sc_dev, "Command Error Code: %s\n",
edc_cmd_error[status_block[1] & 0xff]);
#else
aprint_error_dev(sc->sc_dev, "Command Error Code: %d\n",
status_block[1] & 0xff);
#endif
}
if (intr_id == ISR_CMD_FAILED) {
#ifdef EDC_DEBUG
char buf[100];
printf("%s: Device Error Code: %s\n",
device_xname(sc->sc_dev),
edc_dev_errors[status_block[2] & 0xff]);
snprintb(buf, sizeof(buf),
"\20"
"\01SeekOrCmdComplete"
"\02Track0Flag"
"\03WriteFault"
"\04Selected"
"\05Ready"
"\06Reserved0"
"\07STANDBY"
"\010Reserved0", (status_block[2] & 0xff00) >> 8);
printf("%s: Device Status: %s\n",
device_xname(sc->sc_dev), buf);
#else
printf("%s: Device Error Code: %d, Device Status: %d\n",
device_xname(sc->sc_dev),
status_block[2] & 0xff,
(status_block[2] & 0xff00) >> 8);
#endif
}
}
void
edcworker(void *arg)
{
struct edc_mca_softc *sc = (struct edc_mca_softc *) arg;
struct ed_softc *ed;
struct buf *bp;
int i, error;
config_pending_decr(sc->sc_dev);
for(;;) {
(void) tsleep(sc, PRIBIO, "edcidle", 0);
for(i=0; i<sc->sc_maxdevs; ) {
if ((ed = sc->sc_ed[i]) == NULL) {
i++;
continue;
}
mutex_enter(&ed->sc_q_lock);
if ((bp = bufq_get(ed->sc_q)) == NULL) {
mutex_exit(&ed->sc_q_lock);
i++;
continue;
}
mutex_exit(&ed->sc_q_lock);
disk_busy(&ed->sc_dk);
error = edc_bio(sc, ed, bp->b_data, bp->b_bcount,
bp->b_rawblkno, (bp->b_flags & B_READ), 0);
if (error) {
bp->b_error = error;
} else {
bp->b_resid = sc->sc_resblk * DEV_BSIZE;
}
disk_unbusy(&ed->sc_dk, (bp->b_bcount - bp->b_resid),
(bp->b_flags & B_READ));
rnd_add_uint32(&ed->rnd_source, bp->b_blkno);
biodone(bp);
}
}
}
int
edc_bio(struct edc_mca_softc *sc, struct ed_softc *ed, void *data,
size_t bcount, daddr_t rawblkno, int isread, int poll)
{
u_int16_t cmd_args[4];
int error=0, fl;
u_int16_t track;
u_int16_t cyl;
u_int8_t head;
u_int8_t sector;
mca_disk_busy();
fl = ((poll) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK)
| ((isread) ? BUS_DMA_READ : BUS_DMA_WRITE);
if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap_xfer, data,
bcount, NULL, BUS_DMA_STREAMING|fl))) {
printf("%s: ed_bio: unable to load DMA buffer - error %d\n",
device_xname(ed->sc_dev), error);
goto out;
}
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap_xfer, 0,
bcount, (isread) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE);
track = rawblkno / ed->sectors;
head = track % ed->heads;
cyl = track / ed->heads;
sector = rawblkno % ed->sectors;
cmd_args[0] = 2;
cmd_args[1] = bcount / DEV_BSIZE;
cmd_args[2] = ((cyl & 0x1f) << 11) | (head << 5) | sector;
cmd_args[3] = ((cyl & 0x3E0) >> 5);
error = edc_run_cmd(sc,
(isread) ? CMD_READ_DATA : CMD_WRITE_DATA,
ed->sc_devno, cmd_args, 4, poll);
if (!error) {
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap_xfer, 0, bcount,
(isread)? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
}
bus_dmamap_unload(sc->sc_dmat, sc->sc_dmamap_xfer);
out:
mca_disk_unbusy();
return (error);
}