diff options
Diffstat (limited to 'sys/dev/ct/ct.c')
-rw-r--r-- | sys/dev/ct/ct.c | 1307 |
1 files changed, 1307 insertions, 0 deletions
diff --git a/sys/dev/ct/ct.c b/sys/dev/ct/ct.c new file mode 100644 index 0000000..c9c9ef9 --- /dev/null +++ b/sys/dev/ct/ct.c @@ -0,0 +1,1307 @@ +/* $FreeBSD$ */ +/* $NecBSD: ct.c,v 1.13.12.5 2001/06/26 07:31:53 honda Exp $ */ +/* $NetBSD$ */ + +#define CT_DEBUG +#define CT_IO_CONTROL_FLAGS (CT_USE_CCSEQ | CT_FAST_INTR) + +/* + * [NetBSD for NEC PC-98 series] + * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 + * NetBSD/pc98 porting staff. All rights reserved. + * + * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 + * Naofumi HONDA. 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, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. 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 ``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 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. + */ + +#include "opt_ddb.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#if defined(__FreeBSD__) && __FreeBSD_version > 500001 +#include <sys/bio.h> +#endif /* __ FreeBSD__ */ +#include <sys/buf.h> +#include <sys/queue.h> +#include <sys/malloc.h> +#include <sys/errno.h> + +#ifdef __NetBSD__ +#include <sys/device.h> + +#include <machine/bus.h> +#include <machine/intr.h> + +#include <dev/scsipi/scsi_all.h> +#include <dev/scsipi/scsipi_all.h> +#include <dev/scsipi/scsiconf.h> +#include <dev/scsipi/scsi_disk.h> + +#include <machine/dvcfg.h> +#include <machine/physio_proc.h> + +#include <i386/Cbus/dev/scsi_low.h> + +#include <dev/ic/wd33c93reg.h> +#include <i386/Cbus/dev/ct/ctvar.h> +#include <i386/Cbus/dev/ct/ct_machdep.h> +#endif /* __NetBSD__ */ + +#ifdef __FreeBSD__ +#include <machine/bus.h> + +#include <machine/dvcfg.h> +#include <machine/physio_proc.h> + +#include <cam/scsi/scsi_low.h> + +#include <dev/ic/wd33c93reg.h> +#include <dev/ct/ctvar.h> +#include <dev/ct/ct_machdep.h> +#endif /* __FreeBSD__ */ + +#define CT_NTARGETS 8 +#define CT_NLUNS 8 +#define CT_RESET_DEFAULT 2000 +#define CT_DELAY_MAX (2 * 1000 * 1000) +#define CT_DELAY_INTERVAL (1) + +/*************************************************** + * DEBUG + ***************************************************/ +#ifdef CT_DEBUG +int ct_debug; +#endif /* CT_DEBUG */ + +/*************************************************** + * IO control + ***************************************************/ +#define CT_USE_CCSEQ 0x0100 +#define CT_FAST_INTR 0x0200 + +u_int ct_io_control = CT_IO_CONTROL_FLAGS; + +/*************************************************** + * default data + ***************************************************/ +u_int8_t cthw_cmdlevel[256] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C E D F */ +/*0*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,1 ,0 ,0 ,0 ,0 ,0 , +/*1*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*2*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,1 ,0 ,0 ,0 ,0 ,0 , +/*3*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*4*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*5*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*6*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*7*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*8*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*9*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*A*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*B*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*C*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*D*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*E*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +/*F*/0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 , +}; + +#if 0 +/* default synch data table */ +/* A 10 6.6 5.0 4.0 3.3 2.8 2.5 2.0 M/s */ +/* X 100 150 200 250 300 350 400 500 ns */ +static struct ct_synch_data ct_synch_data_FSCSI[] = { + {25, 0xa0}, {37, 0xb0}, {50, 0x20}, {62, 0xd0}, {75, 0x30}, + {87, 0xf0}, {100, 0x40}, {125, 0x50}, {0, 0} +}; + +static struct ct_synch_data ct_synch_data_SCSI[] = { + {50, 0x20}, {75, 0x30}, {100, 0x40}, {125, 0x50}, {0, 0} +}; +#endif +/*************************************************** + * DEVICE STRUCTURE + ***************************************************/ +extern struct cfdriver ct_cd; + +/***************************************************************** + * Interface functions + *****************************************************************/ +static int ct_xfer(struct ct_softc *, u_int8_t *, int, int, u_int *); +static void ct_io_xfer(struct ct_softc *); +static int ct_reselected(struct ct_softc *, u_int8_t); +static void ct_phase_error(struct ct_softc *, u_int8_t); +static int ct_start_selection(struct ct_softc *, struct slccb *); +static int ct_msg(struct ct_softc *, struct targ_info *, u_int); +static int ct_world_start(struct ct_softc *, int); +static __inline void cthw_phase_bypass(struct ct_softc *, u_int8_t); +static int cthw_chip_reset(struct ct_bus_access_handle *, int *, int, int); +static void cthw_bus_reset(struct ct_softc *); +static int ct_ccb_nexus_establish(struct ct_softc *); +static int ct_lun_nexus_establish(struct ct_softc *); +static int ct_target_nexus_establish(struct ct_softc *, int, int); +static void cthw_attention(struct ct_softc *); +static int ct_targ_init(struct ct_softc *, struct targ_info *, int); +static int ct_unbusy(struct ct_softc *); +static void ct_attention(struct ct_softc *); +static struct ct_synch_data *ct_make_synch_table(struct ct_softc *); +static int ct_catch_intr(struct ct_softc *); + +struct scsi_low_funcs ct_funcs = { + SC_LOW_INIT_T ct_world_start, + SC_LOW_BUSRST_T cthw_bus_reset, + SC_LOW_TARG_INIT_T ct_targ_init, + SC_LOW_LUN_INIT_T NULL, + + SC_LOW_SELECT_T ct_start_selection, + SC_LOW_NEXUS_T ct_lun_nexus_establish, + SC_LOW_NEXUS_T ct_ccb_nexus_establish, + + SC_LOW_ATTEN_T cthw_attention, + SC_LOW_MSG_T ct_msg, + + SC_LOW_TIMEOUT_T NULL, + SC_LOW_POLL_T ctintr, + + NULL, /* SC_LOW_POWER_T cthw_power, */ +}; + +/************************************************** + * HW functions + **************************************************/ +static __inline void +cthw_phase_bypass(ct, ph) + struct ct_softc *ct; + u_int8_t ph; +{ + struct ct_bus_access_handle *chp = &ct->sc_ch; + + ct_cr_write_1(chp, wd3s_cph, ph); + ct_cr_write_1(chp, wd3s_cmd, WD3S_SELECT_ATN_TFR); +} + +static void +cthw_bus_reset(ct) + struct ct_softc *ct; +{ + + /* + * wd33c93 does not have bus reset function. + */ + if (ct->ct_bus_reset != NULL) + ((*ct->ct_bus_reset) (ct)); +} + +static int +cthw_chip_reset(chp, chiprevp, chipclk, hostid) + struct ct_bus_access_handle *chp; + int *chiprevp; + int chipclk, hostid; +{ +#define CT_SELTIMEOUT_20MHz_REGV (0x80) + u_int8_t aux, regv; + u_int seltout; + int wc; + + /* issue abort cmd */ + ct_cr_write_1(chp, wd3s_cmd, WD3S_ABORT); + SCSI_LOW_DELAY(1000); /* 1ms wait */ + (void) ct_stat_read_1(chp); + (void) ct_cr_read_1(chp, wd3s_stat); + + /* setup chip registers */ + regv = 0; + seltout = CT_SELTIMEOUT_20MHz_REGV; + switch (chipclk) + { + case 8: + case 10: + seltout = (seltout * chipclk) / 20; + regv = IDR_FS_8_10; + break; + + case 12: + case 15: + seltout = (seltout * chipclk) / 20; + regv = IDR_FS_12_15; + break; + + case 16: + case 20: + seltout = (seltout * chipclk) / 20; + regv = IDR_FS_16_20; + break; + + default: + panic("ct: illegal chip clk rate\n"); + break; + } + + regv |= IDR_EHP | hostid | IDR_RAF | IDR_EAF; + ct_cr_write_1(chp, wd3s_oid, regv); + + ct_cr_write_1(chp, wd3s_cmd, WD3S_RESET); + for (wc = CT_RESET_DEFAULT; wc > 0; wc --) + { + aux = ct_stat_read_1(chp); + if (aux != 0xff && (aux & STR_INT)) + { + regv = ct_cr_read_1(chp, wd3s_stat); + if (regv == BSR_RESET || regv == BSR_AFM_RESET) + break; + + ct_cr_write_1(chp, wd3s_cmd, WD3S_RESET); + } + SCSI_LOW_DELAY(1); + } + if (wc == 0) + return ENXIO; + + ct_cr_write_1(chp, wd3s_tout, seltout); + ct_cr_write_1(chp, wd3s_sid, SIDR_RESEL); + ct_cr_write_1(chp, wd3s_ctrl, CR_DEFAULT); + ct_cr_write_1(chp, wd3s_synch, 0); + if (chiprevp != NULL) + { + *chiprevp = CT_WD33C93; + if (regv == BSR_RESET) + goto out; + + *chiprevp = CT_WD33C93_A; + ct_cr_write_1(chp, wd3s_qtag, 0xaa); + if (ct_cr_read_1(chp, wd3s_qtag) != 0xaa) + { + ct_cr_write_1(chp, wd3s_qtag, 0x0); + goto out; + } + ct_cr_write_1(chp, wd3s_qtag, 0x55); + if (ct_cr_read_1(chp, wd3s_qtag) != 0x55) + { + ct_cr_write_1(chp, wd3s_qtag, 0x0); + goto out; + } + ct_cr_write_1(chp, wd3s_qtag, 0x0); + *chiprevp = CT_WD33C93_B; + } + +out: + (void) ct_stat_read_1(chp); + (void) ct_cr_read_1(chp, wd3s_stat); + return 0; +} + +static struct ct_synch_data * +ct_make_synch_table(ct) + struct ct_softc *ct; +{ + struct ct_synch_data *sdtp, *sdp; + u_int base, i, period; + + sdtp = sdp = &ct->sc_default_sdt[0]; + + if ((ct->sc_chipclk % 5) == 0) + base = 1000 / (5 * 2); /* 5 MHz type */ + else + base = 1000 / (4 * 2); /* 4 MHz type */ + + if (ct->sc_chiprev >= CT_WD33C93_B) + { + /* fast scsi */ + for (i = 2; i < 8; i ++, sdp ++) + { + period = (base * i) / 2; + if (period >= 200) /* 5 MHz */ + break; + sdp->cs_period = period / 4; + sdp->cs_syncr = (i * 0x10) | 0x80; + } + } + + for (i = 2; i < 8; i ++, sdp ++) + { + period = (base * i); + if (period > 500) /* 2 MHz */ + break; + sdp->cs_period = period / 4; + sdp->cs_syncr = (i * 0x10); + } + + sdp->cs_period = 0; + sdp->cs_syncr = 0; + return sdtp; +} + +/************************************************** + * Attach & Probe + **************************************************/ +int +ctprobesubr(chp, dvcfg, hsid, chipclk, chiprevp) + struct ct_bus_access_handle *chp; + u_int dvcfg, chipclk; + int hsid; + int *chiprevp; +{ + +#if 0 + if ((ct_stat_read_1(chp) & STR_BSY) != 0) + return 0; +#endif + if (cthw_chip_reset(chp, chiprevp, chipclk, hsid) != 0) + return 0; + return 1; +} + +int +ctprint(aux, name) + void *aux; + const char *name; +{ + + if (name != NULL) + printf("%s: scsibus ", name); + return UNCONF; +} + +void +ctattachsubr(ct) + struct ct_softc *ct; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + + ct->sc_tmaxcnt = SCSI_LOW_MIN_TOUT * 1000 * 1000; /* default */ + slp->sl_funcs = &ct_funcs; + slp->sl_flags |= HW_READ_PADDING; + (void) scsi_low_attach(slp, 0, CT_NTARGETS, CT_NLUNS, + sizeof(struct ct_targ_info), 0); +} + +/************************************************** + * SCSI LOW interface functions + **************************************************/ +static void +cthw_attention(ct) + struct ct_softc *ct; +{ + struct ct_bus_access_handle *chp = &ct->sc_ch; + + ct->sc_atten = 1; + if ((ct_stat_read_1(chp) & (STR_BSY | STR_CIP)) != 0) + return; + + ct_cr_write_1(chp, wd3s_cmd, WD3S_ASSERT_ATN); + SCSI_LOW_DELAY(10); + if ((ct_stat_read_1(chp) & STR_LCI) == 0) + ct->sc_atten = 0; + ct_unbusy(ct); + return; +} + +static void +ct_attention(ct) + struct ct_softc *ct; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + + if (slp->sl_atten == 0) + { + ct_unbusy(ct); + scsi_low_attention(slp); + } + else if (ct->sc_atten != 0) + { + ct_unbusy(ct); + cthw_attention(ct); + } +} + +static int +ct_targ_init(ct, ti, action) + struct ct_softc *ct; + struct targ_info *ti; + int action; +{ + struct ct_targ_info *cti = (void *) ti; + + if (action == SCSI_LOW_INFO_ALLOC || action == SCSI_LOW_INFO_REVOKE) + { + if (ct->sc_sdp == NULL) + { + ct->sc_sdp = ct_make_synch_table(ct); + } + + switch (ct->sc_chiprev) + { + default: + ti->ti_maxsynch.offset = 5; + break; + + case CT_WD33C93_A: + case CT_AM33C93_A: + ti->ti_maxsynch.offset = 12; + break; + + case CT_WD33C93_B: + case CT_WD33C93_C: + ti->ti_maxsynch.offset = 12; + break; + } + + ti->ti_maxsynch.period = ct->sc_sdp[0].cs_period; + ti->ti_width = SCSI_LOW_BUS_WIDTH_8; + cti->cti_syncreg = 0; + } + + return 0; +} + +static int +ct_world_start(ct, fdone) + struct ct_softc *ct; + int fdone; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + + if (ct->sc_sdp == NULL) + { + ct->sc_sdp = ct_make_synch_table(ct); + } + + if (slp->sl_cfgflags & CFG_NOPARITY) + ct->sc_creg = CR_DEFAULT; + else + ct->sc_creg = CR_DEFAULT_HP; + + if (ct->sc_dma & CT_DMA_DMASTART) + (*ct->ct_dma_xfer_stop) (ct); + if (ct->sc_dma & CT_DMA_PIOSTART) + (*ct->ct_pio_xfer_stop) (ct); + ct->sc_dma = 0; + ct->sc_atten = 0; + + cthw_chip_reset(chp, NULL, ct->sc_chipclk, slp->sl_hostid); + scsi_low_bus_reset(slp); + cthw_chip_reset(chp, NULL, ct->sc_chipclk, slp->sl_hostid); + + SOFT_INTR_REQUIRED(slp); + return 0; +} + +static int +ct_start_selection(ct, cb) + struct ct_softc *ct; + struct slccb *cb; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + + struct targ_info *ti = slp->sl_Tnexus; + struct lun_info *li = slp->sl_Lnexus; + int s, satok; + u_int8_t cmd; + + ct->sc_tmaxcnt = cb->ccb_tcmax * 1000 * 1000; + ct->sc_atten = 0; + satok = 0; + + if (scsi_low_is_disconnect_ok(cb) != 0) + { + if (ct->sc_chiprev >= CT_WD33C93_A) + satok = 1; + else if (cthw_cmdlevel[slp->sl_scp.scp_cmd[0]] != 0) + satok = 1; + } + + if (satok != 0 && + scsi_low_is_msgout_continue(ti, SCSI_LOW_MSG_IDENTIFY) == 0) + { + cmd = WD3S_SELECT_ATN_TFR; + ct->sc_satgo = CT_SAT_GOING; + } + else + { + cmd = WD3S_SELECT_ATN; + ct->sc_satgo = 0; + } + + if ((ct_stat_read_1(chp) & (STR_BSY | STR_INT | STR_CIP)) != 0) + return SCSI_LOW_START_FAIL; + + if ((ct->sc_satgo & CT_SAT_GOING) != 0) + { + (void) scsi_low_msgout(slp, ti, SCSI_LOW_MSGOUT_INIT); + scsi_low_cmd(slp, ti); + ct_cr_write_1(chp, wd3s_oid, slp->sl_scp.scp_cmdlen); + ct_write_cmds(chp, slp->sl_scp.scp_cmd, slp->sl_scp.scp_cmdlen); + } + else + { + /* anyway attention assert */ + SCSI_LOW_ASSERT_ATN(slp); + } + + ct_target_nexus_establish(ct, li->li_lun, slp->sl_scp.scp_direction); + + s = splhigh(); + if ((ct_stat_read_1(chp) & (STR_BSY | STR_INT | STR_CIP)) == 0) + { + /* XXX: + * Reload a lun again here. + */ + ct_cr_write_1(chp, wd3s_lun, li->li_lun); + ct_cr_write_1(chp, wd3s_cmd, cmd); + if ((ct_stat_read_1(chp) & STR_LCI) == 0) + { + splx(s); + SCSI_LOW_SETUP_PHASE(ti, PH_SELSTART); + return SCSI_LOW_START_OK; + } + } + splx(s); + return SCSI_LOW_START_FAIL; +} + +static int +ct_msg(ct, ti, msg) + struct ct_softc *ct; + struct targ_info *ti; + u_int msg; +{ + struct ct_bus_access_handle *chp = &ct->sc_ch; + struct ct_targ_info *cti = (void *) ti; + struct ct_synch_data *csp = ct->sc_sdp; + u_int offset, period; + int error; + + if ((msg & SCSI_LOW_MSG_WIDE) != 0) + { + if (ti->ti_width != SCSI_LOW_BUS_WIDTH_8) + { + ti->ti_width = SCSI_LOW_BUS_WIDTH_8; + return EINVAL; + } + return 0; + } + + if ((msg & SCSI_LOW_MSG_SYNCH) == 0) + return 0; + + offset = ti->ti_maxsynch.offset; + period = ti->ti_maxsynch.period; + for ( ; csp->cs_period != 0; csp ++) + { + if (period == csp->cs_period) + break; + } + + if (ti->ti_maxsynch.period != 0 && csp->cs_period == 0) + { + ti->ti_maxsynch.period = 0; + ti->ti_maxsynch.offset = 0; + cti->cti_syncreg = 0; + error = EINVAL; + } + else + { + cti->cti_syncreg = ((offset & 0x0f) | csp->cs_syncr); + error = 0; + } + + if (ct->ct_synch_setup != 0) + (*ct->ct_synch_setup) (ct, ti); + ct_cr_write_1(chp, wd3s_synch, cti->cti_syncreg); + return error; +} + +/************************************************* + * <DATA PHASE> + *************************************************/ +static int +ct_xfer(ct, data, len, direction, statp) + struct ct_softc *ct; + u_int8_t *data; + int len, direction; + u_int *statp; +{ + struct ct_bus_access_handle *chp = &ct->sc_ch; + int wc; + register u_int8_t aux; + + *statp = 0; + if (len == 1) + { + ct_cr_write_1(chp, wd3s_cmd, WD3S_SBT | WD3S_TFR_INFO); + } + else + { + cthw_set_count(chp, len); + ct_cr_write_1(chp, wd3s_cmd, WD3S_TFR_INFO); + } + + aux = ct_stat_read_1(chp); + if ((aux & STR_LCI) != 0) + { + cthw_set_count(chp, 0); + return len; + } + + for (wc = 0; wc < ct->sc_tmaxcnt; wc ++) + { + /* check data ready */ + if ((aux & (STR_BSY | STR_DBR)) == (STR_BSY | STR_DBR)) + { + if (direction == SCSI_LOW_READ) + { + *data = ct_cr_read_1(chp, wd3s_data); + if ((aux & STR_PE) != 0) + *statp |= SCSI_LOW_DATA_PE; + } + else + { + ct_cr_write_1(chp, wd3s_data, *data); + } + len --; + if (len <= 0) + break; + data ++; + } + else + { + SCSI_LOW_DELAY(1); + } + + /* check phase miss */ + aux = ct_stat_read_1(chp); + if ((aux & STR_INT) != 0) + break; + } + return len; +} + +#define CT_PADDING_BUF_SIZE 32 + +static void +ct_io_xfer(ct) + struct ct_softc *ct; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + struct sc_p *sp = &slp->sl_scp; + u_int stat; + int len; + u_int8_t pbuf[CT_PADDING_BUF_SIZE]; + + /* polling mode */ + ct_cr_write_1(chp, wd3s_ctrl, ct->sc_creg); + + if (sp->scp_datalen <= 0) + { + slp->sl_error |= PDMAERR; + + if (slp->sl_scp.scp_direction == SCSI_LOW_WRITE) + SCSI_LOW_BZERO(pbuf, CT_PADDING_BUF_SIZE); + ct_xfer(ct, pbuf, CT_PADDING_BUF_SIZE, + sp->scp_direction, &stat); + } + else + { + len = ct_xfer(ct, sp->scp_data, sp->scp_datalen, + sp->scp_direction, &stat); + sp->scp_data += (sp->scp_datalen - len); + sp->scp_datalen = len; + } +} + +/************************************************** + * <PHASE ERROR> + **************************************************/ +struct ct_err { + u_char *pe_msg; + u_int pe_err; + u_int pe_errmsg; + int pe_done; +}; + +struct ct_err ct_cmderr[] = { +/*0*/ { "illegal cmd", FATALIO, SCSI_LOW_MSG_ABORT, 1}, +/*1*/ { "unexpected bus free", FATALIO, 0, 1}, +/*2*/ { NULL, SELTIMEOUTIO, 0, 1}, +/*3*/ { "scsi bus parity error", PARITYERR, SCSI_LOW_MSG_ERROR, 0}, +/*4*/ { "scsi bus parity error", PARITYERR, SCSI_LOW_MSG_ERROR, 0}, +/*5*/ { "unknown" , FATALIO, SCSI_LOW_MSG_ABORT, 1}, +/*6*/ { "miss reselection (target mode)", FATALIO, SCSI_LOW_MSG_ABORT, 0}, +/*7*/ { "wrong status byte", PARITYERR, SCSI_LOW_MSG_ERROR, 0}, +}; + +static void +ct_phase_error(ct, scsi_status) + struct ct_softc *ct; + u_int8_t scsi_status; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct targ_info *ti = slp->sl_Tnexus; + struct ct_err *pep; + u_int msg = 0; + + if ((scsi_status & BSR_CM) == BSR_CMDERR && + (scsi_status & BSR_PHVALID) == 0) + { + pep = &ct_cmderr[scsi_status & BSR_PM]; + slp->sl_error |= pep->pe_err; + if ((pep->pe_err & PARITYERR) != 0) + { + if (ti->ti_phase == PH_MSGIN) + msg = SCSI_LOW_MSG_PARITY; + else + msg = SCSI_LOW_MSG_ERROR; + } + else + msg = pep->pe_errmsg; + + if (msg != 0) + scsi_low_assert_msg(slp, slp->sl_Tnexus, msg, 1); + + if (pep->pe_msg != NULL) + { + printf("%s: phase error: %s", + slp->sl_xname, pep->pe_msg); + scsi_low_print(slp, slp->sl_Tnexus); + } + + if (pep->pe_done != 0) + scsi_low_disconnected(slp, ti); + } + else + { + slp->sl_error |= FATALIO; + scsi_low_restart(slp, SCSI_LOW_RESTART_HARD, "phase error"); + } +} + +/************************************************** + * ### SCSI PHASE SEQUENCER ### + **************************************************/ +static int +ct_reselected(ct, scsi_status) + struct ct_softc *ct; + u_int8_t scsi_status; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + struct targ_info *ti; + u_int sid; + u_int8_t regv; + + ct->sc_atten = 0; + ct->sc_satgo &= ~CT_SAT_GOING; + regv = ct_cr_read_1(chp, wd3s_sid); + if ((regv & SIDR_VALID) == 0) + return EJUSTRETURN; + + sid = regv & SIDR_IDM; + if ((ti = scsi_low_reselected(slp, sid)) == NULL) + return EJUSTRETURN; + + ct_target_nexus_establish(ct, 0, SCSI_LOW_READ); + if (scsi_status != BSR_AFM_RESEL) + return EJUSTRETURN; + + SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN); + regv = ct_cr_read_1(chp, wd3s_data); + if (scsi_low_msgin(slp, ti, (u_int) regv) == 0) + { + if (scsi_low_is_msgout_continue(ti, 0) != 0) + { + /* XXX: scsi_low_attetion */ + scsi_low_attention(slp); + } + } + + if (ct->sc_atten != 0) + { + ct_attention(ct); + } + + ct_cr_write_1(chp, wd3s_cmd, WD3S_NEGATE_ACK); + return EJUSTRETURN; +} + +static int +ct_target_nexus_establish(ct, lun, dir) + struct ct_softc *ct; + int lun, dir; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + struct targ_info *ti = slp->sl_Tnexus; + struct ct_targ_info *cti = (void *) ti; + + if (dir == SCSI_LOW_WRITE) + ct_cr_write_1(chp, wd3s_did, ti->ti_id); + else + ct_cr_write_1(chp, wd3s_did, ti->ti_id | DIDR_DPD); + ct_cr_write_1(chp, wd3s_lun, lun); + ct_cr_write_1(chp, wd3s_ctrl, ct->sc_creg | CR_DMA); + ct_cr_write_1(chp, wd3s_cph, 0); + ct_cr_write_1(chp, wd3s_synch, cti->cti_syncreg); + cthw_set_count(chp, 0); + return 0; +} + +static int +ct_lun_nexus_establish(ct) + struct ct_softc *ct; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + struct lun_info *li = slp->sl_Lnexus; + + ct_cr_write_1(chp, wd3s_lun, li->li_lun); + return 0; +} + +static int +ct_ccb_nexus_establish(ct) + struct ct_softc *ct; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + struct lun_info *li = slp->sl_Lnexus; + struct targ_info *ti = slp->sl_Tnexus; + struct ct_targ_info *cti = (void *) ti; + struct slccb *cb = slp->sl_Qnexus; + + ct->sc_tmaxcnt = cb->ccb_tcmax * 1000 * 1000; + + if ((ct->sc_satgo & CT_SAT_GOING) != 0) + { + ct_cr_write_1(chp, wd3s_oid, slp->sl_scp.scp_cmdlen); + ct_write_cmds(chp, slp->sl_scp.scp_cmd, slp->sl_scp.scp_cmdlen); + } + if (slp->sl_scp.scp_direction == SCSI_LOW_WRITE) + ct_cr_write_1(chp, wd3s_did, ti->ti_id); + else + ct_cr_write_1(chp, wd3s_did, ti->ti_id | DIDR_DPD); + ct_cr_write_1(chp, wd3s_lun, li->li_lun); + ct_cr_write_1(chp, wd3s_synch, cti->cti_syncreg); + return 0; +} + +static int +ct_unbusy(ct) + struct ct_softc *ct; +{ + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + int wc; + register u_int8_t regv; + + for (wc = 0; wc < CT_DELAY_MAX / CT_DELAY_INTERVAL; wc ++) + { + regv = ct_stat_read_1(chp); + if ((regv & (STR_BSY | STR_CIP)) == 0) + return 0; + if (regv == (u_int8_t) -1) + return EIO; + + SCSI_LOW_DELAY(CT_DELAY_INTERVAL); + } + + printf("%s: unbusy timeout\n", slp->sl_xname); + return EBUSY; +} + +static int +ct_catch_intr(ct) + struct ct_softc *ct; +{ + struct ct_bus_access_handle *chp = &ct->sc_ch; + int wc; + register u_int8_t regv; + + for (wc = 0; wc < CT_DELAY_MAX / CT_DELAY_INTERVAL; wc ++) + { + regv = ct_stat_read_1(chp); + if ((regv & (STR_INT | STR_BSY | STR_CIP)) == STR_INT) + return 0; + + SCSI_LOW_DELAY(CT_DELAY_INTERVAL); + } + return EJUSTRETURN; +} + +int +ctintr(arg) + void *arg; +{ + struct ct_softc *ct = arg; + struct scsi_low_softc *slp = &ct->sc_sclow; + struct ct_bus_access_handle *chp = &ct->sc_ch; + struct targ_info *ti; + struct physio_proc *pp; + struct buf *bp; + u_int derror, flags; + int len, satgo, error; + u_int8_t scsi_status, regv; + +again: + if (slp->sl_flags & HW_INACTIVE) + return 0; + + /************************************************** + * Get status & bus phase + **************************************************/ + if ((ct_stat_read_1(chp) & STR_INT) == 0) + return 0; + + scsi_status = ct_cr_read_1(chp, wd3s_stat); + if (scsi_status == ((u_int8_t) -1)) + return 1; + + /************************************************** + * Check reselection, or nexus + **************************************************/ + if (scsi_status == BSR_RESEL || scsi_status == BSR_AFM_RESEL) + { + if (ct_reselected(ct, scsi_status) == EJUSTRETURN) + return 1; + } + + if ((ti = slp->sl_Tnexus) == NULL) + return 1; + + /************************************************** + * Debug section + **************************************************/ +#ifdef CT_DEBUG + if (ct_debug > 0) + { + scsi_low_print(slp, NULL); + printf("%s: scsi_status 0x%x\n\n", slp->sl_xname, + (u_int) scsi_status); +#ifdef DDB + if (ct_debug > 1) + SCSI_LOW_DEBUGGER("ct"); +#endif /* DDB */ + } +#endif /* CT_DEBUG */ + + /************************************************** + * Internal scsi phase + **************************************************/ + satgo = ct->sc_satgo; + ct->sc_satgo &= ~CT_SAT_GOING; + + switch (ti->ti_phase) + { + case PH_SELSTART: + if ((satgo & CT_SAT_GOING) == 0) + { + if (scsi_status != BSR_SELECTED) + { + ct_phase_error(ct, scsi_status); + return 1; + } + scsi_low_arbit_win(slp); + SCSI_LOW_SETUP_PHASE(ti, PH_SELECTED); + return 1; + } + else + { + scsi_low_arbit_win(slp); + SCSI_LOW_SETUP_PHASE(ti, PH_MSGOUT); /* XXX */ + } + break; + + case PH_RESEL: + if ((scsi_status & BSR_PHVALID) == 0 || + (scsi_status & BSR_PM) != BSR_MSGIN) + { + scsi_low_restart(slp, SCSI_LOW_RESTART_HARD, + "phase miss after reselect"); + return 1; + } + break; + + default: + if (slp->sl_flags & HW_PDMASTART) + { + slp->sl_flags &= ~HW_PDMASTART; + if (ct->sc_dma & CT_DMA_DMASTART) + { + (*ct->ct_dma_xfer_stop) (ct); + ct->sc_dma &= ~CT_DMA_DMASTART; + } + else if (ct->sc_dma & CT_DMA_PIOSTART) + { + (*ct->ct_pio_xfer_stop) (ct); + ct->sc_dma &= ~CT_DMA_PIOSTART; + } + else + { + scsi_low_data_finish(slp); + } + } + break; + } + + /************************************************** + * parse scsi phase + **************************************************/ + if (scsi_status & BSR_PHVALID) + { + /************************************************** + * Normal SCSI phase. + **************************************************/ + if ((scsi_status & BSR_CM) == BSR_CMDABT) + { + ct_phase_error(ct, scsi_status); + return 1; + } + + switch (scsi_status & BSR_PM) + { + case BSR_DATAOUT: + SCSI_LOW_SETUP_PHASE(ti, PH_DATA); + if (scsi_low_data(slp, ti, &bp, SCSI_LOW_WRITE) != 0) + { + ct_attention(ct); + } + goto common_data_phase; + + case BSR_DATAIN: + SCSI_LOW_SETUP_PHASE(ti, PH_DATA); + if (scsi_low_data(slp, ti, &bp, SCSI_LOW_READ) != 0) + { + ct_attention(ct); + } + +common_data_phase: + if (slp->sl_scp.scp_datalen > 0) + { + slp->sl_flags |= HW_PDMASTART; + if ((ct->sc_xmode & CT_XMODE_PIO) != 0) + { + pp = physio_proc_enter(bp); + error = (*ct->ct_pio_xfer_start) (ct); + physio_proc_leave(pp); + if (error == 0) + { + ct->sc_dma |= CT_DMA_PIOSTART; + return 1; + } + } + + if ((ct->sc_xmode & CT_XMODE_DMA) != 0) + { + error = (*ct->ct_dma_xfer_start) (ct); + if (error == 0) + { + ct->sc_dma |= CT_DMA_DMASTART; + return 1; + } + } + } + else + { + if (slp->sl_scp.scp_direction == SCSI_LOW_READ) + { + if (!(slp->sl_flags & HW_READ_PADDING)) + { + printf("%s: read padding required\n", slp->sl_xname); + return 1; + } + } + else + { + if (!(slp->sl_flags & HW_WRITE_PADDING)) + { + printf("%s: write padding required\n", slp->sl_xname); + return 1; + } + } + slp->sl_flags |= HW_PDMASTART; + } + + ct_io_xfer(ct); + return 1; + + case BSR_CMDOUT: + SCSI_LOW_SETUP_PHASE(ti, PH_CMD); + if (scsi_low_cmd(slp, ti) != 0) + { + ct_attention(ct); + } + + if (ct_xfer(ct, slp->sl_scp.scp_cmd, + slp->sl_scp.scp_cmdlen, + SCSI_LOW_WRITE, &derror) != 0) + { + printf("%s: scsi cmd xfer short\n", + slp->sl_xname); + } + return 1; + + case BSR_STATIN: + SCSI_LOW_SETUP_PHASE(ti, PH_STAT); + if ((ct_io_control & CT_USE_CCSEQ) != 0) + { + if (scsi_low_is_msgout_continue(ti, 0) != 0 || + ct->sc_atten != 0) + { + ct_xfer(ct, ®v, 1, SCSI_LOW_READ, + &derror); + scsi_low_statusin(slp, ti, + regv | derror); + } + else + { + ct->sc_satgo |= CT_SAT_GOING; + cthw_set_count(chp, 0); + cthw_phase_bypass(ct, 0x41); + } + } + else + { + ct_xfer(ct, ®v, 1, SCSI_LOW_READ, &derror); + scsi_low_statusin(slp, ti, regv | derror); + } + return 1; + + case BSR_UNSPINFO0: + case BSR_UNSPINFO1: + printf("%s: illegal bus phase (0x%x)\n", slp->sl_xname, + (u_int) scsi_status); + scsi_low_print(slp, ti); + return 1; + + case BSR_MSGOUT: + SCSI_LOW_SETUP_PHASE(ti, PH_MSGOUT); + flags = SCSI_LOW_MSGOUT_UNIFY; + if (ti->ti_ophase != ti->ti_phase) + flags |= SCSI_LOW_MSGOUT_INIT; + len = scsi_low_msgout(slp, ti, flags); + + if (len > 1 && slp->sl_atten == 0) + { + ct_attention(ct); + } + + if (ct_xfer(ct, ti->ti_msgoutstr, len, + SCSI_LOW_WRITE, &derror) != 0) + { + printf("%s: scsi msgout xfer short\n", + slp->sl_xname); + } + SCSI_LOW_DEASSERT_ATN(slp); + ct->sc_atten = 0; + return 1; + + case BSR_MSGIN:/* msg in */ + SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN); + + ct_xfer(ct, ®v, 1, SCSI_LOW_READ, &derror); + if (scsi_low_msgin(slp, ti, regv | derror) == 0) + { + if (scsi_low_is_msgout_continue(ti, 0) != 0) + { + /* XXX: scsi_low_attetion */ + scsi_low_attention(slp); + } + } + + if ((ct_io_control & CT_FAST_INTR) != 0) + { + if (ct_catch_intr(ct) == 0) + goto again; + } + return 1; + } + } + else + { + /************************************************** + * Special SCSI phase + **************************************************/ + switch (scsi_status) + { + case BSR_SATSDP: /* SAT with save data pointer */ + SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN); + ct->sc_satgo |= CT_SAT_GOING; + scsi_low_msgin(slp, ti, MSG_SAVESP); + cthw_phase_bypass(ct, 0x41); + return 1; + + case BSR_SATFIN: /* SAT COMPLETE */ + /* + * emulate statusin => msgin + */ + SCSI_LOW_SETUP_PHASE(ti, PH_STAT); + scsi_low_statusin(slp, ti, ct_cr_read_1(chp, wd3s_lun)); + + SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN); + scsi_low_msgin(slp, ti, MSG_COMP); + + scsi_low_disconnected(slp, ti); + return 1; + + case BSR_ACKREQ: /* negate ACK */ + if (ct->sc_atten != 0) + { + ct_attention(ct); + } + + ct_cr_write_1(chp, wd3s_cmd, WD3S_NEGATE_ACK); + if ((ct_io_control & CT_FAST_INTR) != 0) + { + /* XXX: + * Should clear a pending interrupt and + * sync with a next interrupt! + */ + ct_catch_intr(ct); + } + return 1; + + case BSR_DISC: /* disconnect */ + if (slp->sl_msgphase == MSGPH_NULL && + (satgo & CT_SAT_GOING) != 0) + { + /* + * emulate disconnect msg + */ + SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN); + scsi_low_msgin(slp, ti, MSG_DISCON); + } + scsi_low_disconnected(slp, ti); + return 1; + + default: + break; + } + } + + ct_phase_error(ct, scsi_status); + return 1; +} |