/* * Low level routines for the Advanced Systems Inc. SCSI controllers chips * * Copyright (c) 1996-1997, 1999-2000 Justin 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, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 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 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. * * $FreeBSD: src/sys/dev/advansys/advlib.c,v 1.15.2.1 2000/04/14 13:32:49 nyan Exp $ */ /* * Ported from: * advansys.c - Linux Host Driver for AdvanSys SCSI Adapters * * Copyright (c) 1995-1996 Advanced System Products, Inc. * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that redistributions of source * code retain the above copyright notice and this comment without * modification. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "advansys.h" #include "advmcode.h" struct adv_quirk_entry { struct scsi_inquiry_pattern inq_pat; u_int8_t quirks; #define ADV_QUIRK_FIX_ASYN_XFER_ALWAYS 0x01 #define ADV_QUIRK_FIX_ASYN_XFER 0x02 }; static struct adv_quirk_entry adv_quirk_table[] = { { { T_CDROM, SIP_MEDIA_REMOVABLE, "HP", "*", "*" }, ADV_QUIRK_FIX_ASYN_XFER_ALWAYS|ADV_QUIRK_FIX_ASYN_XFER }, { { T_CDROM, SIP_MEDIA_REMOVABLE, "NEC", "CD-ROM DRIVE", "*" }, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "TANDBERG", " TDC 36", "*" }, 0 }, { { T_SEQUENTIAL, SIP_MEDIA_REMOVABLE, "WANGTEK", "*", "*" }, 0 }, { { T_PROCESSOR, SIP_MEDIA_REMOVABLE|SIP_MEDIA_FIXED, "*", "*", "*" }, 0 }, { { T_SCANNER, SIP_MEDIA_REMOVABLE|SIP_MEDIA_FIXED, "*", "*", "*" }, 0 }, { /* Default quirk entry */ { T_ANY, SIP_MEDIA_REMOVABLE|SIP_MEDIA_FIXED, /*vendor*/"*", /*product*/"*", /*revision*/"*" }, ADV_QUIRK_FIX_ASYN_XFER, } }; /* * Allowable periods in ns */ static u_int8_t adv_sdtr_period_tbl[] = { 25, 30, 35, 40, 50, 60, 70, 85 }; static u_int8_t adv_sdtr_period_tbl_ultra[] = { 12, 19, 25, 32, 38, 44, 50, 57, 63, 69, 75, 82, 88, 94, 100, 107 }; struct ext_msg { u_int8_t msg_type; u_int8_t msg_len; u_int8_t msg_req; union { struct { u_int8_t sdtr_xfer_period; u_int8_t sdtr_req_ack_offset; } sdtr; struct { u_int8_t wdtr_width; } wdtr; struct { u_int8_t mdp[4]; } mdp; } u_ext_msg; u_int8_t res; }; #define xfer_period u_ext_msg.sdtr.sdtr_xfer_period #define req_ack_offset u_ext_msg.sdtr.sdtr_req_ack_offset #define wdtr_width u_ext_msg.wdtr.wdtr_width #define mdp_b3 u_ext_msg.mdp_b3 #define mdp_b2 u_ext_msg.mdp_b2 #define mdp_b1 u_ext_msg.mdp_b1 #define mdp_b0 u_ext_msg.mdp_b0 /* * Some of the early PCI adapters have problems with * async transfers. Instead use an offset of 1. */ #define ASYN_SDTR_DATA_FIX_PCI_REV_AB 0x41 /* LRAM routines */ static void adv_read_lram_16_multi(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *buffer, int count); static void adv_write_lram_16_multi(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *buffer, int count); static void adv_mset_lram_16(struct adv_softc *adv, u_int16_t s_addr, u_int16_t set_value, int count); static u_int32_t adv_msum_lram_16(struct adv_softc *adv, u_int16_t s_addr, int count); static int adv_write_and_verify_lram_16(struct adv_softc *adv, u_int16_t addr, u_int16_t value); static u_int32_t adv_read_lram_32(struct adv_softc *adv, u_int16_t addr); static void adv_write_lram_32(struct adv_softc *adv, u_int16_t addr, u_int32_t value); static void adv_write_lram_32_multi(struct adv_softc *adv, u_int16_t s_addr, u_int32_t *buffer, int count); /* EEPROM routines */ static u_int16_t adv_read_eeprom_16(struct adv_softc *adv, u_int8_t addr); static u_int16_t adv_write_eeprom_16(struct adv_softc *adv, u_int8_t addr, u_int16_t value); static int adv_write_eeprom_cmd_reg(struct adv_softc *adv, u_int8_t cmd_reg); static int adv_set_eeprom_config_once(struct adv_softc *adv, struct adv_eeprom_config *eeconfig); /* Initialization */ static u_int32_t adv_load_microcode(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *mcode_buf, u_int16_t mcode_size); static void adv_reinit_lram(struct adv_softc *adv); static void adv_init_lram(struct adv_softc *adv); static int adv_init_microcode_var(struct adv_softc *adv); static void adv_init_qlink_var(struct adv_softc *adv); /* Interrupts */ static void adv_disable_interrupt(struct adv_softc *adv); static void adv_enable_interrupt(struct adv_softc *adv); static void adv_toggle_irq_act(struct adv_softc *adv); /* Chip Control */ static int adv_host_req_chip_halt(struct adv_softc *adv); static void adv_set_chip_ih(struct adv_softc *adv, u_int16_t ins_code); #if 0 /* UNUSED */ static u_int8_t adv_get_chip_scsi_ctrl(struct adv_softc *adv); #endif /* Queue handling and execution */ static __inline int adv_sgcount_to_qcount(int sgcount); static __inline int adv_sgcount_to_qcount(int sgcount) { int n_sg_list_qs; n_sg_list_qs = ((sgcount - 1) / ADV_SG_LIST_PER_Q); if (((sgcount - 1) % ADV_SG_LIST_PER_Q) != 0) n_sg_list_qs++; return (n_sg_list_qs + 1); } static void adv_get_q_info(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *inbuf, int words); static u_int adv_get_num_free_queues(struct adv_softc *adv, u_int8_t n_qs); static u_int8_t adv_alloc_free_queues(struct adv_softc *adv, u_int8_t free_q_head, u_int8_t n_free_q); static u_int8_t adv_alloc_free_queue(struct adv_softc *adv, u_int8_t free_q_head); static int adv_send_scsi_queue(struct adv_softc *adv, struct adv_scsi_q *scsiq, u_int8_t n_q_required); static void adv_put_ready_sg_list_queue(struct adv_softc *adv, struct adv_scsi_q *scsiq, u_int q_no); static void adv_put_ready_queue(struct adv_softc *adv, struct adv_scsi_q *scsiq, u_int q_no); static void adv_put_scsiq(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *buffer, int words); /* Messages */ static void adv_handle_extmsg_in(struct adv_softc *adv, u_int16_t halt_q_addr, u_int8_t q_cntl, target_bit_vector target_id, int tid); static void adv_msgout_sdtr(struct adv_softc *adv, u_int8_t sdtr_period, u_int8_t sdtr_offset); static void adv_set_sdtr_reg_at_id(struct adv_softc *adv, int id, u_int8_t sdtr_data); /* Exported functions first */ void advasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct adv_softc *adv; adv = (struct adv_softc *)callback_arg; switch (code) { case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; target_bit_vector target_mask; int num_entries; caddr_t match; struct adv_quirk_entry *entry; struct adv_target_transinfo* tinfo; cgd = (struct ccb_getdev *)arg; target_mask = ADV_TID_TO_TARGET_MASK(cgd->ccb_h.target_id); num_entries = NELEM(adv_quirk_table); match = cam_quirkmatch((caddr_t)&cgd->inq_data, (caddr_t)adv_quirk_table, num_entries, sizeof(*adv_quirk_table), scsi_inquiry_match); if (match == NULL) panic("advasync: device didn't match wildcard entry!!"); entry = (struct adv_quirk_entry *)match; if (adv->bug_fix_control & ADV_BUG_FIX_ASYN_USE_SYN) { if ((entry->quirks & ADV_QUIRK_FIX_ASYN_XFER_ALWAYS)!=0) adv->fix_asyn_xfer_always |= target_mask; else adv->fix_asyn_xfer_always &= ~target_mask; /* * We start out life with all bits set and clear them * after we've determined that the fix isn't necessary. * It may well be that we've already cleared a target * before the full inquiry session completes, so don't * gratuitously set a target bit even if it has this * quirk. But, if the quirk exonerates a device, clear * the bit now. */ if ((entry->quirks & ADV_QUIRK_FIX_ASYN_XFER) == 0) adv->fix_asyn_xfer &= ~target_mask; } /* * Reset our sync settings now that we've determined * what quirks are in effect for the device. */ tinfo = &adv->tinfo[cgd->ccb_h.target_id]; adv_set_syncrate(adv, cgd->ccb_h.path, cgd->ccb_h.target_id, tinfo->current.period, tinfo->current.offset, ADV_TRANS_CUR); break; } case AC_LOST_DEVICE: { u_int target_mask; if (adv->bug_fix_control & ADV_BUG_FIX_ASYN_USE_SYN) { target_mask = 0x01 << xpt_path_target_id(path); adv->fix_asyn_xfer |= target_mask; } /* * Revert to async transfers * for the next device. */ adv_set_syncrate(adv, /*path*/NULL, xpt_path_target_id(path), /*period*/0, /*offset*/0, ADV_TRANS_GOAL|ADV_TRANS_CUR); } default: break; } } void adv_set_bank(struct adv_softc *adv, u_int8_t bank) { u_int8_t control; /* * Start out with the bank reset to 0 */ control = ADV_INB(adv, ADV_CHIP_CTRL) & (~(ADV_CC_SINGLE_STEP | ADV_CC_TEST | ADV_CC_DIAG | ADV_CC_SCSI_RESET | ADV_CC_CHIP_RESET | ADV_CC_BANK_ONE)); if (bank == 1) { control |= ADV_CC_BANK_ONE; } else if (bank == 2) { control |= ADV_CC_DIAG | ADV_CC_BANK_ONE; } ADV_OUTB(adv, ADV_CHIP_CTRL, control); } u_int8_t adv_read_lram_8(struct adv_softc *adv, u_int16_t addr) { u_int8_t byte_data; u_int16_t word_data; /* * LRAM is accessed on 16bit boundaries. */ ADV_OUTW(adv, ADV_LRAM_ADDR, addr & 0xFFFE); word_data = ADV_INW(adv, ADV_LRAM_DATA); if (addr & 1) { #if BYTE_ORDER == BIG_ENDIAN byte_data = (u_int8_t)(word_data & 0xFF); #else byte_data = (u_int8_t)((word_data >> 8) & 0xFF); #endif } else { #if BYTE_ORDER == BIG_ENDIAN byte_data = (u_int8_t)((word_data >> 8) & 0xFF); #else byte_data = (u_int8_t)(word_data & 0xFF); #endif } return (byte_data); } void adv_write_lram_8(struct adv_softc *adv, u_int16_t addr, u_int8_t value) { u_int16_t word_data; word_data = adv_read_lram_16(adv, addr & 0xFFFE); if (addr & 1) { word_data &= 0x00FF; word_data |= (((u_int8_t)value << 8) & 0xFF00); } else { word_data &= 0xFF00; word_data |= ((u_int8_t)value & 0x00FF); } adv_write_lram_16(adv, addr & 0xFFFE, word_data); } u_int16_t adv_read_lram_16(struct adv_softc *adv, u_int16_t addr) { ADV_OUTW(adv, ADV_LRAM_ADDR, addr); return (ADV_INW(adv, ADV_LRAM_DATA)); } void adv_write_lram_16(struct adv_softc *adv, u_int16_t addr, u_int16_t value) { ADV_OUTW(adv, ADV_LRAM_ADDR, addr); ADV_OUTW(adv, ADV_LRAM_DATA, value); } /* * Determine if there is a board at "iobase" by looking * for the AdvanSys signatures. Return 1 if a board is * found, 0 otherwise. */ int adv_find_signature(bus_space_tag_t tag, bus_space_handle_t bsh) { u_int16_t signature; if (bus_space_read_1(tag, bsh, ADV_SIGNATURE_BYTE) == ADV_1000_ID1B) { signature = bus_space_read_2(tag, bsh, ADV_SIGNATURE_WORD); if ((signature == ADV_1000_ID0W) || (signature == ADV_1000_ID0W_FIX)) return (1); } return (0); } void adv_lib_init(struct adv_softc *adv) { if ((adv->type & ADV_ULTRA) != 0) { adv->sdtr_period_tbl = adv_sdtr_period_tbl_ultra; adv->sdtr_period_tbl_size = sizeof(adv_sdtr_period_tbl_ultra); } else { adv->sdtr_period_tbl = adv_sdtr_period_tbl; adv->sdtr_period_tbl_size = sizeof(adv_sdtr_period_tbl); } } u_int16_t adv_get_eeprom_config(struct adv_softc *adv, struct adv_eeprom_config *eeprom_config) { u_int16_t sum; u_int16_t *wbuf; u_int8_t cfg_beg; u_int8_t cfg_end; u_int8_t s_addr; wbuf = (u_int16_t *)eeprom_config; sum = 0; for (s_addr = 0; s_addr < 2; s_addr++, wbuf++) { *wbuf = adv_read_eeprom_16(adv, s_addr); sum += *wbuf; } if (adv->type & ADV_VL) { cfg_beg = ADV_EEPROM_CFG_BEG_VL; cfg_end = ADV_EEPROM_MAX_ADDR_VL; } else { cfg_beg = ADV_EEPROM_CFG_BEG; cfg_end = ADV_EEPROM_MAX_ADDR; } for (s_addr = cfg_beg; s_addr <= (cfg_end - 1); s_addr++, wbuf++) { *wbuf = adv_read_eeprom_16(adv, s_addr); sum += *wbuf; #ifdef ADV_DEBUG_EEPROM kprintf("Addr 0x%x: 0x%04x\n", s_addr, *wbuf); #endif } *wbuf = adv_read_eeprom_16(adv, s_addr); return (sum); } int adv_set_eeprom_config(struct adv_softc *adv, struct adv_eeprom_config *eeprom_config) { int retry; retry = 0; while (1) { if (adv_set_eeprom_config_once(adv, eeprom_config) == 0) { break; } if (++retry > ADV_EEPROM_MAX_RETRY) { break; } } return (retry > ADV_EEPROM_MAX_RETRY); } int adv_reset_chip(struct adv_softc *adv, int reset_bus) { adv_stop_chip(adv); ADV_OUTB(adv, ADV_CHIP_CTRL, ADV_CC_CHIP_RESET | ADV_CC_HALT | (reset_bus ? ADV_CC_SCSI_RESET : 0)); DELAY(60); adv_set_chip_ih(adv, ADV_INS_RFLAG_WTM); adv_set_chip_ih(adv, ADV_INS_HALT); if (reset_bus) ADV_OUTB(adv, ADV_CHIP_CTRL, ADV_CC_CHIP_RESET | ADV_CC_HALT); ADV_OUTB(adv, ADV_CHIP_CTRL, ADV_CC_HALT); if (reset_bus) DELAY(200 * 1000); ADV_OUTW(adv, ADV_CHIP_STATUS, ADV_CIW_CLR_SCSI_RESET_INT); ADV_OUTW(adv, ADV_CHIP_STATUS, 0); return (adv_is_chip_halted(adv)); } int adv_test_external_lram(struct adv_softc* adv) { u_int16_t q_addr; u_int16_t saved_value; int success; success = 0; q_addr = ADV_QNO_TO_QADDR(241); saved_value = adv_read_lram_16(adv, q_addr); if (adv_write_and_verify_lram_16(adv, q_addr, 0x55AA) == 0) { success = 1; adv_write_lram_16(adv, q_addr, saved_value); } return (success); } int adv_init_lram_and_mcode(struct adv_softc *adv) { u_int32_t retval; adv_disable_interrupt(adv); adv_init_lram(adv); retval = adv_load_microcode(adv, 0, (u_int16_t *)adv_mcode, adv_mcode_size); if (retval != adv_mcode_chksum) { kprintf("adv%d: Microcode download failed checksum!\n", adv->unit); return (1); } if (adv_init_microcode_var(adv) != 0) return (1); adv_enable_interrupt(adv); return (0); } u_int8_t adv_get_chip_irq(struct adv_softc *adv) { u_int16_t cfg_lsw; u_int8_t chip_irq; cfg_lsw = ADV_INW(adv, ADV_CONFIG_LSW); if ((adv->type & ADV_VL) != 0) { chip_irq = (u_int8_t)(((cfg_lsw >> 2) & 0x07)); if ((chip_irq == 0) || (chip_irq == 4) || (chip_irq == 7)) { return (0); } return (chip_irq + (ADV_MIN_IRQ_NO - 1)); } chip_irq = (u_int8_t)(((cfg_lsw >> 2) & 0x03)); if (chip_irq == 3) chip_irq += 2; return (chip_irq + ADV_MIN_IRQ_NO); } u_int8_t adv_set_chip_irq(struct adv_softc *adv, u_int8_t irq_no) { u_int16_t cfg_lsw; if ((adv->type & ADV_VL) != 0) { if (irq_no != 0) { if ((irq_no < ADV_MIN_IRQ_NO) || (irq_no > ADV_MAX_IRQ_NO)) { irq_no = 0; } else { irq_no -= ADV_MIN_IRQ_NO - 1; } } cfg_lsw = ADV_INW(adv, ADV_CONFIG_LSW) & 0xFFE3; cfg_lsw |= 0x0010; ADV_OUTW(adv, ADV_CONFIG_LSW, cfg_lsw); adv_toggle_irq_act(adv); cfg_lsw = ADV_INW(adv, ADV_CONFIG_LSW) & 0xFFE0; cfg_lsw |= (irq_no & 0x07) << 2; ADV_OUTW(adv, ADV_CONFIG_LSW, cfg_lsw); adv_toggle_irq_act(adv); } else if ((adv->type & ADV_ISA) != 0) { if (irq_no == 15) irq_no -= 2; irq_no -= ADV_MIN_IRQ_NO; cfg_lsw = ADV_INW(adv, ADV_CONFIG_LSW) & 0xFFF3; cfg_lsw |= (irq_no & 0x03) << 2; ADV_OUTW(adv, ADV_CONFIG_LSW, cfg_lsw); } return (adv_get_chip_irq(adv)); } void adv_set_chip_scsiid(struct adv_softc *adv, int new_id) { u_int16_t cfg_lsw; cfg_lsw = ADV_INW(adv, ADV_CONFIG_LSW); if (ADV_CONFIG_SCSIID(cfg_lsw) == new_id) return; cfg_lsw &= ~ADV_CFG_LSW_SCSIID; cfg_lsw |= (new_id & ADV_MAX_TID) << ADV_CFG_LSW_SCSIID_SHIFT; ADV_OUTW(adv, ADV_CONFIG_LSW, cfg_lsw); } int adv_execute_scsi_queue(struct adv_softc *adv, struct adv_scsi_q *scsiq, u_int32_t datalen) { struct adv_target_transinfo* tinfo; u_int32_t *p_data_addr; u_int32_t *p_data_bcount; int disable_syn_offset_one_fix; int retval; u_int n_q_required; u_int32_t addr; u_int8_t sg_entry_cnt; u_int8_t target_ix; u_int8_t sg_entry_cnt_minus_one; u_int8_t tid_no; scsiq->q1.q_no = 0; retval = 1; /* Default to error case */ target_ix = scsiq->q2.target_ix; tid_no = ADV_TIX_TO_TID(target_ix); tinfo = &adv->tinfo[tid_no]; if (scsiq->cdbptr[0] == REQUEST_SENSE) { /* Renegotiate if appropriate. */ adv_set_syncrate(adv, /*struct cam_path */NULL, tid_no, /*period*/0, /*offset*/0, ADV_TRANS_CUR); if (tinfo->current.period != tinfo->goal.period) { adv_msgout_sdtr(adv, tinfo->goal.period, tinfo->goal.offset); scsiq->q1.cntl |= (QC_MSG_OUT | QC_URGENT); } } if ((scsiq->q1.cntl & QC_SG_HEAD) != 0) { sg_entry_cnt = scsiq->sg_head->entry_cnt; sg_entry_cnt_minus_one = sg_entry_cnt - 1; #ifdef DIAGNOSTIC if (sg_entry_cnt <= 1) panic("adv_execute_scsi_queue: Queue " "with QC_SG_HEAD set but %d segs.", sg_entry_cnt); if (sg_entry_cnt > ADV_MAX_SG_LIST) panic("adv_execute_scsi_queue: " "Queue with too many segs."); if ((adv->type & (ADV_ISA | ADV_VL | ADV_EISA)) != 0) { int i; for (i = 0; i < sg_entry_cnt_minus_one; i++) { addr = scsiq->sg_head->sg_list[i].addr + scsiq->sg_head->sg_list[i].bytes; if ((addr & 0x0003) != 0) panic("adv_execute_scsi_queue: SG " "with odd address or byte count"); } } #endif p_data_addr = &scsiq->sg_head->sg_list[sg_entry_cnt_minus_one].addr; p_data_bcount = &scsiq->sg_head->sg_list[sg_entry_cnt_minus_one].bytes; n_q_required = adv_sgcount_to_qcount(sg_entry_cnt); scsiq->sg_head->queue_cnt = n_q_required - 1; } else { p_data_addr = &scsiq->q1.data_addr; p_data_bcount = &scsiq->q1.data_cnt; n_q_required = 1; } disable_syn_offset_one_fix = FALSE; if ((adv->fix_asyn_xfer & scsiq->q1.target_id) != 0 && (adv->fix_asyn_xfer_always & scsiq->q1.target_id) == 0) { if (datalen != 0) { if (datalen < 512) { disable_syn_offset_one_fix = TRUE; } else { if (scsiq->cdbptr[0] == INQUIRY || scsiq->cdbptr[0] == REQUEST_SENSE || scsiq->cdbptr[0] == READ_CAPACITY || scsiq->cdbptr[0] == MODE_SELECT_6 || scsiq->cdbptr[0] == MODE_SENSE_6 || scsiq->cdbptr[0] == MODE_SENSE_10 || scsiq->cdbptr[0] == MODE_SELECT_10 || scsiq->cdbptr[0] == READ_TOC) { disable_syn_offset_one_fix = TRUE; } } } } if (disable_syn_offset_one_fix) { scsiq->q2.tag_code &= ~(MSG_SIMPLE_Q_TAG|MSG_HEAD_OF_Q_TAG|MSG_ORDERED_Q_TAG); scsiq->q2.tag_code |= (ADV_TAG_FLAG_DISABLE_ASYN_USE_SYN_FIX | ADV_TAG_FLAG_DISABLE_DISCONNECT); } if ((adv->bug_fix_control & ADV_BUG_FIX_IF_NOT_DWB) != 0 && (scsiq->cdbptr[0] == READ_10 || scsiq->cdbptr[0] == READ_6)) { u_int8_t extra_bytes; addr = *p_data_addr + *p_data_bcount; extra_bytes = addr & 0x0003; if (extra_bytes != 0 && ((scsiq->q1.cntl & QC_SG_HEAD) != 0 || (scsiq->q1.data_cnt & 0x01FF) == 0)) { scsiq->q2.tag_code |= ADV_TAG_FLAG_EXTRA_BYTES; scsiq->q1.extra_bytes = extra_bytes; *p_data_bcount -= extra_bytes; } } if ((adv_get_num_free_queues(adv, n_q_required) >= n_q_required) || ((scsiq->q1.cntl & QC_URGENT) != 0)) retval = adv_send_scsi_queue(adv, scsiq, n_q_required); return (retval); } u_int8_t adv_copy_lram_doneq(struct adv_softc *adv, u_int16_t q_addr, struct adv_q_done_info *scsiq, u_int32_t max_dma_count) { u_int16_t val; u_int8_t sg_queue_cnt; adv_get_q_info(adv, q_addr + ADV_SCSIQ_DONE_INFO_BEG, (u_int16_t *)scsiq, (sizeof(scsiq->d2) + sizeof(scsiq->d3)) / 2); #if BYTE_ORDER == BIG_ENDIAN adv_adj_endian_qdone_info(scsiq); #endif val = adv_read_lram_16(adv, q_addr + ADV_SCSIQ_B_STATUS); scsiq->q_status = val & 0xFF; scsiq->q_no = (val >> 8) & 0XFF; val = adv_read_lram_16(adv, q_addr + ADV_SCSIQ_B_CNTL); scsiq->cntl = val & 0xFF; sg_queue_cnt = (val >> 8) & 0xFF; val = adv_read_lram_16(adv,q_addr + ADV_SCSIQ_B_SENSE_LEN); scsiq->sense_len = val & 0xFF; scsiq->extra_bytes = (val >> 8) & 0xFF; /* * Due to a bug in accessing LRAM on the 940UA, the residual * is split into separate high and low 16bit quantities. */ scsiq->remain_bytes = adv_read_lram_16(adv, q_addr + ADV_SCSIQ_DW_REMAIN_XFER_CNT); scsiq->remain_bytes |= adv_read_lram_16(adv, q_addr + ADV_SCSIQ_W_ALT_DC1) << 16; /* * XXX Is this just a safeguard or will the counter really * have bogus upper bits? */ scsiq->remain_bytes &= max_dma_count; return (sg_queue_cnt); } int adv_start_chip(struct adv_softc *adv) { ADV_OUTB(adv, ADV_CHIP_CTRL, 0); if ((ADV_INW(adv, ADV_CHIP_STATUS) & ADV_CSW_HALTED) != 0) return (0); return (1); } int adv_stop_execution(struct adv_softc *adv) { int count; count = 0; if (adv_read_lram_8(adv, ADV_STOP_CODE_B) == 0) { adv_write_lram_8(adv, ADV_STOP_CODE_B, ADV_STOP_REQ_RISC_STOP); do { if (adv_read_lram_8(adv, ADV_STOP_CODE_B) & ADV_STOP_ACK_RISC_STOP) { return (1); } DELAY(1000); } while (count++ < 20); } return (0); } int adv_is_chip_halted(struct adv_softc *adv) { if ((ADV_INW(adv, ADV_CHIP_STATUS) & ADV_CSW_HALTED) != 0) { if ((ADV_INB(adv, ADV_CHIP_CTRL) & ADV_CC_HALT) != 0) { return (1); } } return (0); } /* * XXX The numeric constants and the loops in this routine * need to be documented. */ void adv_ack_interrupt(struct adv_softc *adv) { u_int8_t host_flag; u_int8_t risc_flag; int loop; loop = 0; do { risc_flag = adv_read_lram_8(adv, ADVV_RISC_FLAG_B); if (loop++ > 0x7FFF) { break; } } while ((risc_flag & ADV_RISC_FLAG_GEN_INT) != 0); host_flag = adv_read_lram_8(adv, ADVV_HOST_FLAG_B); adv_write_lram_8(adv, ADVV_HOST_FLAG_B, host_flag | ADV_HOST_FLAG_ACK_INT); ADV_OUTW(adv, ADV_CHIP_STATUS, ADV_CIW_INT_ACK); loop = 0; while (ADV_INW(adv, ADV_CHIP_STATUS) & ADV_CSW_INT_PENDING) { ADV_OUTW(adv, ADV_CHIP_STATUS, ADV_CIW_INT_ACK); if (loop++ > 3) { break; } } adv_write_lram_8(adv, ADVV_HOST_FLAG_B, host_flag); } /* * Handle all conditions that may halt the chip waiting * for us to intervene. */ void adv_isr_chip_halted(struct adv_softc *adv) { u_int16_t int_halt_code; u_int16_t halt_q_addr; target_bit_vector target_mask; target_bit_vector scsi_busy; u_int8_t halt_qp; u_int8_t target_ix; u_int8_t q_cntl; u_int8_t tid_no; int_halt_code = adv_read_lram_16(adv, ADVV_HALTCODE_W); halt_qp = adv_read_lram_8(adv, ADVV_CURCDB_B); halt_q_addr = ADV_QNO_TO_QADDR(halt_qp); target_ix = adv_read_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_TARGET_IX); q_cntl = adv_read_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_CNTL); tid_no = ADV_TIX_TO_TID(target_ix); target_mask = ADV_TID_TO_TARGET_MASK(tid_no); if (int_halt_code == ADV_HALT_DISABLE_ASYN_USE_SYN_FIX) { /* * Temporarily disable the async fix by removing * this target from the list of affected targets, * setting our async rate, and then putting us * back into the mask. */ adv->fix_asyn_xfer &= ~target_mask; adv_set_syncrate(adv, /*struct cam_path */NULL, tid_no, /*period*/0, /*offset*/0, ADV_TRANS_ACTIVE); adv->fix_asyn_xfer |= target_mask; } else if (int_halt_code == ADV_HALT_ENABLE_ASYN_USE_SYN_FIX) { adv_set_syncrate(adv, /*struct cam_path */NULL, tid_no, /*period*/0, /*offset*/0, ADV_TRANS_ACTIVE); } else if (int_halt_code == ADV_HALT_EXTMSG_IN) { adv_handle_extmsg_in(adv, halt_q_addr, q_cntl, target_mask, tid_no); } else if (int_halt_code == ADV_HALT_CHK_CONDITION) { struct adv_target_transinfo* tinfo; union ccb *ccb; u_int32_t cinfo_index; u_int8_t tag_code; u_int8_t q_status; tinfo = &adv->tinfo[tid_no]; q_cntl |= QC_REQ_SENSE; /* Renegotiate if appropriate. */ adv_set_syncrate(adv, /*struct cam_path */NULL, tid_no, /*period*/0, /*offset*/0, ADV_TRANS_CUR); if (tinfo->current.period != tinfo->goal.period) { adv_msgout_sdtr(adv, tinfo->goal.period, tinfo->goal.offset); q_cntl |= QC_MSG_OUT; } adv_write_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_CNTL, q_cntl); /* Don't tag request sense commands */ tag_code = adv_read_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_TAG_CODE); tag_code &= ~(MSG_SIMPLE_Q_TAG|MSG_HEAD_OF_Q_TAG|MSG_ORDERED_Q_TAG); if ((adv->fix_asyn_xfer & target_mask) != 0 && (adv->fix_asyn_xfer_always & target_mask) == 0) { tag_code |= (ADV_TAG_FLAG_DISABLE_DISCONNECT | ADV_TAG_FLAG_DISABLE_ASYN_USE_SYN_FIX); } adv_write_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_TAG_CODE, tag_code); q_status = adv_read_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_STATUS); q_status |= (QS_READY | QS_BUSY); adv_write_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_STATUS, q_status); /* * Freeze the devq until we can handle the sense condition. */ cinfo_index = adv_read_lram_32(adv, halt_q_addr + ADV_SCSIQ_D_CINFO_IDX); ccb = adv->ccb_infos[cinfo_index].ccb; xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status |= CAM_DEV_QFRZN; adv_abort_ccb(adv, tid_no, ADV_TIX_TO_LUN(target_ix), /*ccb*/NULL, CAM_REQUEUE_REQ, /*queued_only*/TRUE); scsi_busy = adv_read_lram_8(adv, ADVV_SCSIBUSY_B); scsi_busy &= ~target_mask; adv_write_lram_8(adv, ADVV_SCSIBUSY_B, scsi_busy); /* * Ensure we have enough time to actually * retrieve the sense. */ callout_reset(&ccb->ccb_h.timeout_ch, 5 * hz, adv_timeout, ccb); } else if (int_halt_code == ADV_HALT_SDTR_REJECTED) { struct ext_msg out_msg; adv_read_lram_16_multi(adv, ADVV_MSGOUT_BEG, (u_int16_t *) &out_msg, sizeof(out_msg)/2); if ((out_msg.msg_type == MSG_EXTENDED) && (out_msg.msg_len == MSG_EXT_SDTR_LEN) && (out_msg.msg_req == MSG_EXT_SDTR)) { /* Revert to Async */ adv_set_syncrate(adv, /*struct cam_path */NULL, tid_no, /*period*/0, /*offset*/0, ADV_TRANS_GOAL|ADV_TRANS_ACTIVE); } q_cntl &= ~QC_MSG_OUT; adv_write_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_CNTL, q_cntl); } else if (int_halt_code == ADV_HALT_SS_QUEUE_FULL) { u_int8_t scsi_status; union ccb *ccb; u_int32_t cinfo_index; scsi_status = adv_read_lram_8(adv, halt_q_addr + ADV_SCSIQ_SCSI_STATUS); cinfo_index = adv_read_lram_32(adv, halt_q_addr + ADV_SCSIQ_D_CINFO_IDX); ccb = adv->ccb_infos[cinfo_index].ccb; xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status |= CAM_DEV_QFRZN|CAM_SCSI_STATUS_ERROR; ccb->csio.scsi_status = SCSI_STATUS_QUEUE_FULL; adv_abort_ccb(adv, tid_no, ADV_TIX_TO_LUN(target_ix), /*ccb*/NULL, CAM_REQUEUE_REQ, /*queued_only*/TRUE); scsi_busy = adv_read_lram_8(adv, ADVV_SCSIBUSY_B); scsi_busy &= ~target_mask; adv_write_lram_8(adv, ADVV_SCSIBUSY_B, scsi_busy); } else { kprintf("Unhandled Halt Code %x\n", int_halt_code); } adv_write_lram_16(adv, ADVV_HALTCODE_W, 0); } void adv_sdtr_to_period_offset(struct adv_softc *adv, u_int8_t sync_data, u_int8_t *period, u_int8_t *offset, int tid) { if (adv->fix_asyn_xfer & ADV_TID_TO_TARGET_MASK(tid) && (sync_data == ASYN_SDTR_DATA_FIX_PCI_REV_AB)) { *period = *offset = 0; } else { *period = adv->sdtr_period_tbl[((sync_data >> 4) & 0xF)]; *offset = sync_data & 0xF; } } void adv_set_syncrate(struct adv_softc *adv, struct cam_path *path, u_int tid, u_int period, u_int offset, u_int type) { struct adv_target_transinfo* tinfo; u_int old_period; u_int old_offset; u_int8_t sdtr_data; tinfo = &adv->tinfo[tid]; /* Filter our input */ sdtr_data = adv_period_offset_to_sdtr(adv, &period, &offset, tid); old_period = tinfo->current.period; old_offset = tinfo->current.offset; if ((type & ADV_TRANS_CUR) != 0 && ((old_period != period || old_offset != offset) || period == 0 || offset == 0) /*Changes in asyn fix settings*/) { int halted; crit_enter(); halted = adv_is_chip_halted(adv); if (halted == 0) /* Must halt the chip first */ adv_host_req_chip_halt(adv); /* Update current hardware settings */ adv_set_sdtr_reg_at_id(adv, tid, sdtr_data); /* * If a target can run in sync mode, we don't need * to check it for sync problems. */ if (offset != 0) adv->fix_asyn_xfer &= ~ADV_TID_TO_TARGET_MASK(tid); if (halted == 0) /* Start the chip again */ adv_start_chip(adv); crit_exit(); tinfo->current.period = period; tinfo->current.offset = offset; if (path != NULL) { /* * Tell the SCSI layer about the * new transfer parameters. */ struct ccb_trans_settings neg; memset(&neg, 0, sizeof (neg)); struct ccb_trans_settings_spi *spi = &neg.xport_specific.spi; neg.protocol = PROTO_SCSI; neg.protocol_version = SCSI_REV_2; neg.transport = XPORT_SPI; neg.transport_version = 2; spi->sync_offset = offset; spi->sync_period = period; spi->valid |= CTS_SPI_VALID_SYNC_OFFSET; spi->valid |= CTS_SPI_VALID_SYNC_RATE; xpt_setup_ccb(&neg.ccb_h, path, /*priority*/1); xpt_async(AC_TRANSFER_NEG, path, &neg); } } if ((type & ADV_TRANS_GOAL) != 0) { tinfo->goal.period = period; tinfo->goal.offset = offset; } if ((type & ADV_TRANS_USER) != 0) { tinfo->user.period = period; tinfo->user.offset = offset; } } u_int8_t adv_period_offset_to_sdtr(struct adv_softc *adv, u_int *period, u_int *offset, int tid) { u_int i; u_int dummy_offset; u_int dummy_period; if (offset == NULL) { dummy_offset = 0; offset = &dummy_offset; } if (period == NULL) { dummy_period = 0; period = &dummy_period; } *offset = MIN(ADV_SYN_MAX_OFFSET, *offset); if (*period != 0 && *offset != 0) { for (i = 0; i < adv->sdtr_period_tbl_size; i++) { if (*period <= adv->sdtr_period_tbl[i]) { /* * When responding to a target that requests * sync, the requested rate may fall between * two rates that we can output, but still be * a rate that we can receive. Because of this, * we want to respond to the target with * the same rate that it sent to us even * if the period we use to send data to it * is lower. Only lower the response period * if we must. */ if (i == 0 /* Our maximum rate */) *period = adv->sdtr_period_tbl[0]; return ((i << 4) | *offset); } } } /* Must go async */ *period = 0; *offset = 0; if (adv->fix_asyn_xfer & ADV_TID_TO_TARGET_MASK(tid)) return (ASYN_SDTR_DATA_FIX_PCI_REV_AB); return (0); } /* Internal Routines */ static void adv_read_lram_16_multi(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *buffer, int count) { ADV_OUTW(adv, ADV_LRAM_ADDR, s_addr); ADV_INSW(adv, ADV_LRAM_DATA, buffer, count); } static void adv_write_lram_16_multi(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *buffer, int count) { ADV_OUTW(adv, ADV_LRAM_ADDR, s_addr); ADV_OUTSW(adv, ADV_LRAM_DATA, buffer, count); } static void adv_mset_lram_16(struct adv_softc *adv, u_int16_t s_addr, u_int16_t set_value, int count) { ADV_OUTW(adv, ADV_LRAM_ADDR, s_addr); bus_space_set_multi_2(adv->tag, adv->bsh, ADV_LRAM_DATA, set_value, count); } static u_int32_t adv_msum_lram_16(struct adv_softc *adv, u_int16_t s_addr, int count) { u_int32_t sum; int i; sum = 0; ADV_OUTW(adv, ADV_LRAM_ADDR, s_addr); for (i = 0; i < count; i++) sum += ADV_INW(adv, ADV_LRAM_DATA); return (sum); } static int adv_write_and_verify_lram_16(struct adv_softc *adv, u_int16_t addr, u_int16_t value) { int retval; retval = 0; ADV_OUTW(adv, ADV_LRAM_ADDR, addr); ADV_OUTW(adv, ADV_LRAM_DATA, value); DELAY(10000); ADV_OUTW(adv, ADV_LRAM_ADDR, addr); if (value != ADV_INW(adv, ADV_LRAM_DATA)) retval = 1; return (retval); } static u_int32_t adv_read_lram_32(struct adv_softc *adv, u_int16_t addr) { u_int16_t val_low, val_high; ADV_OUTW(adv, ADV_LRAM_ADDR, addr); #if BYTE_ORDER == BIG_ENDIAN val_high = ADV_INW(adv, ADV_LRAM_DATA); val_low = ADV_INW(adv, ADV_LRAM_DATA); #else val_low = ADV_INW(adv, ADV_LRAM_DATA); val_high = ADV_INW(adv, ADV_LRAM_DATA); #endif return (((u_int32_t)val_high << 16) | (u_int32_t)val_low); } static void adv_write_lram_32(struct adv_softc *adv, u_int16_t addr, u_int32_t value) { ADV_OUTW(adv, ADV_LRAM_ADDR, addr); #if BYTE_ORDER == BIG_ENDIAN ADV_OUTW(adv, ADV_LRAM_DATA, (u_int16_t)((value >> 16) & 0xFFFF)); ADV_OUTW(adv, ADV_LRAM_DATA, (u_int16_t)(value & 0xFFFF)); #else ADV_OUTW(adv, ADV_LRAM_DATA, (u_int16_t)(value & 0xFFFF)); ADV_OUTW(adv, ADV_LRAM_DATA, (u_int16_t)((value >> 16) & 0xFFFF)); #endif } static void adv_write_lram_32_multi(struct adv_softc *adv, u_int16_t s_addr, u_int32_t *buffer, int count) { ADV_OUTW(adv, ADV_LRAM_ADDR, s_addr); ADV_OUTSW(adv, ADV_LRAM_DATA, (u_int16_t *)buffer, count * 2); } static u_int16_t adv_read_eeprom_16(struct adv_softc *adv, u_int8_t addr) { u_int16_t read_wval; u_int8_t cmd_reg; adv_write_eeprom_cmd_reg(adv, ADV_EEPROM_CMD_WRITE_DISABLE); DELAY(1000); cmd_reg = addr | ADV_EEPROM_CMD_READ; adv_write_eeprom_cmd_reg(adv, cmd_reg); DELAY(1000); read_wval = ADV_INW(adv, ADV_EEPROM_DATA); DELAY(1000); return (read_wval); } static u_int16_t adv_write_eeprom_16(struct adv_softc *adv, u_int8_t addr, u_int16_t value) { u_int16_t read_value; read_value = adv_read_eeprom_16(adv, addr); if (read_value != value) { adv_write_eeprom_cmd_reg(adv, ADV_EEPROM_CMD_WRITE_ENABLE); DELAY(1000); ADV_OUTW(adv, ADV_EEPROM_DATA, value); DELAY(1000); adv_write_eeprom_cmd_reg(adv, ADV_EEPROM_CMD_WRITE | addr); DELAY(20 * 1000); adv_write_eeprom_cmd_reg(adv, ADV_EEPROM_CMD_WRITE_DISABLE); DELAY(1000); read_value = adv_read_eeprom_16(adv, addr); } return (read_value); } static int adv_write_eeprom_cmd_reg(struct adv_softc *adv, u_int8_t cmd_reg) { u_int8_t read_back; int retry; retry = 0; while (1) { ADV_OUTB(adv, ADV_EEPROM_CMD, cmd_reg); DELAY(1000); read_back = ADV_INB(adv, ADV_EEPROM_CMD); if (read_back == cmd_reg) { return (1); } if (retry++ > ADV_EEPROM_MAX_RETRY) { return (0); } } } static int adv_set_eeprom_config_once(struct adv_softc *adv, struct adv_eeprom_config *eeprom_config) { int n_error; u_int16_t *wbuf; u_int16_t sum; u_int8_t s_addr; u_int8_t cfg_beg; u_int8_t cfg_end; wbuf = (u_int16_t *)eeprom_config; n_error = 0; sum = 0; for (s_addr = 0; s_addr < 2; s_addr++, wbuf++) { sum += *wbuf; if (*wbuf != adv_write_eeprom_16(adv, s_addr, *wbuf)) { n_error++; } } if (adv->type & ADV_VL) { cfg_beg = ADV_EEPROM_CFG_BEG_VL; cfg_end = ADV_EEPROM_MAX_ADDR_VL; } else { cfg_beg = ADV_EEPROM_CFG_BEG; cfg_end = ADV_EEPROM_MAX_ADDR; } for (s_addr = cfg_beg; s_addr <= (cfg_end - 1); s_addr++, wbuf++) { sum += *wbuf; if (*wbuf != adv_write_eeprom_16(adv, s_addr, *wbuf)) { n_error++; } } *wbuf = sum; if (sum != adv_write_eeprom_16(adv, s_addr, sum)) { n_error++; } wbuf = (u_int16_t *)eeprom_config; for (s_addr = 0; s_addr < 2; s_addr++, wbuf++) { if (*wbuf != adv_read_eeprom_16(adv, s_addr)) { n_error++; } } for (s_addr = cfg_beg; s_addr <= cfg_end; s_addr++, wbuf++) { if (*wbuf != adv_read_eeprom_16(adv, s_addr)) { n_error++; } } return (n_error); } static u_int32_t adv_load_microcode(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *mcode_buf, u_int16_t mcode_size) { u_int32_t chksum; u_int16_t mcode_lram_size; u_int16_t mcode_chksum; mcode_lram_size = mcode_size >> 1; /* XXX Why zero the memory just before you write the whole thing?? */ adv_mset_lram_16(adv, s_addr, 0, mcode_lram_size); adv_write_lram_16_multi(adv, s_addr, mcode_buf, mcode_lram_size); chksum = adv_msum_lram_16(adv, s_addr, mcode_lram_size); mcode_chksum = (u_int16_t)adv_msum_lram_16(adv, ADV_CODE_SEC_BEG, ((mcode_size - s_addr - ADV_CODE_SEC_BEG) >> 1)); adv_write_lram_16(adv, ADVV_MCODE_CHKSUM_W, mcode_chksum); adv_write_lram_16(adv, ADVV_MCODE_SIZE_W, mcode_size); return (chksum); } static void adv_reinit_lram(struct adv_softc *adv) { adv_init_lram(adv); adv_init_qlink_var(adv); } static void adv_init_lram(struct adv_softc *adv) { u_int8_t i; u_int16_t s_addr; adv_mset_lram_16(adv, ADV_QADR_BEG, 0, (((adv->max_openings + 2 + 1) * 64) >> 1)); i = ADV_MIN_ACTIVE_QNO; s_addr = ADV_QADR_BEG + ADV_QBLK_SIZE; adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_FWD, i + 1); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_BWD, adv->max_openings); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_QNO, i); i++; s_addr += ADV_QBLK_SIZE; for (; i < adv->max_openings; i++, s_addr += ADV_QBLK_SIZE) { adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_FWD, i + 1); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_BWD, i - 1); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_QNO, i); } adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_FWD, ADV_QLINK_END); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_BWD, adv->max_openings - 1); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_QNO, adv->max_openings); i++; s_addr += ADV_QBLK_SIZE; for (; i <= adv->max_openings + 3; i++, s_addr += ADV_QBLK_SIZE) { adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_FWD, i); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_BWD, i); adv_write_lram_8(adv, s_addr + ADV_SCSIQ_B_QNO, i); } } static int adv_init_microcode_var(struct adv_softc *adv) { int i; for (i = 0; i <= ADV_MAX_TID; i++) { /* Start out async all around */ adv_set_syncrate(adv, /*path*/NULL, i, 0, 0, ADV_TRANS_GOAL|ADV_TRANS_CUR); } adv_init_qlink_var(adv); adv_write_lram_8(adv, ADVV_DISC_ENABLE_B, adv->disc_enable); adv_write_lram_8(adv, ADVV_HOSTSCSI_ID_B, 0x01 << adv->scsi_id); adv_write_lram_32(adv, ADVV_OVERRUN_PADDR_D, adv->overrun_physbase); adv_write_lram_32(adv, ADVV_OVERRUN_BSIZE_D, ADV_OVERRUN_BSIZE); ADV_OUTW(adv, ADV_REG_PROG_COUNTER, ADV_MCODE_START_ADDR); if (ADV_INW(adv, ADV_REG_PROG_COUNTER) != ADV_MCODE_START_ADDR) { kprintf("adv%d: Unable to set program counter. Aborting.\n", adv->unit); return (1); } return (0); } static void adv_init_qlink_var(struct adv_softc *adv) { int i; u_int16_t lram_addr; adv_write_lram_8(adv, ADVV_NEXTRDY_B, 1); adv_write_lram_8(adv, ADVV_DONENEXT_B, adv->max_openings); adv_write_lram_16(adv, ADVV_FREE_Q_HEAD_W, 1); adv_write_lram_16(adv, ADVV_DONE_Q_TAIL_W, adv->max_openings); adv_write_lram_8(adv, ADVV_BUSY_QHEAD_B, (u_int8_t)((int) adv->max_openings + 1)); adv_write_lram_8(adv, ADVV_DISC1_QHEAD_B, (u_int8_t)((int) adv->max_openings + 2)); adv_write_lram_8(adv, ADVV_TOTAL_READY_Q_B, adv->max_openings); adv_write_lram_16(adv, ADVV_ASCDVC_ERR_CODE_W, 0); adv_write_lram_16(adv, ADVV_HALTCODE_W, 0); adv_write_lram_8(adv, ADVV_STOP_CODE_B, 0); adv_write_lram_8(adv, ADVV_SCSIBUSY_B, 0); adv_write_lram_8(adv, ADVV_WTM_FLAG_B, 0); adv_write_lram_8(adv, ADVV_Q_DONE_IN_PROGRESS_B, 0); lram_addr = ADV_QADR_BEG; for (i = 0; i < 32; i++, lram_addr += 2) adv_write_lram_16(adv, lram_addr, 0); } static void adv_disable_interrupt(struct adv_softc *adv) { u_int16_t cfg; cfg = ADV_INW(adv, ADV_CONFIG_LSW); ADV_OUTW(adv, ADV_CONFIG_LSW, cfg & ~ADV_CFG_LSW_HOST_INT_ON); } static void adv_enable_interrupt(struct adv_softc *adv) { u_int16_t cfg; cfg = ADV_INW(adv, ADV_CONFIG_LSW); ADV_OUTW(adv, ADV_CONFIG_LSW, cfg | ADV_CFG_LSW_HOST_INT_ON); } static void adv_toggle_irq_act(struct adv_softc *adv) { ADV_OUTW(adv, ADV_CHIP_STATUS, ADV_CIW_IRQ_ACT); ADV_OUTW(adv, ADV_CHIP_STATUS, 0); } void adv_start_execution(struct adv_softc *adv) { if (adv_read_lram_8(adv, ADV_STOP_CODE_B) != 0) { adv_write_lram_8(adv, ADV_STOP_CODE_B, 0); } } int adv_stop_chip(struct adv_softc *adv) { u_int8_t cc_val; cc_val = ADV_INB(adv, ADV_CHIP_CTRL) & (~(ADV_CC_SINGLE_STEP | ADV_CC_TEST | ADV_CC_DIAG)); ADV_OUTB(adv, ADV_CHIP_CTRL, cc_val | ADV_CC_HALT); adv_set_chip_ih(adv, ADV_INS_HALT); adv_set_chip_ih(adv, ADV_INS_RFLAG_WTM); if ((ADV_INW(adv, ADV_CHIP_STATUS) & ADV_CSW_HALTED) == 0) { return (0); } return (1); } static int adv_host_req_chip_halt(struct adv_softc *adv) { int count; u_int8_t saved_stop_code; if (adv_is_chip_halted(adv)) return (1); count = 0; saved_stop_code = adv_read_lram_8(adv, ADVV_STOP_CODE_B); adv_write_lram_8(adv, ADVV_STOP_CODE_B, ADV_STOP_HOST_REQ_RISC_HALT | ADV_STOP_REQ_RISC_STOP); while (adv_is_chip_halted(adv) == 0 && count++ < 2000) ; adv_write_lram_8(adv, ADVV_STOP_CODE_B, saved_stop_code); return (count < 2000); } static void adv_set_chip_ih(struct adv_softc *adv, u_int16_t ins_code) { adv_set_bank(adv, 1); ADV_OUTW(adv, ADV_REG_IH, ins_code); adv_set_bank(adv, 0); } #if 0 /* UNUSED */ static u_int8_t adv_get_chip_scsi_ctrl(struct adv_softc *adv) { u_int8_t scsi_ctrl; adv_set_bank(adv, 1); scsi_ctrl = ADV_INB(adv, ADV_REG_SC); adv_set_bank(adv, 0); return (scsi_ctrl); } #endif /* * XXX Looks like more padding issues in this routine as well. * There has to be a way to turn this into an insw. */ static void adv_get_q_info(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *inbuf, int words) { int i; ADV_OUTW(adv, ADV_LRAM_ADDR, s_addr); for (i = 0; i < words; i++, inbuf++) { if (i == 5) { continue; } *inbuf = ADV_INW(adv, ADV_LRAM_DATA); } } static u_int adv_get_num_free_queues(struct adv_softc *adv, u_int8_t n_qs) { u_int cur_used_qs; u_int cur_free_qs; cur_used_qs = adv->cur_active + ADV_MIN_FREE_Q; if ((cur_used_qs + n_qs) <= adv->max_openings) { cur_free_qs = adv->max_openings - cur_used_qs; return (cur_free_qs); } adv->openings_needed = n_qs; return (0); } static u_int8_t adv_alloc_free_queues(struct adv_softc *adv, u_int8_t free_q_head, u_int8_t n_free_q) { int i; for (i = 0; i < n_free_q; i++) { free_q_head = adv_alloc_free_queue(adv, free_q_head); if (free_q_head == ADV_QLINK_END) break; } return (free_q_head); } static u_int8_t adv_alloc_free_queue(struct adv_softc *adv, u_int8_t free_q_head) { u_int16_t q_addr; u_int8_t next_qp; u_int8_t q_status; next_qp = ADV_QLINK_END; q_addr = ADV_QNO_TO_QADDR(free_q_head); q_status = adv_read_lram_8(adv, q_addr + ADV_SCSIQ_B_STATUS); if ((q_status & QS_READY) == 0) next_qp = adv_read_lram_8(adv, q_addr + ADV_SCSIQ_B_FWD); return (next_qp); } static int adv_send_scsi_queue(struct adv_softc *adv, struct adv_scsi_q *scsiq, u_int8_t n_q_required) { u_int8_t free_q_head; u_int8_t next_qp; int retval; retval = 1; free_q_head = adv_read_lram_16(adv, ADVV_FREE_Q_HEAD_W) & 0xFF; if ((next_qp = adv_alloc_free_queues(adv, free_q_head, n_q_required)) != ADV_QLINK_END) { scsiq->q1.q_no = free_q_head; /* * Now that we know our Q number, point our sense * buffer pointer to a bus dma mapped area where * we can dma the data to. */ scsiq->q1.sense_addr = adv->sense_physbase + ((free_q_head - 1) * sizeof(struct scsi_sense_data)); adv_put_ready_sg_list_queue(adv, scsiq, free_q_head); adv_write_lram_16(adv, ADVV_FREE_Q_HEAD_W, next_qp); adv->cur_active += n_q_required; retval = 0; } return (retval); } static void adv_put_ready_sg_list_queue(struct adv_softc *adv, struct adv_scsi_q *scsiq, u_int q_no) { u_int8_t sg_list_dwords; u_int8_t sg_index, i; u_int8_t sg_entry_cnt; u_int8_t next_qp; u_int16_t q_addr; struct adv_sg_head *sg_head; struct adv_sg_list_q scsi_sg_q; sg_head = scsiq->sg_head; if (sg_head) { sg_entry_cnt = sg_head->entry_cnt - 1; #ifdef DIAGNOSTIC if (sg_entry_cnt == 0) panic("adv_put_ready_sg_list_queue: ScsiQ with " "a SG list but only one element"); if ((scsiq->q1.cntl & QC_SG_HEAD) == 0) panic("adv_put_ready_sg_list_queue: ScsiQ with " "a SG list but QC_SG_HEAD not set"); #endif q_addr = ADV_QNO_TO_QADDR(q_no); sg_index = 1; scsiq->q1.sg_queue_cnt = sg_head->queue_cnt; scsi_sg_q.sg_head_qp = q_no; scsi_sg_q.cntl = QCSG_SG_XFER_LIST; for (i = 0; i < sg_head->queue_cnt; i++) { u_int8_t segs_this_q; if (sg_entry_cnt > ADV_SG_LIST_PER_Q) segs_this_q = ADV_SG_LIST_PER_Q; else { /* This will be the last segment then */ segs_this_q = sg_entry_cnt; scsi_sg_q.cntl |= QCSG_SG_XFER_END; } scsi_sg_q.seq_no = i + 1; sg_list_dwords = segs_this_q << 1; if (i == 0) { scsi_sg_q.sg_list_cnt = segs_this_q; scsi_sg_q.sg_cur_list_cnt = segs_this_q; } else { scsi_sg_q.sg_list_cnt = segs_this_q - 1; scsi_sg_q.sg_cur_list_cnt = segs_this_q - 1; } next_qp = adv_read_lram_8(adv, q_addr + ADV_SCSIQ_B_FWD); scsi_sg_q.q_no = next_qp; q_addr = ADV_QNO_TO_QADDR(next_qp); adv_write_lram_16_multi(adv, q_addr + ADV_SCSIQ_SGHD_CPY_BEG, (u_int16_t *)&scsi_sg_q, sizeof(scsi_sg_q) >> 1); adv_write_lram_32_multi(adv, q_addr + ADV_SGQ_LIST_BEG, (u_int32_t *)&sg_head->sg_list[sg_index], sg_list_dwords); sg_entry_cnt -= segs_this_q; sg_index += ADV_SG_LIST_PER_Q; } } adv_put_ready_queue(adv, scsiq, q_no); } static void adv_put_ready_queue(struct adv_softc *adv, struct adv_scsi_q *scsiq, u_int q_no) { struct adv_target_transinfo* tinfo; u_int q_addr; u_int tid_no; tid_no = ADV_TIX_TO_TID(scsiq->q2.target_ix); tinfo = &adv->tinfo[tid_no]; if ((tinfo->current.period != tinfo->goal.period) || (tinfo->current.offset != tinfo->goal.offset)) { adv_msgout_sdtr(adv, tinfo->goal.period, tinfo->goal.offset); scsiq->q1.cntl |= QC_MSG_OUT; } q_addr = ADV_QNO_TO_QADDR(q_no); scsiq->q1.status = QS_FREE; adv_write_lram_16_multi(adv, q_addr + ADV_SCSIQ_CDB_BEG, (u_int16_t *)scsiq->cdbptr, scsiq->q2.cdb_len >> 1); #if BYTE_ORDER == BIG_ENDIAN adv_adj_scsiq_endian(scsiq); #endif adv_put_scsiq(adv, q_addr + ADV_SCSIQ_CPY_BEG, (u_int16_t *) &scsiq->q1.cntl, ((sizeof(scsiq->q1) + sizeof(scsiq->q2)) / 2) - 1); #if defined(CC_WRITE_IO_COUNT) && CC_WRITE_IO_COUNT adv_write_lram_16(adv, q_addr + ADV_SCSIQ_W_REQ_COUNT, adv->req_count); #endif #if defined(CC_CLEAR_DMA_REMAIN) && CC_CLEAR_DMA_REMAIN adv_write_lram_32(adv, q_addr + ADV_SCSIQ_DW_REMAIN_XFER_ADDR, 0); adv_write_lram_32(adv, q_addr + ADV_SCSIQ_DW_REMAIN_XFER_CNT, 0); #endif adv_write_lram_16(adv, q_addr + ADV_SCSIQ_B_STATUS, (scsiq->q1.q_no << 8) | QS_READY); } static void adv_put_scsiq(struct adv_softc *adv, u_int16_t s_addr, u_int16_t *buffer, int words) { int i; /* * XXX This routine makes *gross* assumptions * about padding in the data structures. * Either the data structures should have explicit * padding members added, or they should have padding * turned off via compiler attributes depending on * which yields better overall performance. My hunch * would be that turning off padding would be the * faster approach as an outsw is much faster than * this crude loop and accessing un-aligned data * members isn't *that* expensive. The other choice * would be to modify the ASC script so that the * the adv_scsiq_1 structure can be re-arranged so * padding isn't required. */ ADV_OUTW(adv, ADV_LRAM_ADDR, s_addr); for (i = 0; i < words; i++, buffer++) { if (i == 2 || i == 10) { continue; } ADV_OUTW(adv, ADV_LRAM_DATA, *buffer); } } static void adv_handle_extmsg_in(struct adv_softc *adv, u_int16_t halt_q_addr, u_int8_t q_cntl, target_bit_vector target_mask, int tid_no) { struct ext_msg ext_msg; adv_read_lram_16_multi(adv, ADVV_MSGIN_BEG, (u_int16_t *) &ext_msg, sizeof(ext_msg) >> 1); if ((ext_msg.msg_type == MSG_EXTENDED) && (ext_msg.msg_req == MSG_EXT_SDTR) && (ext_msg.msg_len == MSG_EXT_SDTR_LEN)) { union ccb *ccb; struct adv_target_transinfo* tinfo; u_int32_t cinfo_index; u_int period; u_int offset; int sdtr_accept; u_int8_t orig_offset; cinfo_index = adv_read_lram_32(adv, halt_q_addr + ADV_SCSIQ_D_CINFO_IDX); ccb = adv->ccb_infos[cinfo_index].ccb; tinfo = &adv->tinfo[tid_no]; sdtr_accept = TRUE; orig_offset = ext_msg.req_ack_offset; if (ext_msg.xfer_period < tinfo->goal.period) { sdtr_accept = FALSE; ext_msg.xfer_period = tinfo->goal.period; } /* Perform range checking */ period = ext_msg.xfer_period; offset = ext_msg.req_ack_offset; adv_period_offset_to_sdtr(adv, &period, &offset, tid_no); ext_msg.xfer_period = period; ext_msg.req_ack_offset = offset; /* Record our current sync settings */ adv_set_syncrate(adv, ccb->ccb_h.path, tid_no, ext_msg.xfer_period, ext_msg.req_ack_offset, ADV_TRANS_GOAL|ADV_TRANS_ACTIVE); /* Offset too high or large period forced async */ if (orig_offset != ext_msg.req_ack_offset) sdtr_accept = FALSE; if (sdtr_accept && (q_cntl & QC_MSG_OUT)) { /* Valid response to our requested negotiation */ q_cntl &= ~QC_MSG_OUT; } else { /* Must Respond */ q_cntl |= QC_MSG_OUT; adv_msgout_sdtr(adv, ext_msg.xfer_period, ext_msg.req_ack_offset); } } else if (ext_msg.msg_type == MSG_EXTENDED && ext_msg.msg_req == MSG_EXT_WDTR && ext_msg.msg_len == MSG_EXT_WDTR_LEN) { ext_msg.wdtr_width = 0; adv_write_lram_16_multi(adv, ADVV_MSGOUT_BEG, (u_int16_t *)&ext_msg, sizeof(ext_msg) >> 1); q_cntl |= QC_MSG_OUT; } else { ext_msg.msg_type = MSG_MESSAGE_REJECT; adv_write_lram_16_multi(adv, ADVV_MSGOUT_BEG, (u_int16_t *)&ext_msg, sizeof(ext_msg) >> 1); q_cntl |= QC_MSG_OUT; } adv_write_lram_8(adv, halt_q_addr + ADV_SCSIQ_B_CNTL, q_cntl); } static void adv_msgout_sdtr(struct adv_softc *adv, u_int8_t sdtr_period, u_int8_t sdtr_offset) { struct ext_msg sdtr_buf; sdtr_buf.msg_type = MSG_EXTENDED; sdtr_buf.msg_len = MSG_EXT_SDTR_LEN; sdtr_buf.msg_req = MSG_EXT_SDTR; sdtr_buf.xfer_period = sdtr_period; sdtr_offset &= ADV_SYN_MAX_OFFSET; sdtr_buf.req_ack_offset = sdtr_offset; adv_write_lram_16_multi(adv, ADVV_MSGOUT_BEG, (u_int16_t *) &sdtr_buf, sizeof(sdtr_buf) / 2); } int adv_abort_ccb(struct adv_softc *adv, int target, int lun, union ccb *ccb, u_int32_t status, int queued_only) { u_int16_t q_addr; u_int8_t q_no; struct adv_q_done_info scsiq_buf; struct adv_q_done_info *scsiq; u_int8_t target_ix; int count; scsiq = &scsiq_buf; target_ix = ADV_TIDLUN_TO_IX(target, lun); count = 0; for (q_no = ADV_MIN_ACTIVE_QNO; q_no <= adv->max_openings; q_no++) { struct adv_ccb_info *ccb_info; q_addr = ADV_QNO_TO_QADDR(q_no); adv_copy_lram_doneq(adv, q_addr, scsiq, adv->max_dma_count); ccb_info = &adv->ccb_infos[scsiq->d2.ccb_index]; if (((scsiq->q_status & QS_READY) != 0) && ((scsiq->q_status & QS_ABORTED) == 0) && ((scsiq->cntl & QCSG_SG_XFER_LIST) == 0) && (scsiq->d2.target_ix == target_ix) && (queued_only == 0 || !(scsiq->q_status & (QS_DISC1|QS_DISC2|QS_BUSY|QS_DONE))) && (ccb == NULL || (ccb == ccb_info->ccb))) { union ccb *aborted_ccb; struct adv_ccb_info *cinfo; scsiq->q_status |= QS_ABORTED; adv_write_lram_8(adv, q_addr + ADV_SCSIQ_B_STATUS, scsiq->q_status); aborted_ccb = ccb_info->ccb; /* Don't clobber earlier error codes */ if ((aborted_ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INPROG) aborted_ccb->ccb_h.status |= status; cinfo = (struct adv_ccb_info *) aborted_ccb->ccb_h.ccb_cinfo_ptr; cinfo->state |= ACCB_ABORT_QUEUED; count++; } } return (count); } int adv_reset_bus(struct adv_softc *adv, int initiate_bus_reset) { int count; int i; union ccb *ccb; i = 200; while ((ADV_INW(adv, ADV_CHIP_STATUS) & ADV_CSW_SCSI_RESET_ACTIVE) != 0 && i--) DELAY(1000); adv_reset_chip(adv, initiate_bus_reset); adv_reinit_lram(adv); for (i = 0; i <= ADV_MAX_TID; i++) adv_set_syncrate(adv, NULL, i, /*period*/0, /*offset*/0, ADV_TRANS_CUR); ADV_OUTW(adv, ADV_REG_PROG_COUNTER, ADV_MCODE_START_ADDR); /* Tell the XPT layer that a bus reset occured */ if (adv->path != NULL) xpt_async(AC_BUS_RESET, adv->path, NULL); count = 0; while ((ccb = (union ccb *)LIST_FIRST(&adv->pending_ccbs)) != NULL) { if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INPROG) ccb->ccb_h.status |= CAM_SCSI_BUS_RESET; adv_done(adv, ccb, QD_ABORTED_BY_HOST, 0, 0, 0); count++; } adv_start_chip(adv); return (count); } static void adv_set_sdtr_reg_at_id(struct adv_softc *adv, int tid, u_int8_t sdtr_data) { int orig_id; adv_set_bank(adv, 1); orig_id = ffs(ADV_INB(adv, ADV_HOST_SCSIID)) - 1; ADV_OUTB(adv, ADV_HOST_SCSIID, tid); if (ADV_INB(adv, ADV_HOST_SCSIID) == (0x01 << tid)) { adv_set_bank(adv, 0); ADV_OUTB(adv, ADV_SYN_OFFSET, sdtr_data); } adv_set_bank(adv, 1); ADV_OUTB(adv, ADV_HOST_SCSIID, orig_id); adv_set_bank(adv, 0); }