#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <uvm/uvm_extern.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>
#include <dev/ic/uhareg.h>
#include <dev/ic/uhavar.h>
#define KVTOPHYS(x) vtophys((vaddr_t)x)
void uha_reset_mscp(struct uha_softc *, struct uha_mscp *);
void uha_mscp_free(void *, void *);
void *uha_mscp_alloc(void *);
void uha_scsi_cmd(struct scsi_xfer *);
int uhaprint(void *, const char *);
const struct scsi_adapter uha_switch = {
uha_scsi_cmd, NULL, NULL, NULL, NULL
};
struct cfdriver uha_cd = {
NULL, "uha", DV_DULL
};
#define UHA_ABORT_TIMEOUT 2000
int
uhaprint(void *aux, const char *name)
{
if (name != NULL)
printf("%s: scsibus ", name);
return UNCONF;
}
void
uha_attach(struct uha_softc *sc)
{
struct scsibus_attach_args saa;
(sc->init)(sc);
SLIST_INIT(&sc->sc_free_mscp);
mtx_init(&sc->sc_mscp_mtx, IPL_BIO);
scsi_iopool_init(&sc->sc_iopool, sc, uha_mscp_alloc, uha_mscp_free);
saa.saa_adapter_softc = sc;
saa.saa_adapter_target = sc->sc_scsi_dev;
saa.saa_adapter = &uha_switch;
saa.saa_luns = saa.saa_adapter_buswidth = 8;
saa.saa_openings = 2;
saa.saa_pool = &sc->sc_iopool;
saa.saa_quirks = saa.saa_flags = 0;
saa.saa_wwpn = saa.saa_wwnn = 0;
config_found(&sc->sc_dev, &saa, uhaprint);
}
void
uha_reset_mscp(struct uha_softc *sc, struct uha_mscp *mscp)
{
mscp->flags = 0;
}
void
uha_mscp_free(void *xsc, void *xmscp)
{
struct uha_softc *sc = xsc;
struct uha_mscp *mscp = xmscp;
uha_reset_mscp(sc, mscp);
mtx_enter(&sc->sc_mscp_mtx);
SLIST_INSERT_HEAD(&sc->sc_free_mscp, mscp, chain);
mtx_leave(&sc->sc_mscp_mtx);
}
void *
uha_mscp_alloc(void *xsc)
{
struct uha_softc *sc = xsc;
struct uha_mscp *mscp;
mtx_enter(&sc->sc_mscp_mtx);
mscp = SLIST_FIRST(&sc->sc_free_mscp);
if (mscp) {
SLIST_REMOVE_HEAD(&sc->sc_free_mscp, chain);
mscp->flags |= MSCP_ALLOC;
}
mtx_leave(&sc->sc_mscp_mtx);
return (mscp);
}
struct uha_mscp *
uha_mscp_phys_kv(struct uha_softc *sc, u_long mscp_phys)
{
int hashnum = MSCP_HASH(mscp_phys);
struct uha_mscp *mscp = sc->sc_mscphash[hashnum];
while (mscp) {
if (mscp->hashkey == mscp_phys)
break;
mscp = mscp->nexthash;
}
return (mscp);
}
void
uha_done(struct uha_softc *sc, struct uha_mscp *mscp)
{
struct scsi_sense_data *s1, *s2;
struct scsi_xfer *xs = mscp->xs;
#ifdef UHADEBUG
printf("%s: uha_done\n", sc->sc_dev.dv_xname);
#endif
if ((mscp->flags & MSCP_ALLOC) == 0) {
panic("%s: exiting ccb not allocated!", sc->sc_dev.dv_xname);
return;
}
if (xs->error == XS_NOERROR) {
if (mscp->host_stat != UHA_NO_ERR) {
switch (mscp->host_stat) {
case UHA_SBUS_TIMEOUT:
xs->error = XS_SELTIMEOUT;
break;
default:
printf("%s: host_stat %x\n",
sc->sc_dev.dv_xname, mscp->host_stat);
xs->error = XS_DRIVER_STUFFUP;
}
} else if (mscp->target_stat != SCSI_OK) {
switch (mscp->target_stat) {
case SCSI_CHECK:
s1 = &mscp->mscp_sense;
s2 = &xs->sense;
*s2 = *s1;
xs->error = XS_SENSE;
break;
case SCSI_BUSY:
xs->error = XS_BUSY;
break;
default:
printf("%s: target_stat %x\n",
sc->sc_dev.dv_xname, mscp->target_stat);
xs->error = XS_DRIVER_STUFFUP;
}
} else
xs->resid = 0;
}
scsi_done(xs);
}
void
uha_scsi_cmd(struct scsi_xfer *xs)
{
struct scsi_link *sc_link = xs->sc_link;
struct uha_softc *sc = sc_link->bus->sb_adapter_softc;
struct uha_mscp *mscp;
struct uha_dma_seg *sg;
int seg;
u_long thiskv, thisphys, nextphys;
int bytes_this_seg, bytes_this_page, datalen, flags;
int s;
#ifdef UHADEBUG
printf("%s: uha_scsi_cmd\n", sc->sc_dev.dv_xname);
#endif
flags = xs->flags;
mscp = xs->io;
mscp->xs = xs;
mscp->timeout = xs->timeout;
timeout_set(&xs->stimeout, uha_timeout, xs);
if (flags & SCSI_RESET) {
mscp->opcode = UHA_SDR;
mscp->ca = 0x01;
} else {
mscp->opcode = UHA_TSP;
mscp->ca = 0x01;
bcopy(&xs->cmd, &mscp->scsi_cmd, mscp->scsi_cmd_length);
}
mscp->xdir = UHA_SDET;
mscp->dcn = 0x00;
mscp->chan = 0x00;
mscp->target = sc_link->target;
mscp->lun = sc_link->lun;
mscp->scsi_cmd_length = xs->cmdlen;
mscp->sense_ptr = KVTOPHYS(&mscp->mscp_sense);
mscp->req_sense_length = sizeof(mscp->mscp_sense);
mscp->host_stat = 0x00;
mscp->target_stat = 0x00;
if (xs->datalen) {
sg = mscp->uha_dma;
seg = 0;
#ifdef UHADEBUG
printf("%s: %d @%p- ", sc->sc_dev.dv_xname, xs->datalen, xs->data);
#endif
datalen = xs->datalen;
thiskv = (int) xs->data;
thisphys = KVTOPHYS(thiskv);
while (datalen && seg < UHA_NSEG) {
bytes_this_seg = 0;
sg->seg_addr = thisphys;
#ifdef UHADEBUG
printf("0x%lx", thisphys);
#endif
nextphys = thisphys;
while (datalen && thisphys == nextphys) {
nextphys = (thisphys & ~PGOFSET) + NBPG;
bytes_this_page = nextphys - thisphys;
bytes_this_page = min(bytes_this_page,
datalen);
bytes_this_seg += bytes_this_page;
datalen -= bytes_this_page;
thiskv = (thiskv & ~PGOFSET) + NBPG;
if (datalen)
thisphys = KVTOPHYS(thiskv);
}
#ifdef UHADEBUG
printf("(0x%x)", bytes_this_seg);
#endif
sg->seg_len = bytes_this_seg;
sg++;
seg++;
}
#ifdef UHADEBUG
printf("\n");
#endif
if (datalen) {
printf("%s: uha_scsi_cmd, more than %d dma segs\n",
sc->sc_dev.dv_xname, UHA_NSEG);
goto bad;
}
mscp->data_addr = KVTOPHYS(mscp->uha_dma);
mscp->data_length = xs->datalen;
mscp->sgth = 0x01;
mscp->sg_num = seg;
} else {
mscp->data_addr = (physaddr)0;
mscp->data_length = 0;
mscp->sgth = 0x00;
mscp->sg_num = 0;
}
mscp->link_id = 0;
mscp->link_addr = (physaddr)0;
s = splbio();
(sc->start_mbox)(sc, mscp);
splx(s);
if ((flags & SCSI_POLL) == 0)
return;
if ((sc->poll)(sc, xs, mscp->timeout)) {
uha_timeout(mscp);
if ((sc->poll)(sc, xs, mscp->timeout))
uha_timeout(mscp);
}
return;
bad:
xs->error = XS_DRIVER_STUFFUP;
scsi_done(xs);
return;
}
void
uha_timeout(void *arg)
{
struct uha_mscp *mscp = arg;
struct scsi_xfer *xs = mscp->xs;
struct scsi_link *sc_link = xs->sc_link;
struct uha_softc *sc = sc_link->bus->sb_adapter_softc;
int s;
sc_print_addr(sc_link);
printf("timed out");
s = splbio();
if (mscp->flags & MSCP_ABORT) {
printf(" AGAIN\n");
} else {
printf("\n");
mscp->xs->error = XS_TIMEOUT;
mscp->timeout = UHA_ABORT_TIMEOUT;
mscp->flags |= MSCP_ABORT;
(sc->start_mbox)(sc, mscp);
}
splx(s);
}