From 44de612bd0395b3d052ce4d9e76c9c58253e92d5 Mon Sep 17 00:00:00 2001 From: "Justin T. Gibbs" Date: Tue, 15 Sep 1998 07:10:00 +0000 Subject: [PATCH] Adaptec 174x EISA SCSI Host Adapter driver for CAM. --- sys/dev/ahb/ahb.c | 1344 ++++++++++++++++++++++++++++++++++++++++ sys/dev/ahb/ahbreg.h | 283 +++++++++ sys/i386/eisa/ahb.c | 1344 ++++++++++++++++++++++++++++++++++++++++ sys/i386/eisa/ahbreg.h | 283 +++++++++ 4 files changed, 3254 insertions(+) create mode 100644 sys/dev/ahb/ahb.c create mode 100644 sys/dev/ahb/ahbreg.h create mode 100644 sys/i386/eisa/ahb.c create mode 100644 sys/i386/eisa/ahbreg.h diff --git a/sys/dev/ahb/ahb.c b/sys/dev/ahb/ahb.c new file mode 100644 index 00000000000..1993477e2de --- /dev/null +++ b/sys/dev/ahb/ahb.c @@ -0,0 +1,1344 @@ +/* + * CAM SCSI device driver for the Adaptec 174X SCSI Host adapter + * + * Copyright (c) 1998 Justin T. Gibbs + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "eisa.h" +#if NEISA > 0 +#include /* For offsetof() */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#define ccb_ecb_ptr spriv_ptr0 +#define ccb_ahb_ptr spriv_ptr1 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define ahb_inb(ahb, port) \ + bus_space_read_1((ahb)->tag, (ahb)->bsh, port) + +#define ahb_inl(ahb, port) \ + bus_space_read_4((ahb)->tag, (ahb)->bsh, port) + +#define ahb_outb(ahb, port, value) \ + bus_space_write_1((ahb)->tag, (ahb)->bsh, port, value) + +#define ahb_outl(ahb, port, value) \ + bus_space_write_4((ahb)->tag, (ahb)->bsh, port, value) + +static const char *ahbmatch(eisa_id_t type); +static int ahbprobe(void); +static int ahbattach(struct eisa_device *dev); +static struct ahb_softc *ahballoc(u_long unit, u_int iobase, u_int irq); +static void ahbfree(struct ahb_softc *ahb); +static int ahbreset(struct ahb_softc *ahb); +static void ahbmapecbs(void *arg, bus_dma_segment_t *segs, + int nseg, int error); +static int ahbxptattach(struct ahb_softc *ahb); +static void ahbhandleimmed(struct ahb_softc *ahb, + u_int32_t mbox, u_int intstat); +static void ahbcalcresid(struct ahb_softc *ahb, + struct ecb *ecb, union ccb *ccb); +static __inline void ahbdone(struct ahb_softc *ahb, u_int32_t mbox, + u_int intstat); +static void ahbintr(void *arg); +static bus_dmamap_callback_t ahbexecuteecb; +static void ahbaction(struct cam_sim *sim, union ccb *ccb); +static void ahbpoll(struct cam_sim *sim); + +/* Our timeout handler */ +timeout_t ahbtimeout; + +static __inline struct ecb* ahbecbget(struct ahb_softc *ahb); +static __inline void ahbecbfree(struct ahb_softc* ahb, + struct ecb* ecb); +static __inline u_int32_t ahbecbvtop(struct ahb_softc *ahb, + struct ecb *ecb); +static __inline struct ecb* ahbecbptov(struct ahb_softc *ahb, + u_int32_t ecb_addr); +static __inline u_int32_t ahbstatuspaddr(u_int32_t ecb_paddr); +static __inline u_int32_t ahbsensepaddr(u_int32_t ecb_paddr); +static __inline u_int32_t ahbsgpaddr(u_int32_t ecb_paddr); +static __inline void ahbqueuembox(struct ahb_softc *ahb, + u_int32_t mboxval, + u_int attn_code); + +static __inline struct ecb* +ahbecbget(struct ahb_softc *ahb) +{ + struct ecb* ecb; + int s; + + s = splcam(); + if ((ecb = SLIST_FIRST(&ahb->free_ecbs)) != NULL) + SLIST_REMOVE_HEAD(&ahb->free_ecbs, links); + splx(s); + + return (ecb); +} + +static __inline void +ahbecbfree(struct ahb_softc* ahb, struct ecb* ecb) +{ + int s; + + s = splcam(); + ecb->state = ECB_FREE; + SLIST_INSERT_HEAD(&ahb->free_ecbs, ecb, links); + splx(s); +} + +static __inline u_int32_t +ahbecbvtop(struct ahb_softc *ahb, struct ecb *ecb) +{ + return (ahb->ecb_physbase + + (u_int32_t)((caddr_t)ecb - (caddr_t)ahb->ecb_array)); +} + +static __inline struct ecb* +ahbecbptov(struct ahb_softc *ahb, u_int32_t ecb_addr) +{ + return (ahb->ecb_array + + ((struct ecb*)ecb_addr - (struct ecb*)ahb->ecb_physbase)); +} + +static __inline u_int32_t +ahbstatuspaddr(u_int32_t ecb_paddr) +{ + return (ecb_paddr + offsetof(struct ecb, status)); +} + +static __inline u_int32_t +ahbsensepaddr(u_int32_t ecb_paddr) +{ + return (ecb_paddr + offsetof(struct ecb, sense)); +} + +static __inline u_int32_t +ahbsgpaddr(u_int32_t ecb_paddr) +{ + return (ecb_paddr + offsetof(struct ecb, sg_list)); +} + +static __inline void +ahbqueuembox(struct ahb_softc *ahb, u_int32_t mboxval, u_int attn_code) +{ + u_int loopmax = 300; + while (--loopmax) { + u_int status; + + status = ahb_inb(ahb, HOSTSTAT); + if ((status & HOSTSTAT_MBOX_EMPTY|HOSTSTAT_BUSY) + != HOSTSTAT_MBOX_EMPTY) + break; + DELAY(20); + } + if (loopmax == 0) + panic("ahb%d: adapter not taking commands\n", ahb->unit); + + ahb_outl(ahb, MBOXOUT0, mboxval); + ahb_outb(ahb, ATTN, attn_code); +} + +static u_long ahbunit; + +static struct eisa_driver ahb_eisa_driver = +{ + "ahb", + ahbprobe, + ahbattach, + /*shutdown*/NULL, + &ahbunit +}; + +DATA_SET (eisadriver_set, ahb_eisa_driver); + +static const char * +ahbmatch(eisa_id_t type) +{ + switch(type & 0xfffffe00) { + case EISA_DEVICE_ID_ADAPTEC_1740: + return ("Adaptec 174x SCSI host adapter"); + break; + default: + break; + } + return (NULL); +} + +static int +ahbprobe(void) +{ + struct eisa_device *e_dev = NULL; + u_int32_t iobase; + u_int32_t irq; + int count; + + count = 0; + while ((e_dev = eisa_match_dev(e_dev, ahbmatch))) { + u_int8_t intdef; + + iobase = (e_dev->ioconf.slot * EISA_SLOT_SIZE) + + AHB_EISA_SLOT_OFFSET; + + eisa_add_iospace(e_dev, iobase, AHB_EISA_IOSIZE, RESVADDR_NONE); + + intdef = inb(INTDEF + iobase); + switch (intdef & 0x7) { + case INT9: + irq = 9; + break; + case INT10: + irq = 10; + break; + case INT11: + irq = 11; + break; + case INT12: + irq = 12; + break; + case INT14: + irq = 14; + break; + case INT15: + irq = 15; + break; + default: + printf("Adaptec 174X at slot %d: illegal " + "irq setting %d\n", e_dev->ioconf.slot, + (intdef & 0x7)); + irq = 0; + break; + } + if (irq == 0) + continue; + eisa_add_intr(e_dev, irq); + eisa_registerdev(e_dev, &ahb_eisa_driver); + count++; + } + return count; +} + +static int +ahbattach(struct eisa_device *e_dev) +{ + /* + * find unit and check we have that many defined + */ + struct ahb_softc *ahb; + struct ecb* next_ecb; + resvaddr_t *iospace; + u_int irq; + + if (TAILQ_FIRST(&e_dev->ioconf.irqs) == NULL) + return (-1); + + irq = TAILQ_FIRST(&e_dev->ioconf.irqs)->irq_no; + + iospace = e_dev->ioconf.ioaddrs.lh_first; + + if (iospace == NULL) + return (-1); + + eisa_reg_start(e_dev); + if (eisa_reg_iospace(e_dev, iospace)) { + eisa_reg_end(e_dev); + return (-1); + } + + if ((ahb = ahballoc(e_dev->unit, iospace->addr, irq)) == NULL) { + eisa_reg_end(e_dev); + return (-1); + } + + if (ahbreset(ahb) != 0) + return (-1); + + if (eisa_reg_intr(e_dev, irq, ahbintr, (void *)ahb, &cam_imask, + (ahb_inb(ahb, INTDEF) & INTLEVEL) ? TRUE : FALSE)) { + eisa_reg_end(e_dev); + ahbfree(ahb); + return (-1); + } + + /* + * Create our DMA tags. These tags define the kinds of device + * accessable memory allocations and memory mappings we will + * need to perform during normal operation. + */ + /* DMA tag for mapping buffers into device visible space. */ + /* XXX Should be a child of the EISA bus dma tag */ + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/0, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/MAXBSIZE, /*nsegments*/AHB_NSEG, + /*maxsegsz*/BUS_SPACE_MAXSIZE_32BIT, + /*flags*/BUS_DMA_ALLOCNOW, + &ahb->buffer_dmat) != 0) + goto error_exit; + + ahb->init_level++; + + /* DMA tag for our ccb structures and ha inquiry data */ + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/0, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + (AHB_NECB * sizeof(struct ecb)) + + sizeof(*ahb->ha_inq_data), + /*nsegments*/1, + /*maxsegsz*/BUS_SPACE_MAXSIZE_32BIT, + /*flags*/0, &ahb->ecb_dmat) != 0) + goto error_exit; + + ahb->init_level++; + + /* Allocation for our ccbs */ + if (bus_dmamem_alloc(ahb->ecb_dmat, (void **)&ahb->ecb_array, + BUS_DMA_NOWAIT, &ahb->ecb_dmamap) != 0) + goto error_exit; + + ahb->ha_inq_data = (struct ha_inquiry_data *)&ahb->ecb_array[AHB_NECB]; + + ahb->init_level++; + + /* And permanently map them */ + bus_dmamap_load(ahb->ecb_dmat, ahb->ecb_dmamap, + ahb->ecb_array, AHB_NSEG * sizeof(struct ecb), + ahbmapecbs, ahb, /*flags*/0); + + ahb->init_level++; + + /* Allocate the buffer dmamaps for each of our ECBs */ + bzero(ahb->ecb_array, (AHB_NECB * sizeof(struct ecb)) + + sizeof(*ahb->ha_inq_data)); + next_ecb = ahb->ecb_array; + while (ahb->num_ecbs < AHB_NECB) { + u_int32_t ecb_paddr; + + if (bus_dmamap_create(ahb->buffer_dmat, /*flags*/0, + &next_ecb->dmamap)) + break; + ecb_paddr = ahbecbvtop(ahb, next_ecb); + next_ecb->hecb.status_ptr = ahbstatuspaddr(ecb_paddr); + next_ecb->hecb.sense_ptr = ahbsensepaddr(ecb_paddr); + ahb->num_ecbs++; + ahbecbfree(ahb, next_ecb); + next_ecb++; + } + + if (ahb->num_ecbs == 0) + goto error_exit; + + ahb->init_level++; + + eisa_reg_end(e_dev); + + /* + * Now that we know we own the resources we need, register + * our bus with the XPT. + */ + if (ahbxptattach(ahb)) + goto error_exit; + + /* Enable our interrupt */ + eisa_enable_intr(e_dev, irq); + return (0); +error_exit: + /* + * The board's IRQ line will not be left enabled + * if we can't intialize correctly, so its safe + * to release the irq. + */ + eisa_release_intr(e_dev, irq, ahbintr); + ahbfree(ahb); + return (-1); +} + +static struct ahb_softc * +ahballoc(u_long unit, u_int iobase, u_int irq) +{ + struct ahb_softc *ahb; + + /* + * Allocate a storage area for us + */ + ahb = malloc(sizeof(struct ahb_softc), M_TEMP, M_NOWAIT); + if (!ahb) { + printf("ahb%d: cannot malloc!\n", unit); + return (NULL); + } + bzero(ahb, sizeof(struct ahb_softc)); + SLIST_INIT(&ahb->free_ecbs); + LIST_INIT(&ahb->pending_ccbs); + ahb->unit = unit; + ahb->tag = I386_BUS_SPACE_IO; + ahb->bsh = iobase; + ahb->disc_permitted = ~0; + ahb->tags_permitted = ~0; + + return (ahb); +} + +static void +ahbfree(struct ahb_softc *ahb) +{ + switch (ahb->init_level) { + default: + case 4: + bus_dmamap_unload(ahb->ecb_dmat, ahb->ecb_dmamap); + case 3: + bus_dmamem_free(ahb->ecb_dmat, ahb->ecb_array, + ahb->ecb_dmamap); + bus_dmamap_destroy(ahb->ecb_dmat, ahb->ecb_dmamap); + case 2: + bus_dma_tag_destroy(ahb->ecb_dmat); + case 1: + bus_dma_tag_destroy(ahb->buffer_dmat); + case 0: + } + free(ahb, M_DEVBUF); +} + +/* + * reset board, If it doesn't respond, return failure + */ +static int +ahbreset(struct ahb_softc *ahb) +{ + int wait = 1000; /* 1 sec enough? */ + int test; + + if ((ahb_inb(ahb, PORTADDR) & PORTADDR_ENHANCED) == 0) { + printf("ahb_reset: Controller not in enhanced mode\n"); + return (-1); + } + + ahb_outb(ahb, CONTROL, CNTRL_HARD_RST); + DELAY(1000); + ahb_outb(ahb, CONTROL, 0); + while (--wait) { + DELAY(1000); + if ((ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_BUSY) == 0) + break; + } + + if (wait == 0) { + printf("ahbreset: No answer from aha1742 board\n"); + return (-1); + } + if ((test = ahb_inb(ahb, MBOXIN0)) != 0) { + printf("ahb_reset: self test failed, val = 0x%x\n", test); + return (-1); + } + while (ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_INTPEND) { + ahb_outb(ahb, CONTROL, CNTRL_CLRINT); + DELAY(10000); + } + return (0); +} + +static void +ahbmapecbs(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct ahb_softc* ahb; + + ahb = (struct ahb_softc*)arg; + ahb->ecb_physbase = segs->ds_addr; + /* + * Space for adapter inquiry information is on the + * tail of the ecb array. + */ + ahb->ha_inq_physbase = ahbecbvtop(ahb, &ahb->ecb_array[AHB_NECB]); +} + +static int +ahbxptattach(struct ahb_softc *ahb) +{ + struct cam_devq *devq; + struct ecb *ecb; + u_int i; + + /* Remeber who are we on the scsi bus */ + ahb->scsi_id = ahb_inb(ahb, SCSIDEF) & HSCSIID; + + /* Use extended translation?? */ + ahb->extended_trans = ahb_inb(ahb, RESV1) & EXTENDED_TRANS; + + /* Fetch adapter inquiry data */ + ecb = ahbecbget(ahb); /* Always succeeds - no outstanding commands */ + ecb->hecb.opcode = ECBOP_READ_HA_INQDATA; + ecb->hecb.flag_word1 = FW1_SUPPRESS_URUN_ERR|FW1_ERR_STATUS_BLK_ONLY; + ecb->hecb.data_ptr = ahb->ha_inq_physbase; + ecb->hecb.data_len = sizeof(struct ha_inquiry_data); + ecb->hecb.sense_ptr = 0; + ecb->state = ECB_ACTIVE; + + /* Tell the adapter about this command */ + ahbqueuembox(ahb, ahbecbvtop(ahb, ecb), + ATTN_STARTECB|ahb->scsi_id); + + /* Poll for interrupt completion */ + for (i = 1000; ecb->state != ECB_FREE && i != 0; i--) { + ahbintr(ahb); + DELAY(1000); + } + + ahb->num_ecbs = MIN(ahb->num_ecbs, + ahb->ha_inq_data->scsi_data.reserved[1]); + printf("ahb%d: %.8s %s SCSI Adapter, FW Rev. %.4s, ID=%d, %d ECBs\n", + ahb->unit, ahb->ha_inq_data->scsi_data.product, + (ahb->ha_inq_data->scsi_data.flags & 0x4) ? "Differential" + : "Single Ended", + ahb->ha_inq_data->scsi_data.revision, + ahb->scsi_id, ahb->num_ecbs); + + /* Restore sense paddr for future CCB clients */ + ecb->hecb.sense_ptr = ahbsensepaddr(ahbecbvtop(ahb, ecb)); + + ahbecbfree(ahb, ecb); + + /* + * Create the device queue for our SIM. + */ + devq = cam_simq_alloc(ahb->num_ecbs); + if (devq == NULL) + return (ENOMEM); + + /* + * Construct our SIM entry + */ + ahb->sim = cam_sim_alloc(ahbaction, ahbpoll, "ahb", ahb, ahb->unit, + 2, ahb->num_ecbs, devq); + if (ahb->sim == NULL) { + cam_simq_free(devq); + return (ENOMEM); + } + + if (xpt_bus_register(ahb->sim, 0) != CAM_SUCCESS) { + cam_sim_free(ahb->sim, /*free_devq*/TRUE); + return (ENXIO); + } + + if (xpt_create_path(&ahb->path, /*periph*/NULL, + cam_sim_path(ahb->sim), CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + xpt_bus_deregister(cam_sim_path(ahb->sim)); + cam_sim_free(ahb->sim, /*free_devq*/TRUE); + return (ENXIO); + } + + /* + * Allow the board to generate interrupts. + */ + ahb_outb(ahb, INTDEF, ahb_inb(ahb, INTDEF) | INTEN); + + return (0); +} + +static void +ahbhandleimmed(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat) +{ + struct ccb_hdr *ccb_h; + u_int target_id; + + if (ahb->immed_cmd == 0) { + printf("ahb%d: Immediate Command complete with no " + " pending command\n"); + return; + } + + target_id = intstat & INTSTAT_TARGET_MASK; + + ccb_h = LIST_FIRST(&ahb->pending_ccbs); + while (ccb_h != NULL) { + struct ecb *pending_ecb; + union ccb *ccb; + + pending_ecb = (struct ecb *)ccb_h->ccb_ecb_ptr; + ccb = pending_ecb->ccb; + ccb_h = LIST_NEXT(ccb_h, sim_links.le); + if (ccb->ccb_h.target_id == target_id + || target_id == ahb->scsi_id) { + untimeout(ahbtimeout, pending_ecb, + ccb->ccb_h.timeout_ch); + LIST_REMOVE(&ccb->ccb_h, sim_links.le); + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) + bus_dmamap_unload(ahb->buffer_dmat, + pending_ecb->dmamap); + if (pending_ecb == ahb->immed_ecb) + ccb->ccb_h.status = + CAM_CMD_TIMEOUT|CAM_RELEASE_SIMQ; + else if (target_id == ahb->scsi_id) + ccb->ccb_h.status = CAM_SCSI_BUS_RESET; + else + ccb->ccb_h.status = CAM_BDR_SENT; + ahbecbfree(ahb, pending_ecb); + xpt_done(ccb); + } else if (ahb->immed_ecb != NULL) { + /* Re-instate timeout */ + ccb->ccb_h.timeout_ch = + timeout(ahbtimeout, (caddr_t)pending_ecb, + (ccb->ccb_h.timeout * hz) / 1000); + } + } + + if (ahb->immed_ecb != NULL) { + ahb->immed_ecb = NULL; + printf("ahb%d: No longer in timeout\n", ahb->unit); + } else if (target_id == ahb->scsi_id) + printf("ahb%d: SCSI Bus Reset Delivered\n", ahb->unit); + else + printf("ahb%d: Bus Device Reset Delibered to target %d\n", + ahb->unit, target_id); + + ahb->immed_cmd = 0; +} + +static void +ahbcalcresid(struct ahb_softc *ahb, struct ecb *ecb, union ccb *ccb) +{ + if (ecb->status.data_overrun != 0) { + /* + * Overrun Condition. The hardware doesn't + * provide a meaningful byte count in this case + * (the residual is always 0). Tell the XPT + * layer about the error. + */ + ccb->ccb_h.status = CAM_DATA_RUN_ERR; + } else { + ccb->csio.resid = ecb->status.resid_count; + + if ((ecb->hecb.flag_word1 & FW1_SG_ECB) != 0) { + /* + * For S/G transfers, the adapter provides a pointer + * to the address in the last S/G element used and a + * residual for that element. So, we need to sum up + * the elements that follow it in order to get a real + * residual number. If we have an overrun, the residual + * reported will be 0 and we already know that all S/G + * segments have been exhausted, so we can skip this + * step. + */ + ahb_sg_t *sg; + int num_sg; + + num_sg = ecb->hecb.data_len / sizeof(ahb_sg_t); + + /* Find the S/G the adapter was working on */ + for (sg = ecb->sg_list; + num_sg != 0 && sg->addr != ecb->status.resid_addr; + num_sg--, sg++) + ; + + /* Skip it */ + num_sg--; + sg++; + + /* Sum the rest */ + for (; num_sg != 0; num_sg--, sg++) + ccb->csio.resid += sg->len; + } + /* Underruns are not errors */ + ccb->ccb_h.status = CAM_REQ_CMP; + } +} + +static void +ahbprocesserror(struct ahb_softc *ahb, struct ecb *ecb, union ccb *ccb) +{ + struct hardware_ecb *hecb; + struct ecb_status *status; + + hecb = &ecb->hecb; + status = &ecb->status; + switch (status->ha_status) { + case HS_OK: + ccb->csio.scsi_status = status->scsi_status; + if (status->scsi_status != 0) { + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; + if (status->sense_stored) { + ccb->ccb_h.status |= CAM_AUTOSNS_VALID; + ccb->csio.sense_resid = + ccb->csio.sense_len - status->sense_len; + bcopy(&ecb->sense, &ccb->csio.sense_data, + status->sense_len); + } + } + break; + case HS_TARGET_NOT_ASSIGNED: + ccb->ccb_h.status = CAM_PATH_INVALID; + break; + case HS_SEL_TIMEOUT: + ccb->ccb_h.status = CAM_SEL_TIMEOUT; + break; + case HS_DATA_RUN_ERR: + ahbcalcresid(ahb, ecb, ccb); + break; + case HS_UNEXPECTED_BUSFREE: + ccb->ccb_h.status = CAM_UNEXP_BUSFREE; + break; + case HS_INVALID_PHASE: + ccb->ccb_h.status = CAM_SEQUENCE_FAIL; + break; + case HS_REQUEST_SENSE_FAILED: + ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; + break; + case HS_TAG_MSG_REJECTED: + { + struct ccb_trans_settings neg; + + xpt_print_path(ccb->ccb_h.path); + printf("refuses tagged commands. Performing " + "non-tagged I/O\n"); + neg.flags = 0; + neg.valid = CCB_TRANS_TQ_VALID; + xpt_setup_ccb(&neg.ccb_h, ccb->ccb_h.path, /*priority*/1); + xpt_async(AC_TRANSFER_NEG, ccb->ccb_h.path, &neg); + ahb->tags_permitted &= ~(0x01 << ccb->ccb_h.target_id); + ccb->ccb_h.status = CAM_MSG_REJECT_REC; + break; + } + case HS_FIRMWARE_LOAD_REQ: + case HS_HARDWARE_ERR: + /* + * Tell the system that the Adapter + * is no longer functional. + */ + ccb->ccb_h.status = CAM_NO_HBA; + break; + case HS_CMD_ABORTED_HOST: + case HS_CMD_ABORTED_ADAPTER: + case HS_ATN_TARGET_FAILED: + case HS_SCSI_RESET_ADAPTER: + case HS_SCSI_RESET_INCOMING: + ccb->ccb_h.status = CAM_SCSI_BUS_RESET; + break; + case HS_DUP_TCB_RECEIVED: + case HS_INVALID_OPCODE: + case HS_INVALID_CMD_LINK: + case HS_INVALID_ECB_PARAM: + case HS_PROGRAM_CKSUM_ERROR: + panic("ahb%d: Can't happen host status %x occurred", + ahb->unit, status->ha_status); + break; + } + if (ccb->ccb_h.status != CAM_REQ_CMP) { + xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); + ccb->ccb_h.status |= CAM_DEV_QFRZN; + } +} + +static void +ahbdone(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat) +{ + struct ecb *ecb; + union ccb *ccb; + + ecb = ahbecbptov(ahb, mbox); + + if ((ecb->state & ECB_ACTIVE) == 0) + panic("ecb not active"); + + ccb = ecb->ccb; + + if (ccb != NULL) { + untimeout(ahbtimeout, ecb, ccb->ccb_h.timeout_ch); + LIST_REMOVE(&ccb->ccb_h, sim_links.le); + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + bus_dmasync_op_t op; + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) + op = BUS_DMASYNC_POSTREAD; + else + op = BUS_DMASYNC_POSTWRITE; + bus_dmamap_sync(ahb->buffer_dmat, ecb->dmamap, op); + bus_dmamap_unload(ahb->buffer_dmat, ecb->dmamap); + } + + if ((intstat & INTSTAT_MASK) == INTSTAT_ECB_OK) { + ccb->ccb_h.status = CAM_REQ_CMP; + ccb->csio.resid = 0; + } else { + ahbprocesserror(ahb, ecb, ccb); + } + ahbecbfree(ahb, ecb); + xpt_done(ccb); + } else { + /* Non CCB Command */ + if ((intstat & INTSTAT_MASK) != INTSTAT_ECB_OK) { + printf("ahb%d: Command 0%x Failed %x:%x:%x\n", + ahb->unit, ecb->hecb.opcode, + *((u_int16_t*)&ecb->status), + ecb->status.ha_status, ecb->status.resid_count); + } + /* Client owns this ECB and will release it. */ + } +} + +/* + * Catch an interrupt from the adaptor + */ +static void +ahbintr(void *arg) +{ + struct ahb_softc *ahb; + u_int intstat; + u_int32_t mbox; + + ahb = (struct ahb_softc *)arg; + + while (ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_INTPEND) { + /* + * Fetch information about this interrupt. + */ + intstat = ahb_inb(ahb, INTSTAT); + mbox = ahb_inl(ahb, MBOXIN0); + + /* + * Reset interrupt latch. + */ + ahb_outb(ahb, CONTROL, CNTRL_CLRINT); + + /* + * Process the completed operation + */ + switch (intstat & INTSTAT_MASK) { + case INTSTAT_ECB_OK: + case INTSTAT_ECB_CMPWRETRY: + case INTSTAT_ECB_CMPWERR: + ahbdone(ahb, mbox, intstat); + break; + case INTSTAT_AEN_OCCURED: + if ((intstat & INTSTAT_TARGET_MASK) == ahb->scsi_id) { + /* Bus Reset */ + xpt_print_path(ahb->path); + switch (mbox) { + case HS_SCSI_RESET_ADAPTER: + printf("Host Adapter Initiated " + "Bus Reset occurred\n"); + break; + case HS_SCSI_RESET_INCOMING: + printf("Bus Reset Initiated " + "by another device occurred\n"); + break; + } + /* Notify the XPT */ + xpt_async(AC_BUS_RESET, ahb->path, NULL); + break; + } + printf("Unsupported initiator selection AEN occured\n"); + break; + case INTSTAT_IMMED_OK: + case INTSTAT_IMMED_ERR: + ahbhandleimmed(ahb, mbox, intstat); + break; + case INTSTAT_HW_ERR: + panic("Unrecoverable hardware Error Occurred\n"); + } + } +} + +static void +ahbexecuteecb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) +{ + struct ecb *ecb; + union ccb *ccb; + struct ahb_softc *ahb; + u_int32_t ecb_paddr; + int s; + + ecb = (struct ecb *)arg; + ccb = ecb->ccb; + ahb = (struct ahb_softc *)ccb->ccb_h.ccb_ahb_ptr; + + if (error != 0) { + if (error != EFBIG) + printf("ahb%d: Unexepected error 0x%x returned from " + "bus_dmamap_load\n", ahb->unit); + if (ccb->ccb_h.status == CAM_REQ_INPROG) { + xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); + ccb->ccb_h.status = CAM_REQ_TOO_BIG|CAM_DEV_QFRZN; + } + ahbecbfree(ahb, ecb); + xpt_done(ccb); + return; + } + + ecb_paddr = ahbecbvtop(ahb, ecb); + + if (nseg != 0) { + ahb_sg_t *sg; + bus_dma_segment_t *end_seg; + bus_dmasync_op_t op; + + end_seg = dm_segs + nseg; + + /* Copy the segments into our SG list */ + sg = ecb->sg_list; + while (dm_segs < end_seg) { + sg->addr = dm_segs->ds_addr; + sg->len = dm_segs->ds_len; + sg++; + dm_segs++; + } + + if (nseg > 1) { + ecb->hecb.flag_word1 |= FW1_SG_ECB; + ecb->hecb.data_ptr = ahbsgpaddr(ecb_paddr); + ecb->hecb.data_len = sizeof(ahb_sg_t) * nseg; + } else { + ecb->hecb.data_ptr = ecb->sg_list->addr; + ecb->hecb.data_len = ecb->sg_list->len; + } + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { +/* ecb->hecb.flag_word2 |= FW2_DATA_DIR_IN; */ + op = BUS_DMASYNC_PREREAD; + } else { + op = BUS_DMASYNC_PREWRITE; + } + /* ecb->hecb.flag_word2 |= FW2_CHECK_DATA_DIR; */ + + bus_dmamap_sync(ahb->buffer_dmat, ecb->dmamap, op); + + } else { + ecb->hecb.data_ptr = 0; + ecb->hecb.data_len = 0; + } + + s = splcam(); + + /* + * Last time we need to check if this CCB needs to + * be aborted. + */ + if (ccb->ccb_h.status != CAM_REQ_INPROG) { + if (nseg != 0) + bus_dmamap_unload(ahb->buffer_dmat, ecb->dmamap); + ahbecbfree(ahb, ecb); + xpt_done(ccb); + splx(s); + return; + } + + ecb->state = ECB_ACTIVE; + ccb->ccb_h.status |= CAM_SIM_QUEUED; + LIST_INSERT_HEAD(&ahb->pending_ccbs, &ccb->ccb_h, sim_links.le); + + /* Tell the adapter about this command */ + ahbqueuembox(ahb, ecb_paddr, ATTN_STARTECB|ccb->ccb_h.target_id); + + ccb->ccb_h.timeout_ch = timeout(ahbtimeout, (caddr_t)ecb, + (ccb->ccb_h.timeout * hz) / 1000); + splx(s); +} + +static void +ahbaction(struct cam_sim *sim, union ccb *ccb) +{ + struct ahb_softc *ahb; + + CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ahbaction\n")); + + ahb = (struct ahb_softc *)cam_sim_softc(sim); + + switch (ccb->ccb_h.func_code) { + /* Common cases first */ + case XPT_SCSI_IO: /* Execute the requested I/O operation */ + { + struct ecb *ecb; + struct hardware_ecb *hecb; + + /* + * get an ecb to use. + */ + if ((ecb = ahbecbget(ahb)) == NULL) { + /* Should never occur */ + panic("Failed to get an ecb"); + } + + /* + * So we can find the ECB when an abort is requested + */ + ecb->ccb = ccb; + ccb->ccb_h.ccb_ecb_ptr = ecb; + ccb->ccb_h.ccb_ahb_ptr = ahb; + + /* + * Put all the arguments for the xfer in the ecb + */ + hecb = &ecb->hecb; + hecb->opcode = ECBOP_INITIATOR_SCSI_CMD; + hecb->flag_word1 = FW1_AUTO_REQUEST_SENSE + | FW1_ERR_STATUS_BLK_ONLY; + hecb->flag_word2 = ccb->ccb_h.target_lun + | FW2_NO_RETRY_ON_BUSY; + if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) { + hecb->flag_word2 |= FW2_TAG_ENB + | ((ccb->csio.tag_action & 0x3) + << FW2_TAG_TYPE_SHIFT); + } + if ((ccb->ccb_h.flags & CAM_DIS_DISCONNECT) != 0) + hecb->flag_word2 |= FW2_DISABLE_DISC; + hecb->sense_len = ccb->csio.sense_len; + hecb->cdb_len = ccb->csio.cdb_len; + if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { + if ((ccb->ccb_h.flags & CAM_CDB_PHYS) == 0) { + bcopy(ccb->csio.cdb_io.cdb_ptr, + hecb->cdb, hecb->cdb_len); + } else { + /* I guess I could map it in... */ + ccb->ccb_h.status = CAM_REQ_INVALID; + ahbecbfree(ahb, ecb); + xpt_done(ccb); + return; + } + } else { + bcopy(ccb->csio.cdb_io.cdb_bytes, + hecb->cdb, hecb->cdb_len); + } + + /* + * If we have any data to send with this command, + * map it into bus space. + */ + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + if ((ccb->ccb_h.flags & CAM_SCATTER_VALID) == 0) { + /* + * We've been given a pointer + * to a single buffer. + */ + if ((ccb->ccb_h.flags & CAM_DATA_PHYS)==0) { + int s; + int error; + + s = splsoftvm(); + error = bus_dmamap_load( + ahb->buffer_dmat, + ecb->dmamap, + ccb->csio.data_ptr, + ccb->csio.dxfer_len, + ahbexecuteecb, + ecb, /*flags*/0); + if (error == EINPROGRESS) { + /* + * So as to maintain ordering, + * freeze the controller queue + * until our mapping is + * returned. + */ + xpt_freeze_simq(ahb->sim, 1); + ccb->ccb_h.status |= + CAM_RELEASE_SIMQ; + } + splx(s); + } else { + struct bus_dma_segment seg; + + /* Pointer to physical buffer */ + seg.ds_addr = + (bus_addr_t)ccb->csio.data_ptr; + seg.ds_len = ccb->csio.dxfer_len; + ahbexecuteecb(ecb, &seg, 1, 0); + } + } else { + struct bus_dma_segment *segs; + + if ((ccb->ccb_h.flags & CAM_DATA_PHYS) != 0) + panic("ahbaction - Physical segment " + "pointers unsupported"); + + if ((ccb->ccb_h.flags & CAM_SG_LIST_PHYS) == 0) + panic("btaction - Virtual segment " + "addresses unsupported"); + + /* Just use the segments provided */ + segs = (struct bus_dma_segment *) + ccb->csio.data_ptr; + ahbexecuteecb(ecb, segs, ccb->csio.sglist_cnt, + 0); + } + } else { + ahbexecuteecb(ecb, NULL, 0, 0); + } + break; + } + case XPT_EN_LUN: /* Enable LUN as a target */ + case XPT_TARGET_IO: /* Execute target I/O request */ + case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */ + case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/ + case XPT_ABORT: /* Abort the specified CCB */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_SET_TRAN_SETTINGS: + { + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + /* Get default/user set transfer settings for the target */ + { + struct ccb_trans_settings *cts; + u_int target_mask; + + cts = &ccb->cts; + target_mask = 0x01 << ccb->ccb_h.target_id; + if ((cts->flags & CCB_TRANS_USER_SETTINGS) != 0) { + cts->flags = 0; + if ((ahb->disc_permitted & target_mask) != 0) + cts->flags |= CCB_TRANS_DISC_ENB; + if ((ahb->tags_permitted & target_mask) != 0) + cts->flags |= CCB_TRANS_TAG_ENB; + cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT; + cts->sync_period = 25; /* 10MHz */ + + if (cts->sync_period != 0) + cts->sync_offset = 15; + + cts->valid = CCB_TRANS_SYNC_RATE_VALID + | CCB_TRANS_SYNC_OFFSET_VALID + | CCB_TRANS_BUS_WIDTH_VALID + | CCB_TRANS_DISC_VALID + | CCB_TRANS_TQ_VALID; + ccb->ccb_h.status = CAM_REQ_CMP; + } else { + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + } + xpt_done(ccb); + break; + } + case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ + { + int i; + + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ccb->ccb_h.target_id); + /* Poll for interrupt completion */ + for (i = 1000; ahb->immed_cmd != 0 && i != 0; i--) + DELAY(1000); + break; + } + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + + if (size_mb > 1024 && (ahb->extended_trans != 0)) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_BUS: /* Reset the specified SCSI bus */ + { + int i; + + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ahb->scsi_id); + /* Poll for interrupt completion */ + for (i = 1000; ahb->immed_cmd != 0 && i != 0; i--) + DELAY(1000); + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_TERM_IO: /* Terminate the I/O process */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_PATH_INQ: /* Path routing inquiry */ + { + struct ccb_pathinq *cpi = &ccb->cpi; + + cpi->version_num = 1; /* XXX??? */ + cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE; + cpi->target_sprt = 0; + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = 7; + cpi->max_lun = 7; + cpi->initiator_id = ahb->scsi_id; + cpi->bus_id = cam_sim_bus(sim); + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "Adaptec", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } +#if 0 + /* Need these??? */ + case XPT_IMMED_NOTIFY: /* Notify Host Target driver of event */ + case XPT_NOTIFY_ACK: /* Acknowledgement of event */ +#endif + default: + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +} + +static void +ahbpoll(struct cam_sim *sim) +{ +} + +void +ahbtimeout(void *arg) +{ + struct ecb *ecb; + union ccb *ccb; + struct ahb_softc *ahb; + int s; + + ecb = (struct ecb *)arg; + ccb = ecb->ccb; + ahb = (struct ahb_softc *)ccb->ccb_h.ccb_ahb_ptr; + xpt_print_path(ccb->ccb_h.path); + printf("ECB 0x%x - timed out\n", ecb); + + s = splcam(); + + if ((ecb->state & ECB_ACTIVE) == 0) { + xpt_print_path(ccb->ccb_h.path); + printf("ECB 0x%x - timed out ECB already completed\n", ecb); + splx(s); + return; + } + /* + * In order to simplify the recovery process, we ask the XPT + * layer to halt the queue of new transactions and we traverse + * the list of pending CCBs and remove their timeouts. This + * means that the driver attempts to clear only one error + * condition at a time. In general, timeouts that occur + * close together are related anyway, so there is no benefit + * in attempting to handle errors in parrallel. Timeouts will + * be reinstated when the recovery process ends. + */ + if ((ecb->state & ECB_DEVICE_RESET) == 0) { + struct ccb_hdr *ccb_h; + + if ((ecb->state & ECB_RELEASE_SIMQ) == 0) { + xpt_freeze_simq(ahb->sim, /*count*/1); + ecb->state |= ECB_RELEASE_SIMQ; + } + + ccb_h = LIST_FIRST(&ahb->pending_ccbs); + while (ccb_h != NULL) { + struct ecb *pending_ecb; + + pending_ecb = (struct ecb *)ccb_h->ccb_ecb_ptr; + untimeout(ahbtimeout, pending_ecb, ccb_h->timeout_ch); + ccb_h = LIST_NEXT(ccb_h, sim_links.le); + } + + /* Store for our interrupt handler */ + ahb->immed_ecb = ecb; + + /* + * Send a Bus Device Reset message: + * The target that is holding up the bus may not + * be the same as the one that triggered this timeout + * (different commands have different timeout lengths), + * but we have no way of determining this from our + * timeout handler. Our strategy here is to queue a + * BDR message to the target of the timed out command. + * If this fails, we'll get another timeout 2 seconds + * later which will attempt a bus reset. + */ + xpt_print_path(ccb->ccb_h.path); + printf("Queuing BDR\n", ecb); + ecb->state |= ECB_DEVICE_RESET; + ccb->ccb_h.timeout_ch = + timeout(ahbtimeout, (caddr_t)ecb, 2 * hz); + + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ccb->ccb_h.target_id); + } else if ((ecb->state & ECB_SCSIBUS_RESET) != 0) { + /* + * Try a SCSI bus reset. We do this only if we + * have already attempted to clear the condition with a BDR. + */ + xpt_print_path(ccb->ccb_h.path); + printf("Attempting SCSI Bus reset\n", ecb); + ecb->state |= ECB_SCSIBUS_RESET; + ccb->ccb_h.timeout_ch = + timeout(ahbtimeout, (caddr_t)ecb, 2 * hz); + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ahb->scsi_id); + } else { + /* Bring out the hammer... */ + ahbreset(ahb); + + /* Simulate the reset complete interrupt */ + ahbhandleimmed(ahb, 0, ahb->scsi_id|INTSTAT_IMMED_OK); + } + + splx(s); +} + +#endif /* NEISA */ diff --git a/sys/dev/ahb/ahbreg.h b/sys/dev/ahb/ahbreg.h new file mode 100644 index 00000000000..1bed89ebff4 --- /dev/null +++ b/sys/dev/ahb/ahbreg.h @@ -0,0 +1,283 @@ +/* + * Hardware structure definitions for the Adaptec 174X CAM SCSI device driver. + * + * Copyright (c) 1998 Justin T. Gibbs + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +/* Resource Constatns */ +#define AHB_NECB 64 +#define AHB_NSEG 32 + +/* AHA1740 EISA ID, IO port range size, and offset from slot base */ +#define EISA_DEVICE_ID_ADAPTEC_1740 0x04900000 +#define AHB_EISA_IOSIZE 0x100 +#define AHB_EISA_SLOT_OFFSET 0xc00 + +/* AHA1740 EISA board control registers (Offset from slot base) */ +#define EBCTRL 0x084 +#define CDEN 0x01 + +/* + * AHA1740 EISA board mode registers (Offset from slot base) + */ +#define PORTADDR 0x0C0 +#define PORTADDR_ENHANCED 0x80 + +#define BIOSADDR 0x0C1 + +#define INTDEF 0x0C2 +#define INT9 0x00 +#define INT10 0x01 +#define INT11 0x02 +#define INT12 0x03 +#define INT14 0x05 +#define INT15 0x06 +#define INTLEVEL 0x08 +#define INTEN 0x10 + +#define SCSIDEF 0x0C3 +#define HSCSIID 0x0F /* our SCSI ID */ +#define RSTBUS 0x10 + +#define BUSDEF 0x0C4 +#define B0uS 0x00 /* give up bus immediatly */ +#define B4uS 0x01 /* delay 4uSec. */ +#define B8uS 0x02 /* delay 8uSec. */ + +#define RESV0 0x0C5 + +#define RESV1 0x0C6 +#define EXTENDED_TRANS 0x01 + +#define RESV2 0x0C7 + +/* + * AHA1740 ENHANCED mode mailbox control regs (Offset from slot base) + */ +#define MBOXOUT0 0x0D0 +#define MBOXOUT1 0x0D1 +#define MBOXOUT2 0x0D2 +#define MBOXOUT3 0x0D3 + +#define ATTN 0x0D4 +#define ATTN_TARGMASK 0x0F +#define ATTN_IMMED 0x10 +#define ATTN_STARTECB 0x40 +#define ATTN_ABORTECB 0x50 +#define ATTN_TARG_RESET 0x80 + +#define CONTROL 0x0D5 +#define CNTRL_SET_HRDY 0x20 +#define CNTRL_CLRINT 0x40 +#define CNTRL_HARD_RST 0x80 + +#define INTSTAT 0x0D6 +#define INTSTAT_TARGET_MASK 0x0F +#define INTSTAT_MASK 0xF0 +#define INTSTAT_ECB_OK 0x10 /* ECB Completed w/out error */ +#define INTSTAT_ECB_CMPWRETRY 0x50 /* ECB Completed w/retries */ +#define INTSTAT_HW_ERR 0x70 /* Adapter Hardware Failure */ +#define INTSTAT_IMMED_OK 0xA0 /* Immediate command complete */ +#define INTSTAT_ECB_CMPWERR 0xC0 /* ECB Completed w/error */ +#define INTSTAT_AEN_OCCURED 0xD0 /* Async Event Notification */ +#define INTSTAT_IMMED_ERR 0xE0 /* Immediate command failed */ + +#define HOSTSTAT 0x0D7 +#define HOSTSTAT_MBOX_EMPTY 0x04 +#define HOSTSTAT_INTPEND 0x02 +#define HOSTSTAT_BUSY 0x01 + + +#define MBOXIN0 0x0D8 +#define MBOXIN1 0x0D9 +#define MBOXIN2 0x0DA +#define MBOXIN3 0x0DB + +#define STATUS2 0x0DC +#define STATUS2_HOST_READY 0x01 + +typedef enum { + IMMED_RESET = 0x000080, + IMMED_DEVICE_CLEAR_QUEUE = 0x000480, + IMMED_ADAPTER_CLEAR_QUEUE = 0x000880, + IMMED_RESUME = 0x200090 +} immed_cmd; + +struct ecb_status { + /* Status Flags */ + u_int16_t no_error :1, /* Completed with no error */ + data_underrun :1, + :1, + ha_queue_full :1, + spec_check :1, + data_overrun :1, + chain_halted :1, + intr_issued :1, + status_avail :1, /* status bytes 14-31 are valid */ + sense_stored :1, + :1, + init_requied :1, + major_error :1, + :1, + extended_ca :1, + :1; + /* Host Status */ + u_int8_t ha_status; + u_int8_t scsi_status; + int32_t resid_count; + u_int32_t resid_addr; + u_int16_t addit_status; + u_int8_t sense_len; + u_int8_t unused[9]; + u_int8_t cdb[6]; +}; + +typedef enum { + HS_OK = 0x00, + HS_CMD_ABORTED_HOST = 0x04, + HS_CMD_ABORTED_ADAPTER = 0x05, + HS_FIRMWARE_LOAD_REQ = 0x08, + HS_TARGET_NOT_ASSIGNED = 0x0A, + HS_SEL_TIMEOUT = 0x11, + HS_DATA_RUN_ERR = 0x12, + HS_UNEXPECTED_BUSFREE = 0x13, + HS_INVALID_PHASE = 0x14, + HS_INVALID_OPCODE = 0x16, + HS_INVALID_CMD_LINK = 0x17, + HS_INVALID_ECB_PARAM = 0x18, + HS_DUP_TCB_RECEIVED = 0x19, + HS_REQUEST_SENSE_FAILED = 0x1A, + HS_TAG_MSG_REJECTED = 0x1C, + HS_HARDWARE_ERR = 0x20, + HS_ATN_TARGET_FAILED = 0x21, + HS_SCSI_RESET_ADAPTER = 0x22, + HS_SCSI_RESET_INCOMING = 0x23, + HS_PROGRAM_CKSUM_ERROR = 0x80 +} host_status; + +typedef enum { + ECBOP_NOP = 0x00, + ECBOP_INITIATOR_SCSI_CMD = 0x01, + ECBOP_RUN_DIAGNOSTICS = 0x05, + ECBOP_INITIALIZE_SCSI = 0x06, /* Set syncrate/disc/parity */ + ECBOP_READ_SENSE = 0x08, + ECBOP_DOWNLOAD_FIRMWARE = 0x09, + ECBOP_READ_HA_INQDATA = 0x0a, + ECBOP_TARGET_SCSI_CMD = 0x10 +} ecb_op; + +struct ha_inquiry_data { + struct scsi_inquiry_data scsi_data; + u_int8_t release_date[8]; + u_int8_t release_time[8]; + u_int16_t firmware_cksum; + u_int16_t reserved; + u_int16_t target_data[16]; +}; + +struct hardware_ecb { + u_int16_t opcode; + u_int16_t flag_word1; +#define FW1_LINKED_CMD 0x0001 +#define FW1_DISABLE_INTR 0x0080 +#define FW1_SUPPRESS_URUN_ERR 0x0400 +#define FW1_SG_ECB 0x1000 +#define FW1_ERR_STATUS_BLK_ONLY 0x4000 +#define FW1_AUTO_REQUEST_SENSE 0x8000 + u_int16_t flag_word2; +#define FW2_LUN_MASK 0x0007 +#define FW2_TAG_ENB 0x0008 +#define FW2_TAG_TYPE 0x0030 +#define FW2_TAG_TYPE_SHIFT 4 +#define FW2_DISABLE_DISC 0x0040 +#define FW2_CHECK_DATA_DIR 0x0100 +#define FW2_DATA_DIR_IN 0x0200 +#define FW2_SUPRESS_TRANSFER 0x0400 +#define FW2_CALC_CKSUM 0x0800 +#define FW2_RECOVERY_ECB 0x4000 +#define FW2_NO_RETRY_ON_BUSY 0x8000 + u_int16_t reserved; + u_int32_t data_ptr; + u_int32_t data_len; + u_int32_t status_ptr; + u_int32_t link_ptr; + u_int32_t reserved2; + u_int32_t sense_ptr; + u_int8_t sense_len; + u_int8_t cdb_len; + u_int16_t cksum; + u_int8_t cdb[12]; +}; + +typedef struct { + u_int32_t addr; + u_int32_t len; +} ahb_sg_t; + +typedef enum { + ECB_FREE = 0x0, + ECB_ACTIVE = 0x1, + ECB_DEVICE_RESET = 0x2, + ECB_SCSIBUS_RESET = 0x4, + ECB_RELEASE_SIMQ = 0x8 +} ecb_state; + +struct ecb { + struct hardware_ecb hecb; + struct ecb_status status; + struct scsi_sense_data sense; + ahb_sg_t sg_list[AHB_NSEG]; + SLIST_ENTRY(ecb) links; + ecb_state state; + union ccb *ccb; + bus_dmamap_t dmamap; +}; + +struct ahb_softc { + bus_space_tag_t tag; + bus_space_handle_t bsh; + struct cam_sim *sim; + struct cam_path *path; + SLIST_HEAD(,ecb) free_ecbs; + LIST_HEAD(,ccb_hdr) pending_ccbs; + struct ecb *ecb_array; + u_int32_t ecb_physbase; + bus_dma_tag_t buffer_dmat; /* dmat for buffer I/O */ + bus_dma_tag_t ecb_dmat; /* dmat for our ecb array */ + bus_dmamap_t ecb_dmamap; + volatile u_int32_t immed_cmd; + struct ecb *immed_ecb; + struct ha_inquiry_data *ha_inq_data; + u_int32_t ha_inq_physbase; + u_long unit; + u_int init_level; + u_int scsi_id; + u_int num_ecbs; + u_int extended_trans; + u_int8_t disc_permitted; + u_int8_t tags_permitted; +}; diff --git a/sys/i386/eisa/ahb.c b/sys/i386/eisa/ahb.c new file mode 100644 index 00000000000..1993477e2de --- /dev/null +++ b/sys/i386/eisa/ahb.c @@ -0,0 +1,1344 @@ +/* + * CAM SCSI device driver for the Adaptec 174X SCSI Host adapter + * + * Copyright (c) 1998 Justin T. Gibbs + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "eisa.h" +#if NEISA > 0 +#include /* For offsetof() */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#define ccb_ecb_ptr spriv_ptr0 +#define ccb_ahb_ptr spriv_ptr1 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define ahb_inb(ahb, port) \ + bus_space_read_1((ahb)->tag, (ahb)->bsh, port) + +#define ahb_inl(ahb, port) \ + bus_space_read_4((ahb)->tag, (ahb)->bsh, port) + +#define ahb_outb(ahb, port, value) \ + bus_space_write_1((ahb)->tag, (ahb)->bsh, port, value) + +#define ahb_outl(ahb, port, value) \ + bus_space_write_4((ahb)->tag, (ahb)->bsh, port, value) + +static const char *ahbmatch(eisa_id_t type); +static int ahbprobe(void); +static int ahbattach(struct eisa_device *dev); +static struct ahb_softc *ahballoc(u_long unit, u_int iobase, u_int irq); +static void ahbfree(struct ahb_softc *ahb); +static int ahbreset(struct ahb_softc *ahb); +static void ahbmapecbs(void *arg, bus_dma_segment_t *segs, + int nseg, int error); +static int ahbxptattach(struct ahb_softc *ahb); +static void ahbhandleimmed(struct ahb_softc *ahb, + u_int32_t mbox, u_int intstat); +static void ahbcalcresid(struct ahb_softc *ahb, + struct ecb *ecb, union ccb *ccb); +static __inline void ahbdone(struct ahb_softc *ahb, u_int32_t mbox, + u_int intstat); +static void ahbintr(void *arg); +static bus_dmamap_callback_t ahbexecuteecb; +static void ahbaction(struct cam_sim *sim, union ccb *ccb); +static void ahbpoll(struct cam_sim *sim); + +/* Our timeout handler */ +timeout_t ahbtimeout; + +static __inline struct ecb* ahbecbget(struct ahb_softc *ahb); +static __inline void ahbecbfree(struct ahb_softc* ahb, + struct ecb* ecb); +static __inline u_int32_t ahbecbvtop(struct ahb_softc *ahb, + struct ecb *ecb); +static __inline struct ecb* ahbecbptov(struct ahb_softc *ahb, + u_int32_t ecb_addr); +static __inline u_int32_t ahbstatuspaddr(u_int32_t ecb_paddr); +static __inline u_int32_t ahbsensepaddr(u_int32_t ecb_paddr); +static __inline u_int32_t ahbsgpaddr(u_int32_t ecb_paddr); +static __inline void ahbqueuembox(struct ahb_softc *ahb, + u_int32_t mboxval, + u_int attn_code); + +static __inline struct ecb* +ahbecbget(struct ahb_softc *ahb) +{ + struct ecb* ecb; + int s; + + s = splcam(); + if ((ecb = SLIST_FIRST(&ahb->free_ecbs)) != NULL) + SLIST_REMOVE_HEAD(&ahb->free_ecbs, links); + splx(s); + + return (ecb); +} + +static __inline void +ahbecbfree(struct ahb_softc* ahb, struct ecb* ecb) +{ + int s; + + s = splcam(); + ecb->state = ECB_FREE; + SLIST_INSERT_HEAD(&ahb->free_ecbs, ecb, links); + splx(s); +} + +static __inline u_int32_t +ahbecbvtop(struct ahb_softc *ahb, struct ecb *ecb) +{ + return (ahb->ecb_physbase + + (u_int32_t)((caddr_t)ecb - (caddr_t)ahb->ecb_array)); +} + +static __inline struct ecb* +ahbecbptov(struct ahb_softc *ahb, u_int32_t ecb_addr) +{ + return (ahb->ecb_array + + ((struct ecb*)ecb_addr - (struct ecb*)ahb->ecb_physbase)); +} + +static __inline u_int32_t +ahbstatuspaddr(u_int32_t ecb_paddr) +{ + return (ecb_paddr + offsetof(struct ecb, status)); +} + +static __inline u_int32_t +ahbsensepaddr(u_int32_t ecb_paddr) +{ + return (ecb_paddr + offsetof(struct ecb, sense)); +} + +static __inline u_int32_t +ahbsgpaddr(u_int32_t ecb_paddr) +{ + return (ecb_paddr + offsetof(struct ecb, sg_list)); +} + +static __inline void +ahbqueuembox(struct ahb_softc *ahb, u_int32_t mboxval, u_int attn_code) +{ + u_int loopmax = 300; + while (--loopmax) { + u_int status; + + status = ahb_inb(ahb, HOSTSTAT); + if ((status & HOSTSTAT_MBOX_EMPTY|HOSTSTAT_BUSY) + != HOSTSTAT_MBOX_EMPTY) + break; + DELAY(20); + } + if (loopmax == 0) + panic("ahb%d: adapter not taking commands\n", ahb->unit); + + ahb_outl(ahb, MBOXOUT0, mboxval); + ahb_outb(ahb, ATTN, attn_code); +} + +static u_long ahbunit; + +static struct eisa_driver ahb_eisa_driver = +{ + "ahb", + ahbprobe, + ahbattach, + /*shutdown*/NULL, + &ahbunit +}; + +DATA_SET (eisadriver_set, ahb_eisa_driver); + +static const char * +ahbmatch(eisa_id_t type) +{ + switch(type & 0xfffffe00) { + case EISA_DEVICE_ID_ADAPTEC_1740: + return ("Adaptec 174x SCSI host adapter"); + break; + default: + break; + } + return (NULL); +} + +static int +ahbprobe(void) +{ + struct eisa_device *e_dev = NULL; + u_int32_t iobase; + u_int32_t irq; + int count; + + count = 0; + while ((e_dev = eisa_match_dev(e_dev, ahbmatch))) { + u_int8_t intdef; + + iobase = (e_dev->ioconf.slot * EISA_SLOT_SIZE) + + AHB_EISA_SLOT_OFFSET; + + eisa_add_iospace(e_dev, iobase, AHB_EISA_IOSIZE, RESVADDR_NONE); + + intdef = inb(INTDEF + iobase); + switch (intdef & 0x7) { + case INT9: + irq = 9; + break; + case INT10: + irq = 10; + break; + case INT11: + irq = 11; + break; + case INT12: + irq = 12; + break; + case INT14: + irq = 14; + break; + case INT15: + irq = 15; + break; + default: + printf("Adaptec 174X at slot %d: illegal " + "irq setting %d\n", e_dev->ioconf.slot, + (intdef & 0x7)); + irq = 0; + break; + } + if (irq == 0) + continue; + eisa_add_intr(e_dev, irq); + eisa_registerdev(e_dev, &ahb_eisa_driver); + count++; + } + return count; +} + +static int +ahbattach(struct eisa_device *e_dev) +{ + /* + * find unit and check we have that many defined + */ + struct ahb_softc *ahb; + struct ecb* next_ecb; + resvaddr_t *iospace; + u_int irq; + + if (TAILQ_FIRST(&e_dev->ioconf.irqs) == NULL) + return (-1); + + irq = TAILQ_FIRST(&e_dev->ioconf.irqs)->irq_no; + + iospace = e_dev->ioconf.ioaddrs.lh_first; + + if (iospace == NULL) + return (-1); + + eisa_reg_start(e_dev); + if (eisa_reg_iospace(e_dev, iospace)) { + eisa_reg_end(e_dev); + return (-1); + } + + if ((ahb = ahballoc(e_dev->unit, iospace->addr, irq)) == NULL) { + eisa_reg_end(e_dev); + return (-1); + } + + if (ahbreset(ahb) != 0) + return (-1); + + if (eisa_reg_intr(e_dev, irq, ahbintr, (void *)ahb, &cam_imask, + (ahb_inb(ahb, INTDEF) & INTLEVEL) ? TRUE : FALSE)) { + eisa_reg_end(e_dev); + ahbfree(ahb); + return (-1); + } + + /* + * Create our DMA tags. These tags define the kinds of device + * accessable memory allocations and memory mappings we will + * need to perform during normal operation. + */ + /* DMA tag for mapping buffers into device visible space. */ + /* XXX Should be a child of the EISA bus dma tag */ + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/0, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/MAXBSIZE, /*nsegments*/AHB_NSEG, + /*maxsegsz*/BUS_SPACE_MAXSIZE_32BIT, + /*flags*/BUS_DMA_ALLOCNOW, + &ahb->buffer_dmat) != 0) + goto error_exit; + + ahb->init_level++; + + /* DMA tag for our ccb structures and ha inquiry data */ + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/0, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + (AHB_NECB * sizeof(struct ecb)) + + sizeof(*ahb->ha_inq_data), + /*nsegments*/1, + /*maxsegsz*/BUS_SPACE_MAXSIZE_32BIT, + /*flags*/0, &ahb->ecb_dmat) != 0) + goto error_exit; + + ahb->init_level++; + + /* Allocation for our ccbs */ + if (bus_dmamem_alloc(ahb->ecb_dmat, (void **)&ahb->ecb_array, + BUS_DMA_NOWAIT, &ahb->ecb_dmamap) != 0) + goto error_exit; + + ahb->ha_inq_data = (struct ha_inquiry_data *)&ahb->ecb_array[AHB_NECB]; + + ahb->init_level++; + + /* And permanently map them */ + bus_dmamap_load(ahb->ecb_dmat, ahb->ecb_dmamap, + ahb->ecb_array, AHB_NSEG * sizeof(struct ecb), + ahbmapecbs, ahb, /*flags*/0); + + ahb->init_level++; + + /* Allocate the buffer dmamaps for each of our ECBs */ + bzero(ahb->ecb_array, (AHB_NECB * sizeof(struct ecb)) + + sizeof(*ahb->ha_inq_data)); + next_ecb = ahb->ecb_array; + while (ahb->num_ecbs < AHB_NECB) { + u_int32_t ecb_paddr; + + if (bus_dmamap_create(ahb->buffer_dmat, /*flags*/0, + &next_ecb->dmamap)) + break; + ecb_paddr = ahbecbvtop(ahb, next_ecb); + next_ecb->hecb.status_ptr = ahbstatuspaddr(ecb_paddr); + next_ecb->hecb.sense_ptr = ahbsensepaddr(ecb_paddr); + ahb->num_ecbs++; + ahbecbfree(ahb, next_ecb); + next_ecb++; + } + + if (ahb->num_ecbs == 0) + goto error_exit; + + ahb->init_level++; + + eisa_reg_end(e_dev); + + /* + * Now that we know we own the resources we need, register + * our bus with the XPT. + */ + if (ahbxptattach(ahb)) + goto error_exit; + + /* Enable our interrupt */ + eisa_enable_intr(e_dev, irq); + return (0); +error_exit: + /* + * The board's IRQ line will not be left enabled + * if we can't intialize correctly, so its safe + * to release the irq. + */ + eisa_release_intr(e_dev, irq, ahbintr); + ahbfree(ahb); + return (-1); +} + +static struct ahb_softc * +ahballoc(u_long unit, u_int iobase, u_int irq) +{ + struct ahb_softc *ahb; + + /* + * Allocate a storage area for us + */ + ahb = malloc(sizeof(struct ahb_softc), M_TEMP, M_NOWAIT); + if (!ahb) { + printf("ahb%d: cannot malloc!\n", unit); + return (NULL); + } + bzero(ahb, sizeof(struct ahb_softc)); + SLIST_INIT(&ahb->free_ecbs); + LIST_INIT(&ahb->pending_ccbs); + ahb->unit = unit; + ahb->tag = I386_BUS_SPACE_IO; + ahb->bsh = iobase; + ahb->disc_permitted = ~0; + ahb->tags_permitted = ~0; + + return (ahb); +} + +static void +ahbfree(struct ahb_softc *ahb) +{ + switch (ahb->init_level) { + default: + case 4: + bus_dmamap_unload(ahb->ecb_dmat, ahb->ecb_dmamap); + case 3: + bus_dmamem_free(ahb->ecb_dmat, ahb->ecb_array, + ahb->ecb_dmamap); + bus_dmamap_destroy(ahb->ecb_dmat, ahb->ecb_dmamap); + case 2: + bus_dma_tag_destroy(ahb->ecb_dmat); + case 1: + bus_dma_tag_destroy(ahb->buffer_dmat); + case 0: + } + free(ahb, M_DEVBUF); +} + +/* + * reset board, If it doesn't respond, return failure + */ +static int +ahbreset(struct ahb_softc *ahb) +{ + int wait = 1000; /* 1 sec enough? */ + int test; + + if ((ahb_inb(ahb, PORTADDR) & PORTADDR_ENHANCED) == 0) { + printf("ahb_reset: Controller not in enhanced mode\n"); + return (-1); + } + + ahb_outb(ahb, CONTROL, CNTRL_HARD_RST); + DELAY(1000); + ahb_outb(ahb, CONTROL, 0); + while (--wait) { + DELAY(1000); + if ((ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_BUSY) == 0) + break; + } + + if (wait == 0) { + printf("ahbreset: No answer from aha1742 board\n"); + return (-1); + } + if ((test = ahb_inb(ahb, MBOXIN0)) != 0) { + printf("ahb_reset: self test failed, val = 0x%x\n", test); + return (-1); + } + while (ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_INTPEND) { + ahb_outb(ahb, CONTROL, CNTRL_CLRINT); + DELAY(10000); + } + return (0); +} + +static void +ahbmapecbs(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct ahb_softc* ahb; + + ahb = (struct ahb_softc*)arg; + ahb->ecb_physbase = segs->ds_addr; + /* + * Space for adapter inquiry information is on the + * tail of the ecb array. + */ + ahb->ha_inq_physbase = ahbecbvtop(ahb, &ahb->ecb_array[AHB_NECB]); +} + +static int +ahbxptattach(struct ahb_softc *ahb) +{ + struct cam_devq *devq; + struct ecb *ecb; + u_int i; + + /* Remeber who are we on the scsi bus */ + ahb->scsi_id = ahb_inb(ahb, SCSIDEF) & HSCSIID; + + /* Use extended translation?? */ + ahb->extended_trans = ahb_inb(ahb, RESV1) & EXTENDED_TRANS; + + /* Fetch adapter inquiry data */ + ecb = ahbecbget(ahb); /* Always succeeds - no outstanding commands */ + ecb->hecb.opcode = ECBOP_READ_HA_INQDATA; + ecb->hecb.flag_word1 = FW1_SUPPRESS_URUN_ERR|FW1_ERR_STATUS_BLK_ONLY; + ecb->hecb.data_ptr = ahb->ha_inq_physbase; + ecb->hecb.data_len = sizeof(struct ha_inquiry_data); + ecb->hecb.sense_ptr = 0; + ecb->state = ECB_ACTIVE; + + /* Tell the adapter about this command */ + ahbqueuembox(ahb, ahbecbvtop(ahb, ecb), + ATTN_STARTECB|ahb->scsi_id); + + /* Poll for interrupt completion */ + for (i = 1000; ecb->state != ECB_FREE && i != 0; i--) { + ahbintr(ahb); + DELAY(1000); + } + + ahb->num_ecbs = MIN(ahb->num_ecbs, + ahb->ha_inq_data->scsi_data.reserved[1]); + printf("ahb%d: %.8s %s SCSI Adapter, FW Rev. %.4s, ID=%d, %d ECBs\n", + ahb->unit, ahb->ha_inq_data->scsi_data.product, + (ahb->ha_inq_data->scsi_data.flags & 0x4) ? "Differential" + : "Single Ended", + ahb->ha_inq_data->scsi_data.revision, + ahb->scsi_id, ahb->num_ecbs); + + /* Restore sense paddr for future CCB clients */ + ecb->hecb.sense_ptr = ahbsensepaddr(ahbecbvtop(ahb, ecb)); + + ahbecbfree(ahb, ecb); + + /* + * Create the device queue for our SIM. + */ + devq = cam_simq_alloc(ahb->num_ecbs); + if (devq == NULL) + return (ENOMEM); + + /* + * Construct our SIM entry + */ + ahb->sim = cam_sim_alloc(ahbaction, ahbpoll, "ahb", ahb, ahb->unit, + 2, ahb->num_ecbs, devq); + if (ahb->sim == NULL) { + cam_simq_free(devq); + return (ENOMEM); + } + + if (xpt_bus_register(ahb->sim, 0) != CAM_SUCCESS) { + cam_sim_free(ahb->sim, /*free_devq*/TRUE); + return (ENXIO); + } + + if (xpt_create_path(&ahb->path, /*periph*/NULL, + cam_sim_path(ahb->sim), CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + xpt_bus_deregister(cam_sim_path(ahb->sim)); + cam_sim_free(ahb->sim, /*free_devq*/TRUE); + return (ENXIO); + } + + /* + * Allow the board to generate interrupts. + */ + ahb_outb(ahb, INTDEF, ahb_inb(ahb, INTDEF) | INTEN); + + return (0); +} + +static void +ahbhandleimmed(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat) +{ + struct ccb_hdr *ccb_h; + u_int target_id; + + if (ahb->immed_cmd == 0) { + printf("ahb%d: Immediate Command complete with no " + " pending command\n"); + return; + } + + target_id = intstat & INTSTAT_TARGET_MASK; + + ccb_h = LIST_FIRST(&ahb->pending_ccbs); + while (ccb_h != NULL) { + struct ecb *pending_ecb; + union ccb *ccb; + + pending_ecb = (struct ecb *)ccb_h->ccb_ecb_ptr; + ccb = pending_ecb->ccb; + ccb_h = LIST_NEXT(ccb_h, sim_links.le); + if (ccb->ccb_h.target_id == target_id + || target_id == ahb->scsi_id) { + untimeout(ahbtimeout, pending_ecb, + ccb->ccb_h.timeout_ch); + LIST_REMOVE(&ccb->ccb_h, sim_links.le); + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) + bus_dmamap_unload(ahb->buffer_dmat, + pending_ecb->dmamap); + if (pending_ecb == ahb->immed_ecb) + ccb->ccb_h.status = + CAM_CMD_TIMEOUT|CAM_RELEASE_SIMQ; + else if (target_id == ahb->scsi_id) + ccb->ccb_h.status = CAM_SCSI_BUS_RESET; + else + ccb->ccb_h.status = CAM_BDR_SENT; + ahbecbfree(ahb, pending_ecb); + xpt_done(ccb); + } else if (ahb->immed_ecb != NULL) { + /* Re-instate timeout */ + ccb->ccb_h.timeout_ch = + timeout(ahbtimeout, (caddr_t)pending_ecb, + (ccb->ccb_h.timeout * hz) / 1000); + } + } + + if (ahb->immed_ecb != NULL) { + ahb->immed_ecb = NULL; + printf("ahb%d: No longer in timeout\n", ahb->unit); + } else if (target_id == ahb->scsi_id) + printf("ahb%d: SCSI Bus Reset Delivered\n", ahb->unit); + else + printf("ahb%d: Bus Device Reset Delibered to target %d\n", + ahb->unit, target_id); + + ahb->immed_cmd = 0; +} + +static void +ahbcalcresid(struct ahb_softc *ahb, struct ecb *ecb, union ccb *ccb) +{ + if (ecb->status.data_overrun != 0) { + /* + * Overrun Condition. The hardware doesn't + * provide a meaningful byte count in this case + * (the residual is always 0). Tell the XPT + * layer about the error. + */ + ccb->ccb_h.status = CAM_DATA_RUN_ERR; + } else { + ccb->csio.resid = ecb->status.resid_count; + + if ((ecb->hecb.flag_word1 & FW1_SG_ECB) != 0) { + /* + * For S/G transfers, the adapter provides a pointer + * to the address in the last S/G element used and a + * residual for that element. So, we need to sum up + * the elements that follow it in order to get a real + * residual number. If we have an overrun, the residual + * reported will be 0 and we already know that all S/G + * segments have been exhausted, so we can skip this + * step. + */ + ahb_sg_t *sg; + int num_sg; + + num_sg = ecb->hecb.data_len / sizeof(ahb_sg_t); + + /* Find the S/G the adapter was working on */ + for (sg = ecb->sg_list; + num_sg != 0 && sg->addr != ecb->status.resid_addr; + num_sg--, sg++) + ; + + /* Skip it */ + num_sg--; + sg++; + + /* Sum the rest */ + for (; num_sg != 0; num_sg--, sg++) + ccb->csio.resid += sg->len; + } + /* Underruns are not errors */ + ccb->ccb_h.status = CAM_REQ_CMP; + } +} + +static void +ahbprocesserror(struct ahb_softc *ahb, struct ecb *ecb, union ccb *ccb) +{ + struct hardware_ecb *hecb; + struct ecb_status *status; + + hecb = &ecb->hecb; + status = &ecb->status; + switch (status->ha_status) { + case HS_OK: + ccb->csio.scsi_status = status->scsi_status; + if (status->scsi_status != 0) { + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; + if (status->sense_stored) { + ccb->ccb_h.status |= CAM_AUTOSNS_VALID; + ccb->csio.sense_resid = + ccb->csio.sense_len - status->sense_len; + bcopy(&ecb->sense, &ccb->csio.sense_data, + status->sense_len); + } + } + break; + case HS_TARGET_NOT_ASSIGNED: + ccb->ccb_h.status = CAM_PATH_INVALID; + break; + case HS_SEL_TIMEOUT: + ccb->ccb_h.status = CAM_SEL_TIMEOUT; + break; + case HS_DATA_RUN_ERR: + ahbcalcresid(ahb, ecb, ccb); + break; + case HS_UNEXPECTED_BUSFREE: + ccb->ccb_h.status = CAM_UNEXP_BUSFREE; + break; + case HS_INVALID_PHASE: + ccb->ccb_h.status = CAM_SEQUENCE_FAIL; + break; + case HS_REQUEST_SENSE_FAILED: + ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; + break; + case HS_TAG_MSG_REJECTED: + { + struct ccb_trans_settings neg; + + xpt_print_path(ccb->ccb_h.path); + printf("refuses tagged commands. Performing " + "non-tagged I/O\n"); + neg.flags = 0; + neg.valid = CCB_TRANS_TQ_VALID; + xpt_setup_ccb(&neg.ccb_h, ccb->ccb_h.path, /*priority*/1); + xpt_async(AC_TRANSFER_NEG, ccb->ccb_h.path, &neg); + ahb->tags_permitted &= ~(0x01 << ccb->ccb_h.target_id); + ccb->ccb_h.status = CAM_MSG_REJECT_REC; + break; + } + case HS_FIRMWARE_LOAD_REQ: + case HS_HARDWARE_ERR: + /* + * Tell the system that the Adapter + * is no longer functional. + */ + ccb->ccb_h.status = CAM_NO_HBA; + break; + case HS_CMD_ABORTED_HOST: + case HS_CMD_ABORTED_ADAPTER: + case HS_ATN_TARGET_FAILED: + case HS_SCSI_RESET_ADAPTER: + case HS_SCSI_RESET_INCOMING: + ccb->ccb_h.status = CAM_SCSI_BUS_RESET; + break; + case HS_DUP_TCB_RECEIVED: + case HS_INVALID_OPCODE: + case HS_INVALID_CMD_LINK: + case HS_INVALID_ECB_PARAM: + case HS_PROGRAM_CKSUM_ERROR: + panic("ahb%d: Can't happen host status %x occurred", + ahb->unit, status->ha_status); + break; + } + if (ccb->ccb_h.status != CAM_REQ_CMP) { + xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); + ccb->ccb_h.status |= CAM_DEV_QFRZN; + } +} + +static void +ahbdone(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat) +{ + struct ecb *ecb; + union ccb *ccb; + + ecb = ahbecbptov(ahb, mbox); + + if ((ecb->state & ECB_ACTIVE) == 0) + panic("ecb not active"); + + ccb = ecb->ccb; + + if (ccb != NULL) { + untimeout(ahbtimeout, ecb, ccb->ccb_h.timeout_ch); + LIST_REMOVE(&ccb->ccb_h, sim_links.le); + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + bus_dmasync_op_t op; + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) + op = BUS_DMASYNC_POSTREAD; + else + op = BUS_DMASYNC_POSTWRITE; + bus_dmamap_sync(ahb->buffer_dmat, ecb->dmamap, op); + bus_dmamap_unload(ahb->buffer_dmat, ecb->dmamap); + } + + if ((intstat & INTSTAT_MASK) == INTSTAT_ECB_OK) { + ccb->ccb_h.status = CAM_REQ_CMP; + ccb->csio.resid = 0; + } else { + ahbprocesserror(ahb, ecb, ccb); + } + ahbecbfree(ahb, ecb); + xpt_done(ccb); + } else { + /* Non CCB Command */ + if ((intstat & INTSTAT_MASK) != INTSTAT_ECB_OK) { + printf("ahb%d: Command 0%x Failed %x:%x:%x\n", + ahb->unit, ecb->hecb.opcode, + *((u_int16_t*)&ecb->status), + ecb->status.ha_status, ecb->status.resid_count); + } + /* Client owns this ECB and will release it. */ + } +} + +/* + * Catch an interrupt from the adaptor + */ +static void +ahbintr(void *arg) +{ + struct ahb_softc *ahb; + u_int intstat; + u_int32_t mbox; + + ahb = (struct ahb_softc *)arg; + + while (ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_INTPEND) { + /* + * Fetch information about this interrupt. + */ + intstat = ahb_inb(ahb, INTSTAT); + mbox = ahb_inl(ahb, MBOXIN0); + + /* + * Reset interrupt latch. + */ + ahb_outb(ahb, CONTROL, CNTRL_CLRINT); + + /* + * Process the completed operation + */ + switch (intstat & INTSTAT_MASK) { + case INTSTAT_ECB_OK: + case INTSTAT_ECB_CMPWRETRY: + case INTSTAT_ECB_CMPWERR: + ahbdone(ahb, mbox, intstat); + break; + case INTSTAT_AEN_OCCURED: + if ((intstat & INTSTAT_TARGET_MASK) == ahb->scsi_id) { + /* Bus Reset */ + xpt_print_path(ahb->path); + switch (mbox) { + case HS_SCSI_RESET_ADAPTER: + printf("Host Adapter Initiated " + "Bus Reset occurred\n"); + break; + case HS_SCSI_RESET_INCOMING: + printf("Bus Reset Initiated " + "by another device occurred\n"); + break; + } + /* Notify the XPT */ + xpt_async(AC_BUS_RESET, ahb->path, NULL); + break; + } + printf("Unsupported initiator selection AEN occured\n"); + break; + case INTSTAT_IMMED_OK: + case INTSTAT_IMMED_ERR: + ahbhandleimmed(ahb, mbox, intstat); + break; + case INTSTAT_HW_ERR: + panic("Unrecoverable hardware Error Occurred\n"); + } + } +} + +static void +ahbexecuteecb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) +{ + struct ecb *ecb; + union ccb *ccb; + struct ahb_softc *ahb; + u_int32_t ecb_paddr; + int s; + + ecb = (struct ecb *)arg; + ccb = ecb->ccb; + ahb = (struct ahb_softc *)ccb->ccb_h.ccb_ahb_ptr; + + if (error != 0) { + if (error != EFBIG) + printf("ahb%d: Unexepected error 0x%x returned from " + "bus_dmamap_load\n", ahb->unit); + if (ccb->ccb_h.status == CAM_REQ_INPROG) { + xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); + ccb->ccb_h.status = CAM_REQ_TOO_BIG|CAM_DEV_QFRZN; + } + ahbecbfree(ahb, ecb); + xpt_done(ccb); + return; + } + + ecb_paddr = ahbecbvtop(ahb, ecb); + + if (nseg != 0) { + ahb_sg_t *sg; + bus_dma_segment_t *end_seg; + bus_dmasync_op_t op; + + end_seg = dm_segs + nseg; + + /* Copy the segments into our SG list */ + sg = ecb->sg_list; + while (dm_segs < end_seg) { + sg->addr = dm_segs->ds_addr; + sg->len = dm_segs->ds_len; + sg++; + dm_segs++; + } + + if (nseg > 1) { + ecb->hecb.flag_word1 |= FW1_SG_ECB; + ecb->hecb.data_ptr = ahbsgpaddr(ecb_paddr); + ecb->hecb.data_len = sizeof(ahb_sg_t) * nseg; + } else { + ecb->hecb.data_ptr = ecb->sg_list->addr; + ecb->hecb.data_len = ecb->sg_list->len; + } + + if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { +/* ecb->hecb.flag_word2 |= FW2_DATA_DIR_IN; */ + op = BUS_DMASYNC_PREREAD; + } else { + op = BUS_DMASYNC_PREWRITE; + } + /* ecb->hecb.flag_word2 |= FW2_CHECK_DATA_DIR; */ + + bus_dmamap_sync(ahb->buffer_dmat, ecb->dmamap, op); + + } else { + ecb->hecb.data_ptr = 0; + ecb->hecb.data_len = 0; + } + + s = splcam(); + + /* + * Last time we need to check if this CCB needs to + * be aborted. + */ + if (ccb->ccb_h.status != CAM_REQ_INPROG) { + if (nseg != 0) + bus_dmamap_unload(ahb->buffer_dmat, ecb->dmamap); + ahbecbfree(ahb, ecb); + xpt_done(ccb); + splx(s); + return; + } + + ecb->state = ECB_ACTIVE; + ccb->ccb_h.status |= CAM_SIM_QUEUED; + LIST_INSERT_HEAD(&ahb->pending_ccbs, &ccb->ccb_h, sim_links.le); + + /* Tell the adapter about this command */ + ahbqueuembox(ahb, ecb_paddr, ATTN_STARTECB|ccb->ccb_h.target_id); + + ccb->ccb_h.timeout_ch = timeout(ahbtimeout, (caddr_t)ecb, + (ccb->ccb_h.timeout * hz) / 1000); + splx(s); +} + +static void +ahbaction(struct cam_sim *sim, union ccb *ccb) +{ + struct ahb_softc *ahb; + + CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ahbaction\n")); + + ahb = (struct ahb_softc *)cam_sim_softc(sim); + + switch (ccb->ccb_h.func_code) { + /* Common cases first */ + case XPT_SCSI_IO: /* Execute the requested I/O operation */ + { + struct ecb *ecb; + struct hardware_ecb *hecb; + + /* + * get an ecb to use. + */ + if ((ecb = ahbecbget(ahb)) == NULL) { + /* Should never occur */ + panic("Failed to get an ecb"); + } + + /* + * So we can find the ECB when an abort is requested + */ + ecb->ccb = ccb; + ccb->ccb_h.ccb_ecb_ptr = ecb; + ccb->ccb_h.ccb_ahb_ptr = ahb; + + /* + * Put all the arguments for the xfer in the ecb + */ + hecb = &ecb->hecb; + hecb->opcode = ECBOP_INITIATOR_SCSI_CMD; + hecb->flag_word1 = FW1_AUTO_REQUEST_SENSE + | FW1_ERR_STATUS_BLK_ONLY; + hecb->flag_word2 = ccb->ccb_h.target_lun + | FW2_NO_RETRY_ON_BUSY; + if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) { + hecb->flag_word2 |= FW2_TAG_ENB + | ((ccb->csio.tag_action & 0x3) + << FW2_TAG_TYPE_SHIFT); + } + if ((ccb->ccb_h.flags & CAM_DIS_DISCONNECT) != 0) + hecb->flag_word2 |= FW2_DISABLE_DISC; + hecb->sense_len = ccb->csio.sense_len; + hecb->cdb_len = ccb->csio.cdb_len; + if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { + if ((ccb->ccb_h.flags & CAM_CDB_PHYS) == 0) { + bcopy(ccb->csio.cdb_io.cdb_ptr, + hecb->cdb, hecb->cdb_len); + } else { + /* I guess I could map it in... */ + ccb->ccb_h.status = CAM_REQ_INVALID; + ahbecbfree(ahb, ecb); + xpt_done(ccb); + return; + } + } else { + bcopy(ccb->csio.cdb_io.cdb_bytes, + hecb->cdb, hecb->cdb_len); + } + + /* + * If we have any data to send with this command, + * map it into bus space. + */ + if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { + if ((ccb->ccb_h.flags & CAM_SCATTER_VALID) == 0) { + /* + * We've been given a pointer + * to a single buffer. + */ + if ((ccb->ccb_h.flags & CAM_DATA_PHYS)==0) { + int s; + int error; + + s = splsoftvm(); + error = bus_dmamap_load( + ahb->buffer_dmat, + ecb->dmamap, + ccb->csio.data_ptr, + ccb->csio.dxfer_len, + ahbexecuteecb, + ecb, /*flags*/0); + if (error == EINPROGRESS) { + /* + * So as to maintain ordering, + * freeze the controller queue + * until our mapping is + * returned. + */ + xpt_freeze_simq(ahb->sim, 1); + ccb->ccb_h.status |= + CAM_RELEASE_SIMQ; + } + splx(s); + } else { + struct bus_dma_segment seg; + + /* Pointer to physical buffer */ + seg.ds_addr = + (bus_addr_t)ccb->csio.data_ptr; + seg.ds_len = ccb->csio.dxfer_len; + ahbexecuteecb(ecb, &seg, 1, 0); + } + } else { + struct bus_dma_segment *segs; + + if ((ccb->ccb_h.flags & CAM_DATA_PHYS) != 0) + panic("ahbaction - Physical segment " + "pointers unsupported"); + + if ((ccb->ccb_h.flags & CAM_SG_LIST_PHYS) == 0) + panic("btaction - Virtual segment " + "addresses unsupported"); + + /* Just use the segments provided */ + segs = (struct bus_dma_segment *) + ccb->csio.data_ptr; + ahbexecuteecb(ecb, segs, ccb->csio.sglist_cnt, + 0); + } + } else { + ahbexecuteecb(ecb, NULL, 0, 0); + } + break; + } + case XPT_EN_LUN: /* Enable LUN as a target */ + case XPT_TARGET_IO: /* Execute target I/O request */ + case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */ + case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/ + case XPT_ABORT: /* Abort the specified CCB */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_SET_TRAN_SETTINGS: + { + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + /* Get default/user set transfer settings for the target */ + { + struct ccb_trans_settings *cts; + u_int target_mask; + + cts = &ccb->cts; + target_mask = 0x01 << ccb->ccb_h.target_id; + if ((cts->flags & CCB_TRANS_USER_SETTINGS) != 0) { + cts->flags = 0; + if ((ahb->disc_permitted & target_mask) != 0) + cts->flags |= CCB_TRANS_DISC_ENB; + if ((ahb->tags_permitted & target_mask) != 0) + cts->flags |= CCB_TRANS_TAG_ENB; + cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT; + cts->sync_period = 25; /* 10MHz */ + + if (cts->sync_period != 0) + cts->sync_offset = 15; + + cts->valid = CCB_TRANS_SYNC_RATE_VALID + | CCB_TRANS_SYNC_OFFSET_VALID + | CCB_TRANS_BUS_WIDTH_VALID + | CCB_TRANS_DISC_VALID + | CCB_TRANS_TQ_VALID; + ccb->ccb_h.status = CAM_REQ_CMP; + } else { + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + } + xpt_done(ccb); + break; + } + case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ + { + int i; + + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ccb->ccb_h.target_id); + /* Poll for interrupt completion */ + for (i = 1000; ahb->immed_cmd != 0 && i != 0; i--) + DELAY(1000); + break; + } + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + + if (size_mb > 1024 && (ahb->extended_trans != 0)) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_BUS: /* Reset the specified SCSI bus */ + { + int i; + + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ahb->scsi_id); + /* Poll for interrupt completion */ + for (i = 1000; ahb->immed_cmd != 0 && i != 0; i--) + DELAY(1000); + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_TERM_IO: /* Terminate the I/O process */ + /* XXX Implement */ + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case XPT_PATH_INQ: /* Path routing inquiry */ + { + struct ccb_pathinq *cpi = &ccb->cpi; + + cpi->version_num = 1; /* XXX??? */ + cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE; + cpi->target_sprt = 0; + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = 7; + cpi->max_lun = 7; + cpi->initiator_id = ahb->scsi_id; + cpi->bus_id = cam_sim_bus(sim); + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "Adaptec", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } +#if 0 + /* Need these??? */ + case XPT_IMMED_NOTIFY: /* Notify Host Target driver of event */ + case XPT_NOTIFY_ACK: /* Acknowledgement of event */ +#endif + default: + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } +} + +static void +ahbpoll(struct cam_sim *sim) +{ +} + +void +ahbtimeout(void *arg) +{ + struct ecb *ecb; + union ccb *ccb; + struct ahb_softc *ahb; + int s; + + ecb = (struct ecb *)arg; + ccb = ecb->ccb; + ahb = (struct ahb_softc *)ccb->ccb_h.ccb_ahb_ptr; + xpt_print_path(ccb->ccb_h.path); + printf("ECB 0x%x - timed out\n", ecb); + + s = splcam(); + + if ((ecb->state & ECB_ACTIVE) == 0) { + xpt_print_path(ccb->ccb_h.path); + printf("ECB 0x%x - timed out ECB already completed\n", ecb); + splx(s); + return; + } + /* + * In order to simplify the recovery process, we ask the XPT + * layer to halt the queue of new transactions and we traverse + * the list of pending CCBs and remove their timeouts. This + * means that the driver attempts to clear only one error + * condition at a time. In general, timeouts that occur + * close together are related anyway, so there is no benefit + * in attempting to handle errors in parrallel. Timeouts will + * be reinstated when the recovery process ends. + */ + if ((ecb->state & ECB_DEVICE_RESET) == 0) { + struct ccb_hdr *ccb_h; + + if ((ecb->state & ECB_RELEASE_SIMQ) == 0) { + xpt_freeze_simq(ahb->sim, /*count*/1); + ecb->state |= ECB_RELEASE_SIMQ; + } + + ccb_h = LIST_FIRST(&ahb->pending_ccbs); + while (ccb_h != NULL) { + struct ecb *pending_ecb; + + pending_ecb = (struct ecb *)ccb_h->ccb_ecb_ptr; + untimeout(ahbtimeout, pending_ecb, ccb_h->timeout_ch); + ccb_h = LIST_NEXT(ccb_h, sim_links.le); + } + + /* Store for our interrupt handler */ + ahb->immed_ecb = ecb; + + /* + * Send a Bus Device Reset message: + * The target that is holding up the bus may not + * be the same as the one that triggered this timeout + * (different commands have different timeout lengths), + * but we have no way of determining this from our + * timeout handler. Our strategy here is to queue a + * BDR message to the target of the timed out command. + * If this fails, we'll get another timeout 2 seconds + * later which will attempt a bus reset. + */ + xpt_print_path(ccb->ccb_h.path); + printf("Queuing BDR\n", ecb); + ecb->state |= ECB_DEVICE_RESET; + ccb->ccb_h.timeout_ch = + timeout(ahbtimeout, (caddr_t)ecb, 2 * hz); + + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ccb->ccb_h.target_id); + } else if ((ecb->state & ECB_SCSIBUS_RESET) != 0) { + /* + * Try a SCSI bus reset. We do this only if we + * have already attempted to clear the condition with a BDR. + */ + xpt_print_path(ccb->ccb_h.path); + printf("Attempting SCSI Bus reset\n", ecb); + ecb->state |= ECB_SCSIBUS_RESET; + ccb->ccb_h.timeout_ch = + timeout(ahbtimeout, (caddr_t)ecb, 2 * hz); + ahb->immed_cmd = IMMED_RESET; + ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ahb->scsi_id); + } else { + /* Bring out the hammer... */ + ahbreset(ahb); + + /* Simulate the reset complete interrupt */ + ahbhandleimmed(ahb, 0, ahb->scsi_id|INTSTAT_IMMED_OK); + } + + splx(s); +} + +#endif /* NEISA */ diff --git a/sys/i386/eisa/ahbreg.h b/sys/i386/eisa/ahbreg.h new file mode 100644 index 00000000000..1bed89ebff4 --- /dev/null +++ b/sys/i386/eisa/ahbreg.h @@ -0,0 +1,283 @@ +/* + * Hardware structure definitions for the Adaptec 174X CAM SCSI device driver. + * + * Copyright (c) 1998 Justin T. Gibbs + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +/* Resource Constatns */ +#define AHB_NECB 64 +#define AHB_NSEG 32 + +/* AHA1740 EISA ID, IO port range size, and offset from slot base */ +#define EISA_DEVICE_ID_ADAPTEC_1740 0x04900000 +#define AHB_EISA_IOSIZE 0x100 +#define AHB_EISA_SLOT_OFFSET 0xc00 + +/* AHA1740 EISA board control registers (Offset from slot base) */ +#define EBCTRL 0x084 +#define CDEN 0x01 + +/* + * AHA1740 EISA board mode registers (Offset from slot base) + */ +#define PORTADDR 0x0C0 +#define PORTADDR_ENHANCED 0x80 + +#define BIOSADDR 0x0C1 + +#define INTDEF 0x0C2 +#define INT9 0x00 +#define INT10 0x01 +#define INT11 0x02 +#define INT12 0x03 +#define INT14 0x05 +#define INT15 0x06 +#define INTLEVEL 0x08 +#define INTEN 0x10 + +#define SCSIDEF 0x0C3 +#define HSCSIID 0x0F /* our SCSI ID */ +#define RSTBUS 0x10 + +#define BUSDEF 0x0C4 +#define B0uS 0x00 /* give up bus immediatly */ +#define B4uS 0x01 /* delay 4uSec. */ +#define B8uS 0x02 /* delay 8uSec. */ + +#define RESV0 0x0C5 + +#define RESV1 0x0C6 +#define EXTENDED_TRANS 0x01 + +#define RESV2 0x0C7 + +/* + * AHA1740 ENHANCED mode mailbox control regs (Offset from slot base) + */ +#define MBOXOUT0 0x0D0 +#define MBOXOUT1 0x0D1 +#define MBOXOUT2 0x0D2 +#define MBOXOUT3 0x0D3 + +#define ATTN 0x0D4 +#define ATTN_TARGMASK 0x0F +#define ATTN_IMMED 0x10 +#define ATTN_STARTECB 0x40 +#define ATTN_ABORTECB 0x50 +#define ATTN_TARG_RESET 0x80 + +#define CONTROL 0x0D5 +#define CNTRL_SET_HRDY 0x20 +#define CNTRL_CLRINT 0x40 +#define CNTRL_HARD_RST 0x80 + +#define INTSTAT 0x0D6 +#define INTSTAT_TARGET_MASK 0x0F +#define INTSTAT_MASK 0xF0 +#define INTSTAT_ECB_OK 0x10 /* ECB Completed w/out error */ +#define INTSTAT_ECB_CMPWRETRY 0x50 /* ECB Completed w/retries */ +#define INTSTAT_HW_ERR 0x70 /* Adapter Hardware Failure */ +#define INTSTAT_IMMED_OK 0xA0 /* Immediate command complete */ +#define INTSTAT_ECB_CMPWERR 0xC0 /* ECB Completed w/error */ +#define INTSTAT_AEN_OCCURED 0xD0 /* Async Event Notification */ +#define INTSTAT_IMMED_ERR 0xE0 /* Immediate command failed */ + +#define HOSTSTAT 0x0D7 +#define HOSTSTAT_MBOX_EMPTY 0x04 +#define HOSTSTAT_INTPEND 0x02 +#define HOSTSTAT_BUSY 0x01 + + +#define MBOXIN0 0x0D8 +#define MBOXIN1 0x0D9 +#define MBOXIN2 0x0DA +#define MBOXIN3 0x0DB + +#define STATUS2 0x0DC +#define STATUS2_HOST_READY 0x01 + +typedef enum { + IMMED_RESET = 0x000080, + IMMED_DEVICE_CLEAR_QUEUE = 0x000480, + IMMED_ADAPTER_CLEAR_QUEUE = 0x000880, + IMMED_RESUME = 0x200090 +} immed_cmd; + +struct ecb_status { + /* Status Flags */ + u_int16_t no_error :1, /* Completed with no error */ + data_underrun :1, + :1, + ha_queue_full :1, + spec_check :1, + data_overrun :1, + chain_halted :1, + intr_issued :1, + status_avail :1, /* status bytes 14-31 are valid */ + sense_stored :1, + :1, + init_requied :1, + major_error :1, + :1, + extended_ca :1, + :1; + /* Host Status */ + u_int8_t ha_status; + u_int8_t scsi_status; + int32_t resid_count; + u_int32_t resid_addr; + u_int16_t addit_status; + u_int8_t sense_len; + u_int8_t unused[9]; + u_int8_t cdb[6]; +}; + +typedef enum { + HS_OK = 0x00, + HS_CMD_ABORTED_HOST = 0x04, + HS_CMD_ABORTED_ADAPTER = 0x05, + HS_FIRMWARE_LOAD_REQ = 0x08, + HS_TARGET_NOT_ASSIGNED = 0x0A, + HS_SEL_TIMEOUT = 0x11, + HS_DATA_RUN_ERR = 0x12, + HS_UNEXPECTED_BUSFREE = 0x13, + HS_INVALID_PHASE = 0x14, + HS_INVALID_OPCODE = 0x16, + HS_INVALID_CMD_LINK = 0x17, + HS_INVALID_ECB_PARAM = 0x18, + HS_DUP_TCB_RECEIVED = 0x19, + HS_REQUEST_SENSE_FAILED = 0x1A, + HS_TAG_MSG_REJECTED = 0x1C, + HS_HARDWARE_ERR = 0x20, + HS_ATN_TARGET_FAILED = 0x21, + HS_SCSI_RESET_ADAPTER = 0x22, + HS_SCSI_RESET_INCOMING = 0x23, + HS_PROGRAM_CKSUM_ERROR = 0x80 +} host_status; + +typedef enum { + ECBOP_NOP = 0x00, + ECBOP_INITIATOR_SCSI_CMD = 0x01, + ECBOP_RUN_DIAGNOSTICS = 0x05, + ECBOP_INITIALIZE_SCSI = 0x06, /* Set syncrate/disc/parity */ + ECBOP_READ_SENSE = 0x08, + ECBOP_DOWNLOAD_FIRMWARE = 0x09, + ECBOP_READ_HA_INQDATA = 0x0a, + ECBOP_TARGET_SCSI_CMD = 0x10 +} ecb_op; + +struct ha_inquiry_data { + struct scsi_inquiry_data scsi_data; + u_int8_t release_date[8]; + u_int8_t release_time[8]; + u_int16_t firmware_cksum; + u_int16_t reserved; + u_int16_t target_data[16]; +}; + +struct hardware_ecb { + u_int16_t opcode; + u_int16_t flag_word1; +#define FW1_LINKED_CMD 0x0001 +#define FW1_DISABLE_INTR 0x0080 +#define FW1_SUPPRESS_URUN_ERR 0x0400 +#define FW1_SG_ECB 0x1000 +#define FW1_ERR_STATUS_BLK_ONLY 0x4000 +#define FW1_AUTO_REQUEST_SENSE 0x8000 + u_int16_t flag_word2; +#define FW2_LUN_MASK 0x0007 +#define FW2_TAG_ENB 0x0008 +#define FW2_TAG_TYPE 0x0030 +#define FW2_TAG_TYPE_SHIFT 4 +#define FW2_DISABLE_DISC 0x0040 +#define FW2_CHECK_DATA_DIR 0x0100 +#define FW2_DATA_DIR_IN 0x0200 +#define FW2_SUPRESS_TRANSFER 0x0400 +#define FW2_CALC_CKSUM 0x0800 +#define FW2_RECOVERY_ECB 0x4000 +#define FW2_NO_RETRY_ON_BUSY 0x8000 + u_int16_t reserved; + u_int32_t data_ptr; + u_int32_t data_len; + u_int32_t status_ptr; + u_int32_t link_ptr; + u_int32_t reserved2; + u_int32_t sense_ptr; + u_int8_t sense_len; + u_int8_t cdb_len; + u_int16_t cksum; + u_int8_t cdb[12]; +}; + +typedef struct { + u_int32_t addr; + u_int32_t len; +} ahb_sg_t; + +typedef enum { + ECB_FREE = 0x0, + ECB_ACTIVE = 0x1, + ECB_DEVICE_RESET = 0x2, + ECB_SCSIBUS_RESET = 0x4, + ECB_RELEASE_SIMQ = 0x8 +} ecb_state; + +struct ecb { + struct hardware_ecb hecb; + struct ecb_status status; + struct scsi_sense_data sense; + ahb_sg_t sg_list[AHB_NSEG]; + SLIST_ENTRY(ecb) links; + ecb_state state; + union ccb *ccb; + bus_dmamap_t dmamap; +}; + +struct ahb_softc { + bus_space_tag_t tag; + bus_space_handle_t bsh; + struct cam_sim *sim; + struct cam_path *path; + SLIST_HEAD(,ecb) free_ecbs; + LIST_HEAD(,ccb_hdr) pending_ccbs; + struct ecb *ecb_array; + u_int32_t ecb_physbase; + bus_dma_tag_t buffer_dmat; /* dmat for buffer I/O */ + bus_dma_tag_t ecb_dmat; /* dmat for our ecb array */ + bus_dmamap_t ecb_dmamap; + volatile u_int32_t immed_cmd; + struct ecb *immed_ecb; + struct ha_inquiry_data *ha_inq_data; + u_int32_t ha_inq_physbase; + u_long unit; + u_int init_level; + u_int scsi_id; + u_int num_ecbs; + u_int extended_trans; + u_int8_t disc_permitted; + u_int8_t tags_permitted; +};