/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2011 Bayard G. Bell. All rights reserved. */ /* * ISSUES * * - more consistent error messages * - report name of device on errors? * - if wide target renegotiates sync, back to narrow? * - last_msgout is not accurate ???? * - resolve XXXX * - improve msg reject code (use special msg reject handler) * - better use of IDE message * - keep track if ATN remains asserted and target not going into * a msg-out phase * - improve comments * - no slave accesses when start address is odd and dma hasn't started * this affect asserting ATN */ /* * fas - QLogic fas366 wide/fast SCSI Processor HBA driver with * tagged and non-tagged queueing support */ #if defined(lint) && !defined(DEBUG) #define DEBUG 1 #define FASDEBUG #endif #define DMA_REG_TRACING /* enable dma register access tracing */ /* * standard header files */ #include #include #include #include /* * private header files */ #include #include #include #include #include /* * tunables */ static int fas_selection_timeout = 250; /* 250 milliseconds */ static uchar_t fas_default_offset = DEFAULT_OFFSET; /* * needed for presto support, do not remove */ static int fas_enable_sbus64 = 1; #ifdef FASDEBUG int fasdebug = 0; int fasdebug_instance = -1; /* debug all instances */ static int fas_burstsizes_limit = -1; static int fas_no_sync_wide_backoff = 0; #endif /* FASDEBUG */ /* * Local static data protected by global mutex */ static kmutex_t fas_global_mutex; /* to allow concurrent attach */ static int fas_scsi_watchdog_tick; /* in seconds, for all */ /* instances */ static clock_t fas_tick; /* fas_watch() interval in Hz */ static timeout_id_t fas_reset_watch; /* timeout id for reset watch */ static timeout_id_t fas_timeout_id = 0; static int fas_timeout_initted = 0; static krwlock_t fas_global_rwlock; static void *fas_state; /* soft state ptr */ static struct fas *fas_head; /* link all softstate structures */ static struct fas *fas_tail; /* for fas_watch() */ static kmutex_t fas_log_mutex; static char fas_log_buf[256]; _NOTE(MUTEX_PROTECTS_DATA(fas_global_mutex, fas_reset_watch)) _NOTE(DATA_READABLE_WITHOUT_LOCK(fas_state fas_head fas_tail \ fas_scsi_watchdog_tick fas_tick)) _NOTE(SCHEME_PROTECTS_DATA("safe sharing", fas::f_quiesce_timeid)) /* * dma attribute structure for scsi engine */ static ddi_dma_attr_t dma_fasattr = { DMA_ATTR_V0, (unsigned long long)0, (unsigned long long)0xffffffff, (unsigned long long)((1<<24)-1), 1, DEFAULT_BURSTSIZE, 1, (unsigned long long)0xffffffff, (unsigned long long)0xffffffff, 1, 512, 0 }; /* * optional torture test stuff */ #ifdef FASDEBUG #define FAS_TEST static int fas_ptest_emsgin; static int fas_ptest_msgin; static int fas_ptest_msg = -1; static int fas_ptest_status; static int fas_ptest_data_in; static int fas_atest; static int fas_atest_disc; static int fas_atest_reconn; static void fas_test_abort(struct fas *fas, int slot); static int fas_rtest; static int fas_rtest_type; static void fas_test_reset(struct fas *fas, int slot); static int fas_force_timeout; static int fas_btest; static int fas_test_stop; static int fas_transport_busy; static int fas_transport_busy_rqs; static int fas_transport_reject; static int fas_arqs_failure; static int fas_tran_err; static int fas_test_untagged; static int fas_enable_untagged; #endif /* * warlock directives */ _NOTE(DATA_READABLE_WITHOUT_LOCK(dma fasdebug)) _NOTE(SCHEME_PROTECTS_DATA("just test variables", fas_transport_busy)) _NOTE(SCHEME_PROTECTS_DATA("just test variables", fas_transport_busy_rqs)) _NOTE(SCHEME_PROTECTS_DATA("just test variables", fas_transport_reject)) _NOTE(SCHEME_PROTECTS_DATA("just test variables", fas_arqs_failure)) _NOTE(SCHEME_PROTECTS_DATA("just test variables", fas_tran_err)) _NOTE(MUTEX_PROTECTS_DATA(fas_log_mutex, fas_log_buf)) _NOTE(MUTEX_PROTECTS_DATA(fas_global_mutex, fas_reset_watch)) _NOTE(DATA_READABLE_WITHOUT_LOCK(fas_state fas_head fas_tail \ fas_scsi_watchdog_tick fas_tick)) /* * function prototypes * * scsa functions are exported by means of the transport table: */ static int fas_scsi_tgt_probe(struct scsi_device *sd, int (*waitfunc)(void)); static int fas_scsi_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int fas_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt); static int fas_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt); static int fas_scsi_reset(struct scsi_address *ap, int level); static int fas_scsi_getcap(struct scsi_address *ap, char *cap, int whom); static int fas_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom); static struct scsi_pkt *fas_scsi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(), caddr_t arg); static void fas_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt); static void fas_scsi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt); static void fas_scsi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt); /* * internal functions: */ static int fas_prepare_pkt(struct fas *fas, struct fas_cmd *sp); static int fas_alloc_tag(struct fas *fas, struct fas_cmd *sp); static int fas_accept_pkt(struct fas *fas, struct fas_cmd *sp, int flag); static void fas_empty_waitQ(struct fas *fas); static void fas_move_waitQ_to_readyQ(struct fas *fas); static void fas_check_waitQ_and_mutex_exit(struct fas *fas); static int fas_istart(struct fas *fas); static int fas_ustart(struct fas *fas); static int fas_startcmd(struct fas *fas, struct fas_cmd *sp); static int fas_pkt_alloc_extern(struct fas *fas, struct fas_cmd *sp, int cmdlen, int tgtlen, int statuslen, int kf); static void fas_pkt_destroy_extern(struct fas *fas, struct fas_cmd *sp); static int fas_kmem_cache_constructor(void *buf, void *cdrarg, int kmflags); static void fas_kmem_cache_destructor(void *buf, void *cdrarg); static int fas_finish(struct fas *fas); static void fas_handle_qfull(struct fas *fas, struct fas_cmd *sp); static void fas_restart_cmd(void *); static int fas_dopoll(struct fas *fas, int timeout); static void fas_runpoll(struct fas *fas, short slot, struct fas_cmd *sp); static uint_t fas_intr(caddr_t arg); static int fas_intr_svc(struct fas *fas); static int fas_phasemanage(struct fas *fas); static int fas_handle_unknown(struct fas *fas); static int fas_handle_cmd_start(struct fas *fas); static int fas_handle_cmd_done(struct fas *fas); static int fas_handle_msg_out_start(struct fas *fas); static int fas_handle_msg_out_done(struct fas *fas); static int fas_handle_clearing(struct fas *fas); static int fas_handle_data_start(struct fas *fas); static int fas_handle_data_done(struct fas *fas); static int fas_handle_c_cmplt(struct fas *fas); static int fas_handle_msg_in_start(struct fas *fas); static int fas_handle_more_msgin(struct fas *fas); static int fas_handle_msg_in_done(struct fas *fas); static int fas_onebyte_msg(struct fas *fas); static int fas_twobyte_msg(struct fas *fas); static int fas_multibyte_msg(struct fas *fas); static void fas_revert_to_async(struct fas *fas, int tgt); static int fas_finish_select(struct fas *fas); static int fas_reselect_preempt(struct fas *fas); static int fas_reconnect(struct fas *fas); static int fas_handle_selection(struct fas *fas); static void fas_head_of_readyQ(struct fas *fas, struct fas_cmd *sp); static int fas_handle_gross_err(struct fas *fas); static int fas_illegal_cmd_or_bus_reset(struct fas *fas); static int fas_check_dma_error(struct fas *fas); static void fas_make_sdtr(struct fas *fas, int msgout_offset, int target); static void fas_make_wdtr(struct fas *fas, int msgout_offset, int target, int width); static void fas_update_props(struct fas *fas, int tgt); static void fas_update_this_prop(struct fas *fas, char *property, int value); static int fas_commoncap(struct scsi_address *ap, char *cap, int val, int tgtonly, int doset); static void fas_watch(void *arg); static void fas_watchsubr(struct fas *fas); static void fas_cmd_timeout(struct fas *fas, int slot); static void fas_sync_wide_backoff(struct fas *fas, struct fas_cmd *sp, int slot); static void fas_reset_sync_wide(struct fas *fas); static void fas_set_wide_conf3(struct fas *fas, int target, int width); static void fas_force_renegotiation(struct fas *fas, int target); static int fas_set_new_window(struct fas *fas, struct fas_cmd *sp); static int fas_restore_pointers(struct fas *fas, struct fas_cmd *sp); static int fas_next_window(struct fas *fas, struct fas_cmd *sp, uint64_t end); /*PRINTFLIKE3*/ static void fas_log(struct fas *fas, int level, const char *fmt, ...); /*PRINTFLIKE2*/ static void fas_printf(struct fas *fas, const char *fmt, ...); static void fas_printstate(struct fas *fas, char *msg); static void fas_dump_cmd(struct fas *fas, struct fas_cmd *sp); static void fas_short_dump_cmd(struct fas *fas, struct fas_cmd *sp); static char *fas_state_name(ushort_t state); static void fas_makeproxy_cmd(struct fas_cmd *sp, struct scsi_address *ap, struct scsi_pkt *pkt, int nmsg, ...); static int fas_do_proxy_cmd(struct fas *fas, struct fas_cmd *sp, struct scsi_address *ap, char *what); static void fas_internal_reset(struct fas *fas, int reset_action); static int fas_alloc_active_slots(struct fas *fas, int slot, int flag); static int fas_abort_curcmd(struct fas *fas); static int fas_abort_cmd(struct fas *fas, struct fas_cmd *sp, int slot); static int fas_do_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt); static int fas_do_scsi_reset(struct scsi_address *ap, int level); static int fas_remove_from_readyQ(struct fas *fas, struct fas_cmd *sp, int slot); static void fas_flush_readyQ(struct fas *fas, int slot); static void fas_flush_tagQ(struct fas *fas, int slot); static void fas_flush_cmd(struct fas *fas, struct fas_cmd *sp, uchar_t reason, uint_t stat); static int fas_abort_connected_cmd(struct fas *fas, struct fas_cmd *sp, uchar_t msg); static int fas_abort_disconnected_cmd(struct fas *fas, struct scsi_address *ap, struct fas_cmd *sp, uchar_t msg, int slot); static void fas_mark_packets(struct fas *fas, int slot, uchar_t reason, uint_t stat); static void fas_set_pkt_reason(struct fas *fas, struct fas_cmd *sp, uchar_t reason, uint_t stat); static int fas_reset_bus(struct fas *fas); static int fas_reset_recovery(struct fas *fas); static int fas_reset_connected_cmd(struct fas *fas, struct scsi_address *ap); static int fas_reset_disconnected_cmd(struct fas *fas, struct scsi_address *ap); static void fas_start_watch_reset_delay(struct fas *); static void fas_setup_reset_delay(struct fas *fas); static void fas_watch_reset_delay(void *arg); static int fas_watch_reset_delay_subr(struct fas *fas); static void fas_reset_cleanup(struct fas *fas, int slot); static int fas_scsi_reset_notify(struct scsi_address *ap, int flag, void (*callback)(caddr_t), caddr_t arg); static int fas_scsi_quiesce(dev_info_t *hba_dip); static int fas_scsi_unquiesce(dev_info_t *hba_dip); static void fas_set_throttles(struct fas *fas, int slot, int n, int what); static void fas_set_all_lun_throttles(struct fas *fas, int slot, int what); static void fas_full_throttle(struct fas *fas, int slot); static void fas_remove_cmd(struct fas *fas, struct fas_cmd *sp, int timeout); static void fas_decrement_ncmds(struct fas *fas, struct fas_cmd *sp); static int fas_quiesce_bus(struct fas *fas); static int fas_unquiesce_bus(struct fas *fas); static void fas_ncmds_checkdrain(void *arg); static int fas_check_outstanding(struct fas *fas); static int fas_create_arq_pkt(struct fas *fas, struct scsi_address *ap); static int fas_delete_arq_pkt(struct fas *fas, struct scsi_address *ap); static int fas_handle_sts_chk(struct fas *fas, struct fas_cmd *sp); void fas_complete_arq_pkt(struct scsi_pkt *pkt); void fas_call_pkt_comp(struct fas *fas, struct fas_cmd *sp); void fas_empty_callbackQ(struct fas *fas); int fas_init_callbacks(struct fas *fas); void fas_destroy_callbacks(struct fas *fas); static int fas_check_dma_error(struct fas *fas); static int fas_init_chip(struct fas *fas, uchar_t id); static void fas_read_fifo(struct fas *fas); static void fas_write_fifo(struct fas *fas, uchar_t *buf, int length, int pad); #ifdef FASDEBUG static void fas_reg_cmd_write(struct fas *fas, uint8_t cmd); static void fas_reg_write(struct fas *fas, volatile uint8_t *p, uint8_t what); static uint8_t fas_reg_read(struct fas *fas, volatile uint8_t *p); static void fas_dma_reg_write(struct fas *fas, volatile uint32_t *p, uint32_t what); static uint32_t fas_dma_reg_read(struct fas *fas, volatile uint32_t *p); #else #define fas_reg_cmd_write(fas, cmd) \ fas->f_reg->fas_cmd = (cmd), fas->f_last_cmd = (cmd) #define fas_reg_write(fas, p, what) *(p) = (what) #define fas_reg_read(fas, p) *(p) #define fas_dma_reg_write(fas, p, what) *(p) = (what) #define fas_dma_reg_read(fas, p) *(p) #endif /* * autoconfiguration data and routines. */ static int fas_attach(dev_info_t *dev, ddi_attach_cmd_t cmd); static int fas_detach(dev_info_t *dev, ddi_detach_cmd_t cmd); static int fas_dr_detach(dev_info_t *dev); static struct dev_ops fas_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ ddi_no_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ fas_attach, /* attach */ fas_detach, /* detach */ nodev, /* reset */ NULL, /* driver operations */ NULL, /* bus operations */ NULL, /* power */ ddi_quiesce_not_supported, /* devo_quiesce */ }; static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ "FAS SCSI HBA Driver", /* Name of the module. */ &fas_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; int _init(void) { int rval; /* CONSTCOND */ ASSERT(NO_COMPETING_THREADS); rval = ddi_soft_state_init(&fas_state, sizeof (struct fas), FAS_INITIAL_SOFT_SPACE); if (rval != 0) { return (rval); } if ((rval = scsi_hba_init(&modlinkage)) != 0) { ddi_soft_state_fini(&fas_state); return (rval); } mutex_init(&fas_global_mutex, NULL, MUTEX_DRIVER, NULL); rw_init(&fas_global_rwlock, NULL, RW_DRIVER, NULL); mutex_init(&fas_log_mutex, NULL, MUTEX_DRIVER, NULL); if ((rval = mod_install(&modlinkage)) != 0) { mutex_destroy(&fas_log_mutex); rw_destroy(&fas_global_rwlock); mutex_destroy(&fas_global_mutex); ddi_soft_state_fini(&fas_state); scsi_hba_fini(&modlinkage); return (rval); } return (rval); } int _fini(void) { int rval; /* CONSTCOND */ ASSERT(NO_COMPETING_THREADS); if ((rval = mod_remove(&modlinkage)) == 0) { ddi_soft_state_fini(&fas_state); scsi_hba_fini(&modlinkage); mutex_destroy(&fas_log_mutex); rw_destroy(&fas_global_rwlock); mutex_destroy(&fas_global_mutex); } return (rval); } int _info(struct modinfo *modinfop) { /* CONSTCOND */ ASSERT(NO_COMPETING_THREADS); return (mod_info(&modlinkage, modinfop)); } static int fas_scsi_tgt_probe(struct scsi_device *sd, int (*waitfunc)(void)) { dev_info_t *dip = ddi_get_parent(sd->sd_dev); int rval = SCSIPROBE_FAILURE; scsi_hba_tran_t *tran; struct fas *fas; int tgt = sd->sd_address.a_target; tran = ddi_get_driver_private(dip); ASSERT(tran != NULL); fas = TRAN2FAS(tran); /* * force renegotiation since inquiry cmds do not cause * check conditions */ mutex_enter(FAS_MUTEX(fas)); fas_force_renegotiation(fas, tgt); mutex_exit(FAS_MUTEX(fas)); rval = scsi_hba_probe(sd, waitfunc); /* * the scsi-options precedence is: * target-scsi-options highest * device-type-scsi-options * per bus scsi-options * global scsi-options lowest */ mutex_enter(FAS_MUTEX(fas)); if ((rval == SCSIPROBE_EXISTS) && ((fas->f_target_scsi_options_defined & (1 << tgt)) == 0)) { int options; options = scsi_get_device_type_scsi_options(dip, sd, -1); if (options != -1) { fas->f_target_scsi_options[tgt] = options; fas_log(fas, CE_NOTE, "?target%x-scsi-options = 0x%x\n", tgt, fas->f_target_scsi_options[tgt]); fas_force_renegotiation(fas, tgt); } } mutex_exit(FAS_MUTEX(fas)); IPRINTF2("target%x-scsi-options= 0x%x\n", tgt, fas->f_target_scsi_options[tgt]); return (rval); } /*ARGSUSED*/ static int fas_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd) { return (((sd->sd_address.a_target < NTARGETS_WIDE) && (sd->sd_address.a_lun < NLUNS_PER_TARGET)) ? DDI_SUCCESS : DDI_FAILURE); } /*ARGSUSED*/ static int fas_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct fas *fas = NULL; volatile struct dma *dmar = NULL; volatile struct fasreg *fasreg; ddi_dma_attr_t *fas_dma_attr; ddi_device_acc_attr_t dev_attr; int instance, id, slot, i, hm_rev; size_t rlen; uint_t count; char buf[64]; scsi_hba_tran_t *tran = NULL; char intr_added = 0; char mutex_init_done = 0; char hba_attached = 0; char bound_handle = 0; char *prop_template = "target%d-scsi-options"; char prop_str[32]; /* CONSTCOND */ ASSERT(NO_COMPETING_THREADS); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: if ((tran = ddi_get_driver_private(dip)) == NULL) return (DDI_FAILURE); fas = TRAN2FAS(tran); if (!fas) { return (DDI_FAILURE); } /* * Reset hardware and softc to "no outstanding commands" * Note that a check condition can result on first command * to a target. */ mutex_enter(FAS_MUTEX(fas)); fas_internal_reset(fas, FAS_RESET_SOFTC|FAS_RESET_FAS|FAS_RESET_DMA); (void) fas_reset_bus(fas); fas->f_suspended = 0; /* make sure that things get started */ (void) fas_istart(fas); fas_check_waitQ_and_mutex_exit(fas); mutex_enter(&fas_global_mutex); if (fas_timeout_id == 0) { fas_timeout_id = timeout(fas_watch, NULL, fas_tick); fas_timeout_initted = 1; } mutex_exit(&fas_global_mutex); return (DDI_SUCCESS); default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); /* * Since we know that some instantiations of this device can * be plugged into slave-only SBus slots, check to see whether * this is one such. */ if (ddi_slaveonly(dip) == DDI_SUCCESS) { cmn_err(CE_WARN, "fas%d: device in slave-only slot", instance); return (DDI_FAILURE); } if (ddi_intr_hilevel(dip, 0)) { /* * Interrupt number '0' is a high-level interrupt. * At this point you either add a special interrupt * handler that triggers a soft interrupt at a lower level, * or - more simply and appropriately here - you just * fail the attach. */ cmn_err(CE_WARN, "fas%d: Device is using a hilevel intr", instance); return (DDI_FAILURE); } /* * Allocate softc information. */ if (ddi_soft_state_zalloc(fas_state, instance) != DDI_SUCCESS) { cmn_err(CE_WARN, "fas%d: cannot allocate soft state", instance); goto fail; } fas = (struct fas *)ddi_get_soft_state(fas_state, instance); if (fas == NULL) { goto fail; } /* * map in device registers */ dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; dev_attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC; dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; if (ddi_regs_map_setup(dip, (uint_t)0, (caddr_t *)&dmar, (off_t)0, (off_t)sizeof (struct dma), &dev_attr, &fas->f_dmar_acc_handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "fas%d: cannot map dma", instance); goto fail; } if (ddi_regs_map_setup(dip, (uint_t)1, (caddr_t *)&fasreg, (off_t)0, (off_t)sizeof (struct fasreg), &dev_attr, &fas->f_regs_acc_handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "fas%d: unable to map fas366 registers", instance); goto fail; } fas_dma_attr = &dma_fasattr; if (ddi_dma_alloc_handle(dip, fas_dma_attr, DDI_DMA_SLEEP, NULL, &fas->f_dmahandle) != DDI_SUCCESS) { cmn_err(CE_WARN, "fas%d: cannot alloc dma handle", instance); goto fail; } /* * allocate cmdarea and its dma handle */ if (ddi_dma_mem_alloc(fas->f_dmahandle, (uint_t)2*FIFOSIZE, &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&fas->f_cmdarea, &rlen, &fas->f_cmdarea_acc_handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "fas%d: cannot alloc cmd area", instance); goto fail; } fas->f_reg = fasreg; fas->f_dma = dmar; fas->f_instance = instance; if (ddi_dma_addr_bind_handle(fas->f_dmahandle, NULL, (caddr_t)fas->f_cmdarea, rlen, DDI_DMA_RDWR|DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &fas->f_dmacookie, &count) != DDI_DMA_MAPPED) { cmn_err(CE_WARN, "fas%d: cannot bind cmdarea", instance); goto fail; } bound_handle++; ASSERT(count == 1); /* * Allocate a transport structure */ tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); /* Indicate that we are 'sizeof (scsi_*(9S))' clean. */ scsi_size_clean(dip); /* SCSI_SIZE_CLEAN_VERIFY ok */ /* * initialize transport structure */ fas->f_tran = tran; fas->f_dev = dip; tran->tran_hba_private = fas; tran->tran_tgt_private = NULL; tran->tran_tgt_init = fas_scsi_tgt_init; tran->tran_tgt_probe = fas_scsi_tgt_probe; tran->tran_tgt_free = NULL; tran->tran_start = fas_scsi_start; tran->tran_abort = fas_scsi_abort; tran->tran_reset = fas_scsi_reset; tran->tran_getcap = fas_scsi_getcap; tran->tran_setcap = fas_scsi_setcap; tran->tran_init_pkt = fas_scsi_init_pkt; tran->tran_destroy_pkt = fas_scsi_destroy_pkt; tran->tran_dmafree = fas_scsi_dmafree; tran->tran_sync_pkt = fas_scsi_sync_pkt; tran->tran_reset_notify = fas_scsi_reset_notify; tran->tran_get_bus_addr = NULL; tran->tran_get_name = NULL; tran->tran_quiesce = fas_scsi_quiesce; tran->tran_unquiesce = fas_scsi_unquiesce; tran->tran_bus_reset = NULL; tran->tran_add_eventcall = NULL; tran->tran_get_eventcookie = NULL; tran->tran_post_event = NULL; tran->tran_remove_eventcall = NULL; fas->f_force_async = 0; /* * disable tagged queuing and wide for all targets * (will be enabled by target driver if required) * sync is enabled by default */ fas->f_nowide = fas->f_notag = ALL_TARGETS; fas->f_force_narrow = ALL_TARGETS; /* * By default we assume embedded devices and save time * checking for timeouts in fas_watch() by skipping * the rest of luns * If we're talking to any non-embedded devices, * we can't cheat and skip over non-zero luns anymore * in fas_watch() and fas_ustart(). */ fas->f_dslot = NLUNS_PER_TARGET; /* * f_active is used for saving disconnected cmds; * For tagged targets, we need to increase the size later * Only allocate for Lun == 0, if we probe a lun > 0 then * we allocate an active structure * If TQ gets enabled then we need to increase the size * to hold 256 cmds */ for (slot = 0; slot < N_SLOTS; slot += NLUNS_PER_TARGET) { (void) fas_alloc_active_slots(fas, slot, KM_SLEEP); } /* * initialize the qfull retry counts */ for (i = 0; i < NTARGETS_WIDE; i++) { fas->f_qfull_retries[i] = QFULL_RETRIES; fas->f_qfull_retry_interval[i] = drv_usectohz(QFULL_RETRY_INTERVAL * 1000); } /* * Initialize throttles. */ fas_set_throttles(fas, 0, N_SLOTS, MAX_THROTTLE); /* * Initialize mask of deferred property updates */ fas->f_props_update = 0; /* * set host ID */ fas->f_fasconf = DEFAULT_HOSTID; id = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "initiator-id", -1); if (id == -1) { id = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "scsi-initiator-id", -1); } if (id != DEFAULT_HOSTID && id >= 0 && id < NTARGETS_WIDE) { fas_log(fas, CE_NOTE, "?initiator SCSI ID now %d\n", id); fas->f_fasconf = (uchar_t)id; } /* * find the burstsize and reduce ours if necessary */ fas->f_dma_attr = fas_dma_attr; fas->f_dma_attr->dma_attr_burstsizes &= ddi_dma_burstsizes(fas->f_dmahandle); #ifdef FASDEBUG fas->f_dma_attr->dma_attr_burstsizes &= fas_burstsizes_limit; IPRINTF1("dma burstsize=%x\n", fas->f_dma_attr->dma_attr_burstsizes); #endif /* * Attach this instance of the hba */ if (scsi_hba_attach_setup(dip, fas->f_dma_attr, tran, 0) != DDI_SUCCESS) { fas_log(fas, CE_WARN, "scsi_hba_attach_setup failed"); goto fail; } hba_attached++; /* * if scsi-options property exists, use it */ fas->f_scsi_options = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "scsi-options", DEFAULT_SCSI_OPTIONS); /* * if scsi-selection-timeout property exists, use it */ fas_selection_timeout = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "scsi-selection-timeout", SCSI_DEFAULT_SELECTION_TIMEOUT); /* * if hm-rev property doesn't exist, use old scheme for rev */ hm_rev = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "hm-rev", -1); if (hm_rev == 0xa0 || hm_rev == -1) { if (DMAREV(dmar) != 0) { fas->f_hm_rev = 0x20; fas_log(fas, CE_WARN, "obsolete rev 2.0 FEPS chip, " "possible data corruption"); } else { fas->f_hm_rev = 0x10; fas_log(fas, CE_WARN, "obsolete and unsupported rev 1.0 FEPS chip"); goto fail; } } else if (hm_rev == 0x20) { fas->f_hm_rev = 0x21; fas_log(fas, CE_WARN, "obsolete rev 2.1 FEPS chip"); } else { fas->f_hm_rev = (uchar_t)hm_rev; fas_log(fas, CE_NOTE, "?rev %x.%x FEPS chip\n", (hm_rev >> 4) & 0xf, hm_rev & 0xf); } if ((fas->f_scsi_options & SCSI_OPTIONS_SYNC) == 0) { fas->f_nosync = ALL_TARGETS; } if ((fas->f_scsi_options & SCSI_OPTIONS_WIDE) == 0) { fas->f_nowide = ALL_TARGETS; } /* * if target-scsi-options property exists, use it; * otherwise use the f_scsi_options */ for (i = 0; i < NTARGETS_WIDE; i++) { (void) sprintf(prop_str, prop_template, i); fas->f_target_scsi_options[i] = ddi_prop_get_int( DDI_DEV_T_ANY, dip, 0, prop_str, -1); if (fas->f_target_scsi_options[i] != -1) { fas_log(fas, CE_NOTE, "?target%x-scsi-options=0x%x\n", i, fas->f_target_scsi_options[i]); fas->f_target_scsi_options_defined |= 1 << i; } else { fas->f_target_scsi_options[i] = fas->f_scsi_options; } if (((fas->f_target_scsi_options[i] & SCSI_OPTIONS_DR) == 0) && (fas->f_target_scsi_options[i] & SCSI_OPTIONS_TAG)) { fas->f_target_scsi_options[i] &= ~SCSI_OPTIONS_TAG; fas_log(fas, CE_WARN, "Disabled TQ since disconnects are disabled"); } } fas->f_scsi_tag_age_limit = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "scsi-tag-age-limit", DEFAULT_TAG_AGE_LIMIT); fas->f_scsi_reset_delay = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "scsi-reset-delay", SCSI_DEFAULT_RESET_DELAY); if (fas->f_scsi_reset_delay == 0) { fas_log(fas, CE_NOTE, "scsi_reset_delay of 0 is not recommended," " resetting to SCSI_DEFAULT_RESET_DELAY\n"); fas->f_scsi_reset_delay = SCSI_DEFAULT_RESET_DELAY; } /* * get iblock cookie and initialize mutexes */ if (ddi_get_iblock_cookie(dip, (uint_t)0, &fas->f_iblock) != DDI_SUCCESS) { cmn_err(CE_WARN, "fas_attach: cannot get iblock cookie"); goto fail; } mutex_init(&fas->f_mutex, NULL, MUTEX_DRIVER, fas->f_iblock); cv_init(&fas->f_cv, NULL, CV_DRIVER, NULL); /* * initialize mutex for waitQ */ mutex_init(&fas->f_waitQ_mutex, NULL, MUTEX_DRIVER, fas->f_iblock); mutex_init_done++; /* * initialize callback mechanism (immediate callback) */ mutex_enter(&fas_global_mutex); if (fas_init_callbacks(fas)) { mutex_exit(&fas_global_mutex); goto fail; } mutex_exit(&fas_global_mutex); /* * kstat_intr support */ (void) sprintf(buf, "fas%d", instance); fas->f_intr_kstat = kstat_create("fas", instance, buf, "controller", \ KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT); if (fas->f_intr_kstat) kstat_install(fas->f_intr_kstat); /* * install interrupt handler */ mutex_enter(FAS_MUTEX(fas)); if (ddi_add_intr(dip, (uint_t)0, &fas->f_iblock, NULL, fas_intr, (caddr_t)fas)) { cmn_err(CE_WARN, "fas: cannot add intr"); mutex_exit(FAS_MUTEX(fas)); goto fail; } intr_added++; /* * initialize fas chip */ if (fas_init_chip(fas, id)) { cmn_err(CE_WARN, "fas: cannot initialize"); mutex_exit(FAS_MUTEX(fas)); goto fail; } mutex_exit(FAS_MUTEX(fas)); /* * create kmem cache for packets */ (void) sprintf(buf, "fas%d_cache", instance); fas->f_kmem_cache = kmem_cache_create(buf, EXTCMD_SIZE, 8, fas_kmem_cache_constructor, fas_kmem_cache_destructor, NULL, (void *)fas, NULL, 0); if (fas->f_kmem_cache == NULL) { cmn_err(CE_WARN, "fas: cannot create kmem_cache"); goto fail; } /* * at this point, we are not going to fail the attach * so there is no need to undo the rest: * * add this fas to the list, this makes debugging easier * and fas_watch() needs it to walk thru all fas's */ rw_enter(&fas_global_rwlock, RW_WRITER); if (fas_head == NULL) { fas_head = fas; } else { fas_tail->f_next = fas; } fas_tail = fas; /* point to last fas in list */ rw_exit(&fas_global_rwlock); /* * there is one watchdog handler for all driver instances. * start the watchdog if it hasn't been done yet */ mutex_enter(&fas_global_mutex); if (fas_scsi_watchdog_tick == 0) { fas_scsi_watchdog_tick = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, "scsi-watchdog-tick", DEFAULT_WD_TICK); if (fas_scsi_watchdog_tick != DEFAULT_WD_TICK) { fas_log(fas, CE_NOTE, "?scsi-watchdog-tick=%d\n", fas_scsi_watchdog_tick); } fas_tick = drv_usectohz((clock_t) fas_scsi_watchdog_tick * 1000000); IPRINTF2("fas scsi watchdog tick=%x, fas_tick=%lx\n", fas_scsi_watchdog_tick, fas_tick); if (fas_timeout_id == 0) { fas_timeout_id = timeout(fas_watch, NULL, fas_tick); fas_timeout_initted = 1; } } mutex_exit(&fas_global_mutex); ddi_report_dev(dip); return (DDI_SUCCESS); fail: cmn_err(CE_WARN, "fas%d: cannot attach", instance); if (fas) { for (slot = 0; slot < N_SLOTS; slot++) { struct f_slots *active = fas->f_active[slot]; if (active) { kmem_free(active, active->f_size); fas->f_active[slot] = NULL; } } if (mutex_init_done) { mutex_destroy(&fas->f_mutex); mutex_destroy(&fas->f_waitQ_mutex); cv_destroy(&fas->f_cv); } if (intr_added) { ddi_remove_intr(dip, (uint_t)0, fas->f_iblock); } /* * kstat_intr support */ if (fas->f_intr_kstat) { kstat_delete(fas->f_intr_kstat); } if (hba_attached) { (void) scsi_hba_detach(dip); } if (tran) { scsi_hba_tran_free(tran); } if (fas->f_kmem_cache) { kmem_cache_destroy(fas->f_kmem_cache); } if (fas->f_cmdarea) { if (bound_handle) { (void) ddi_dma_unbind_handle(fas->f_dmahandle); } ddi_dma_mem_free(&fas->f_cmdarea_acc_handle); } if (fas->f_dmahandle) { ddi_dma_free_handle(&fas->f_dmahandle); } fas_destroy_callbacks(fas); if (fas->f_regs_acc_handle) { ddi_regs_map_free(&fas->f_regs_acc_handle); } if (fas->f_dmar_acc_handle) { ddi_regs_map_free(&fas->f_dmar_acc_handle); } ddi_soft_state_free(fas_state, instance); ddi_remove_minor_node(dip, NULL); } return (DDI_FAILURE); } /*ARGSUSED*/ static int fas_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct fas *fas, *nfas; scsi_hba_tran_t *tran; /* CONSTCOND */ ASSERT(NO_COMPETING_THREADS); switch (cmd) { case DDI_DETACH: return (fas_dr_detach(dip)); case DDI_SUSPEND: if ((tran = ddi_get_driver_private(dip)) == NULL) return (DDI_FAILURE); fas = TRAN2FAS(tran); if (!fas) { return (DDI_FAILURE); } mutex_enter(FAS_MUTEX(fas)); fas->f_suspended = 1; if (fas->f_ncmds) { (void) fas_reset_bus(fas); (void) fas_dopoll(fas, SHORT_POLL_TIMEOUT); } /* * disable dma and fas interrupt */ fas->f_dma_csr &= ~DMA_INTEN; fas->f_dma_csr &= ~DMA_ENDVMA; fas_dma_reg_write(fas, &fas->f_dma->dma_csr, fas->f_dma_csr); mutex_exit(FAS_MUTEX(fas)); if (fas->f_quiesce_timeid) { (void) untimeout(fas->f_quiesce_timeid); fas->f_quiesce_timeid = 0; } if (fas->f_restart_cmd_timeid) { (void) untimeout(fas->f_restart_cmd_timeid); fas->f_restart_cmd_timeid = 0; } /* Last fas? */ rw_enter(&fas_global_rwlock, RW_WRITER); for (nfas = fas_head; nfas; nfas = nfas->f_next) { if (!nfas->f_suspended) { rw_exit(&fas_global_rwlock); return (DDI_SUCCESS); } } rw_exit(&fas_global_rwlock); mutex_enter(&fas_global_mutex); if (fas_timeout_id != 0) { timeout_id_t tid = fas_timeout_id; fas_timeout_id = 0; fas_timeout_initted = 0; mutex_exit(&fas_global_mutex); (void) untimeout(tid); } else { mutex_exit(&fas_global_mutex); } mutex_enter(&fas_global_mutex); if (fas_reset_watch) { timeout_id_t tid = fas_reset_watch; fas_reset_watch = 0; mutex_exit(&fas_global_mutex); (void) untimeout(tid); } else { mutex_exit(&fas_global_mutex); } return (DDI_SUCCESS); default: return (DDI_FAILURE); } _NOTE(NOT_REACHED) /* NOTREACHED */ } static int fas_dr_detach(dev_info_t *dip) { struct fas *fas, *f; scsi_hba_tran_t *tran; short slot; int i, j; if ((tran = ddi_get_driver_private(dip)) == NULL) return (DDI_FAILURE); fas = TRAN2FAS(tran); if (!fas) { return (DDI_FAILURE); } /* * disable interrupts */ fas->f_dma_csr &= ~DMA_INTEN; fas->f_dma->dma_csr = fas->f_dma_csr; ddi_remove_intr(dip, (uint_t)0, fas->f_iblock); /* * Remove device instance from the global linked list */ rw_enter(&fas_global_rwlock, RW_WRITER); if (fas_head == fas) { f = fas_head = fas->f_next; } else { for (f = fas_head; f != (struct fas *)NULL; f = f->f_next) { if (f->f_next == fas) { f->f_next = fas->f_next; break; } } /* * Instance not in softc list. Since the * instance is not there in softc list, don't * enable interrupts, the instance is effectively * unusable. */ if (f == (struct fas *)NULL) { cmn_err(CE_WARN, "fas_dr_detach: fas instance not" " in softc list!"); rw_exit(&fas_global_rwlock); return (DDI_FAILURE); } } if (fas_tail == fas) fas_tail = f; rw_exit(&fas_global_rwlock); if (fas->f_intr_kstat) kstat_delete(fas->f_intr_kstat); fas_destroy_callbacks(fas); scsi_hba_reset_notify_tear_down(fas->f_reset_notify_listf); mutex_enter(&fas_global_mutex); /* * destroy any outstanding tagged command info */ for (slot = 0; slot < N_SLOTS; slot++) { struct f_slots *active = fas->f_active[slot]; if (active) { ushort_t tag; for (tag = 0; tag < active->f_n_slots; tag++) { struct fas_cmd *sp = active->f_slot[tag]; if (sp) { struct scsi_pkt *pkt = sp->cmd_pkt; if (pkt) { (void) fas_scsi_destroy_pkt( &pkt->pkt_address, pkt); } /* sp freed in fas_scsi_destroy_pkt */ active->f_slot[tag] = NULL; } } kmem_free(active, active->f_size); fas->f_active[slot] = NULL; } ASSERT(fas->f_tcmds[slot] == 0); } /* * disallow timeout thread rescheduling */ fas->f_flags |= FAS_FLG_NOTIMEOUTS; mutex_exit(&fas_global_mutex); if (fas->f_quiesce_timeid) { (void) untimeout(fas->f_quiesce_timeid); } /* * last fas? ... if active, CANCEL watch threads. */ mutex_enter(&fas_global_mutex); if (fas_head == (struct fas *)NULL) { if (fas_timeout_initted) { timeout_id_t tid = fas_timeout_id; fas_timeout_initted = 0; fas_timeout_id = 0; /* don't resched */ mutex_exit(&fas_global_mutex); (void) untimeout(tid); mutex_enter(&fas_global_mutex); } if (fas_reset_watch) { mutex_exit(&fas_global_mutex); (void) untimeout(fas_reset_watch); mutex_enter(&fas_global_mutex); fas_reset_watch = 0; } } mutex_exit(&fas_global_mutex); if (fas->f_restart_cmd_timeid) { (void) untimeout(fas->f_restart_cmd_timeid); fas->f_restart_cmd_timeid = 0; } /* * destroy outstanding ARQ pkts */ for (i = 0; i < NTARGETS_WIDE; i++) { for (j = 0; j < NLUNS_PER_TARGET; j++) { int slot = i * NLUNS_PER_TARGET | j; if (fas->f_arq_pkt[slot]) { struct scsi_address sa; sa.a_hba_tran = NULL; /* not used */ sa.a_target = (ushort_t)i; sa.a_lun = (uchar_t)j; (void) fas_delete_arq_pkt(fas, &sa); } } } /* * Remove device MT locks and CV */ mutex_destroy(&fas->f_waitQ_mutex); mutex_destroy(&fas->f_mutex); cv_destroy(&fas->f_cv); /* * Release miscellaneous device resources */ if (fas->f_kmem_cache) { kmem_cache_destroy(fas->f_kmem_cache); } if (fas->f_cmdarea != (uchar_t *)NULL) { (void) ddi_dma_unbind_handle(fas->f_dmahandle); ddi_dma_mem_free(&fas->f_cmdarea_acc_handle); } if (fas->f_dmahandle != (ddi_dma_handle_t)NULL) { ddi_dma_free_handle(&fas->f_dmahandle); } if (fas->f_regs_acc_handle) { ddi_regs_map_free(&fas->f_regs_acc_handle); } if (fas->f_dmar_acc_handle) { ddi_regs_map_free(&fas->f_dmar_acc_handle); } /* * Remove properties created during attach() */ ddi_prop_remove_all(dip); /* * Delete the DMA limits, transport vectors and remove the device * links to the scsi_transport layer. * -- ddi_set_driver_private(dip, NULL) */ (void) scsi_hba_detach(dip); /* * Free the scsi_transport structure for this device. */ scsi_hba_tran_free(tran); ddi_soft_state_free(fas_state, ddi_get_instance(dip)); return (DDI_SUCCESS); } static int fas_quiesce_bus(struct fas *fas) { mutex_enter(FAS_MUTEX(fas)); IPRINTF("fas_quiesce: QUIESCEing\n"); IPRINTF3("fas_quiesce: ncmds (%d) ndisc (%d) state (%d)\n", fas->f_ncmds, fas->f_ndisc, fas->f_softstate); fas_set_throttles(fas, 0, N_SLOTS, HOLD_THROTTLE); if (fas_check_outstanding(fas)) { fas->f_softstate |= FAS_SS_DRAINING; fas->f_quiesce_timeid = timeout(fas_ncmds_checkdrain, fas, (FAS_QUIESCE_TIMEOUT * drv_usectohz(1000000))); if (cv_wait_sig(FAS_CV(fas), FAS_MUTEX(fas)) == 0) { /* * quiesce has been interrupted. */ IPRINTF("fas_quiesce: abort QUIESCE\n"); fas->f_softstate &= ~FAS_SS_DRAINING; fas_set_throttles(fas, 0, N_SLOTS, MAX_THROTTLE); (void) fas_istart(fas); if (fas->f_quiesce_timeid != 0) { mutex_exit(FAS_MUTEX(fas)); #ifndef __lock_lint /* warlock complains but there is a NOTE on this */ (void) untimeout(fas->f_quiesce_timeid); fas->f_quiesce_timeid = 0; #endif return (-1); } mutex_exit(FAS_MUTEX(fas)); return (-1); } else { IPRINTF("fas_quiesce: bus is QUIESCED\n"); ASSERT(fas->f_quiesce_timeid == 0); fas->f_softstate &= ~FAS_SS_DRAINING; fas->f_softstate |= FAS_SS_QUIESCED; mutex_exit(FAS_MUTEX(fas)); return (0); } } IPRINTF("fas_quiesce: bus was not busy QUIESCED\n"); mutex_exit(FAS_MUTEX(fas)); return (0); } static int fas_unquiesce_bus(struct fas *fas) { mutex_enter(FAS_MUTEX(fas)); fas->f_softstate &= ~FAS_SS_QUIESCED; fas_set_throttles(fas, 0, N_SLOTS, MAX_THROTTLE); (void) fas_istart(fas); IPRINTF("fas_quiesce: bus has been UNQUIESCED\n"); mutex_exit(FAS_MUTEX(fas)); return (0); } /* * invoked from timeout() to check the number of outstanding commands */ static void fas_ncmds_checkdrain(void *arg) { struct fas *fas = arg; mutex_enter(FAS_MUTEX(fas)); IPRINTF3("fas_checkdrain: ncmds (%d) ndisc (%d) state (%d)\n", fas->f_ncmds, fas->f_ndisc, fas->f_softstate); if (fas->f_softstate & FAS_SS_DRAINING) { fas->f_quiesce_timeid = 0; if (fas_check_outstanding(fas) == 0) { IPRINTF("fas_drain: bus has drained\n"); cv_signal(FAS_CV(fas)); } else { /* * throttle may have been reset by a bus reset * or fas_runpoll() * XXX shouldn't be necessary */ fas_set_throttles(fas, 0, N_SLOTS, HOLD_THROTTLE); IPRINTF("fas_drain: rescheduling timeout\n"); fas->f_quiesce_timeid = timeout(fas_ncmds_checkdrain, fas, (FAS_QUIESCE_TIMEOUT * drv_usectohz(1000000))); } } mutex_exit(FAS_MUTEX(fas)); } static int fas_check_outstanding(struct fas *fas) { uint_t slot; uint_t d = ((fas->f_dslot == 0)? 1 : fas->f_dslot); int ncmds = 0; ASSERT(mutex_owned(FAS_MUTEX(fas))); for (slot = 0; slot < N_SLOTS; slot += d) ncmds += fas->f_tcmds[slot]; return (ncmds); } #ifdef FASDEBUG /* * fas register read/write functions with tracing */ static void fas_reg_tracing(struct fas *fas, int type, int regno, uint32_t what) { fas->f_reg_trace[fas->f_reg_trace_index++] = type; fas->f_reg_trace[fas->f_reg_trace_index++] = regno; fas->f_reg_trace[fas->f_reg_trace_index++] = what; fas->f_reg_trace[fas->f_reg_trace_index++] = gethrtime(); fas->f_reg_trace[fas->f_reg_trace_index] = 0xff; if (fas->f_reg_trace_index >= REG_TRACE_BUF_SIZE) { fas->f_reg_trace_index = 0; } } static void fas_reg_cmd_write(struct fas *fas, uint8_t cmd) { volatile struct fasreg *fasreg = fas->f_reg; int regno = (uintptr_t)&fasreg->fas_cmd - (uintptr_t)fasreg; fasreg->fas_cmd = cmd; fas->f_last_cmd = cmd; EPRINTF1("issuing cmd %x\n", (uchar_t)cmd); fas_reg_tracing(fas, 0, regno, cmd); fas->f_reg_cmds++; } static void fas_reg_write(struct fas *fas, volatile uint8_t *p, uint8_t what) { int regno = (uintptr_t)p - (uintptr_t)fas->f_reg; *p = what; EPRINTF2("writing reg%x = %x\n", regno, what); fas_reg_tracing(fas, 1, regno, what); fas->f_reg_writes++; } static uint8_t fas_reg_read(struct fas *fas, volatile uint8_t *p) { uint8_t what; int regno = (uintptr_t)p - (uintptr_t)fas->f_reg; what = *p; EPRINTF2("reading reg%x => %x\n", regno, what); fas_reg_tracing(fas, 2, regno, what); fas->f_reg_reads++; return (what); } /* * dma register access routines */ static void fas_dma_reg_write(struct fas *fas, volatile uint32_t *p, uint32_t what) { *p = what; fas->f_reg_dma_writes++; #ifdef DMA_REG_TRACING { int regno = (uintptr_t)p - (uintptr_t)fas->f_dma; EPRINTF2("writing dma reg%x = %x\n", regno, what); fas_reg_tracing(fas, 3, regno, what); } #endif } static uint32_t fas_dma_reg_read(struct fas *fas, volatile uint32_t *p) { uint32_t what = *p; fas->f_reg_dma_reads++; #ifdef DMA_REG_TRACING { int regno = (uintptr_t)p - (uintptr_t)fas->f_dma; EPRINTF2("reading dma reg%x => %x\n", regno, what); fas_reg_tracing(fas, 4, regno, what); } #endif return (what); } #endif #define FIFO_EMPTY(fas) (fas_reg_read(fas, &fas->f_reg->fas_stat2) & \ FAS_STAT2_EMPTY) #define FIFO_CNT(fas) \ (fas_reg_read(fas, &fas->f_reg->fas_fifo_flag) & FIFO_CNT_MASK) #ifdef FASDEBUG static void fas_assert_atn(struct fas *fas) { fas_reg_cmd_write(fas, CMD_SET_ATN); #ifdef FAS_TEST if (fas_test_stop > 1) debug_enter("asserted atn"); #endif } #else #define fas_assert_atn(fas) fas_reg_cmd_write(fas, CMD_SET_ATN) #endif /* * DMA macros; we use a shadow copy of the dma_csr to save unnecessary * reads */ #define FAS_DMA_WRITE(fas, count, base, cmd) { \ volatile struct fasreg *fasreg = fas->f_reg; \ volatile struct dma *dmar = fas->f_dma; \ ASSERT((fas_dma_reg_read(fas, &dmar->dma_csr) & DMA_ENDVMA) == 0); \ SET_FAS_COUNT(fasreg, count); \ fas_reg_cmd_write(fas, cmd); \ fas_dma_reg_write(fas, &dmar->dma_count, count); \ fas->f_dma_csr |= \ DMA_WRITE | DMA_ENDVMA | DMA_DSBL_DRAIN; \ fas_dma_reg_write(fas, &dmar->dma_addr, (fas->f_lastdma = base)); \ fas_dma_reg_write(fas, &dmar->dma_csr, fas->f_dma_csr); \ } #define FAS_DMA_WRITE_SETUP(fas, count, base) { \ volatile struct fasreg *fasreg = fas->f_reg; \ volatile struct dma *dmar = fas->f_dma; \ ASSERT((fas_dma_reg_read(fas, &dmar->dma_csr) & DMA_ENDVMA) == 0); \ SET_FAS_COUNT(fasreg, count); \ fas_dma_reg_write(fas, &dmar->dma_count, count); \ fas->f_dma_csr |= \ DMA_WRITE | DMA_ENDVMA | DMA_DSBL_DRAIN; \ fas_dma_reg_write(fas, &dmar->dma_addr, (fas->f_lastdma = base)); \ } #define FAS_DMA_READ(fas, count, base, dmacount, cmd) { \ volatile struct fasreg *fasreg = fas->f_reg; \ volatile struct dma *dmar = fas->f_dma; \ ASSERT((fas_dma_reg_read(fas, &dmar->dma_csr) & DMA_ENDVMA) == 0); \ SET_FAS_COUNT(fasreg, count); \ fas_reg_cmd_write(fas, cmd); \ fas->f_dma_csr |= \ (fas->f_dma_csr & ~DMA_WRITE) | DMA_ENDVMA | DMA_DSBL_DRAIN; \ fas_dma_reg_write(fas, &dmar->dma_count, dmacount); \ fas_dma_reg_write(fas, &dmar->dma_addr, (fas->f_lastdma = base)); \ fas_dma_reg_write(fas, &dmar->dma_csr, fas->f_dma_csr); \ } static void FAS_FLUSH_DMA(struct fas *fas) { fas_dma_reg_write(fas, &fas->f_dma->dma_csr, DMA_RESET); fas->f_dma_csr |= (DMA_INTEN|DMA_TWO_CYCLE|DMA_DSBL_PARITY| DMA_DSBL_DRAIN); fas->f_dma_csr &= ~(DMA_ENDVMA | DMA_WRITE); fas_dma_reg_write(fas, &fas->f_dma->dma_csr, 0); fas_dma_reg_write(fas, &fas->f_dma->dma_csr, fas->f_dma_csr); fas_dma_reg_write(fas, &fas->f_dma->dma_addr, 0); } /* * FAS_FLUSH_DMA_HARD checks on REQPEND before taking away the reset */ static void FAS_FLUSH_DMA_HARD(struct fas *fas) { fas_dma_reg_write(fas, &fas->f_dma->dma_csr, DMA_RESET); fas->f_dma_csr |= (DMA_INTEN|DMA_TWO_CYCLE|DMA_DSBL_PARITY| DMA_DSBL_DRAIN); fas->f_dma_csr &= ~(DMA_ENDVMA | DMA_WRITE); while (fas_dma_reg_read(fas, &fas->f_dma->dma_csr) & DMA_REQPEND) ; fas_dma_reg_write(fas, &fas->f_dma->dma_csr, 0); fas_dma_reg_write(fas, &fas->f_dma->dma_csr, fas->f_dma_csr); fas_dma_reg_write(fas, &fas->f_dma->dma_addr, 0); } /* * update period, conf3, offset reg, if necessary */ #define FAS_SET_PERIOD_OFFSET_CONF3_REGS(fas, target) \ { \ uchar_t period, offset, conf3; \ period = fas->f_sync_period[target] & SYNC_PERIOD_MASK; \ offset = fas->f_offset[target]; \ conf3 = fas->f_fasconf3[target]; \ if ((period != fas->f_period_reg_last) || \ (offset != fas->f_offset_reg_last) || \ (conf3 != fas->f_fasconf3_reg_last)) { \ fas->f_period_reg_last = period; \ fas->f_offset_reg_last = offset; \ fas->f_fasconf3_reg_last = conf3; \ fas_reg_write(fas, &fasreg->fas_sync_period, period); \ fas_reg_write(fas, &fasreg->fas_sync_offset, offset); \ fas_reg_write(fas, &fasreg->fas_conf3, conf3); \ } \ } /* * fifo read/write routines * always read the fifo bytes before reading the interrupt register */ static void fas_read_fifo(struct fas *fas) { int stat = fas->f_stat; volatile struct fasreg *fasreg = fas->f_reg; int i; i = fas_reg_read(fas, &fasreg->fas_fifo_flag) & FIFO_CNT_MASK; EPRINTF2("fas_read_fifo: fifo cnt=%x, stat=%x\n", i, stat); ASSERT(i <= FIFOSIZE); fas->f_fifolen = 0; while (i-- > 0) { fas->f_fifo[fas->f_fifolen++] = fas_reg_read(fas, &fasreg->fas_fifo_data); fas->f_fifo[fas->f_fifolen++] = fas_reg_read(fas, &fasreg->fas_fifo_data); } if (fas->f_stat2 & FAS_STAT2_ISHUTTLE) { /* write pad byte */ fas_reg_write(fas, &fasreg->fas_fifo_data, 0); fas->f_fifo[fas->f_fifolen++] = fas_reg_read(fas, &fasreg->fas_fifo_data); /* flush pad byte */ fas_reg_cmd_write(fas, CMD_FLUSH); } EPRINTF2("fas_read_fifo: fifo len=%x, stat2=%x\n", fas->f_fifolen, stat); } /* fas_read_fifo */ static void fas_write_fifo(struct fas *fas, uchar_t *buf, int length, int pad) { int i; volatile struct fasreg *fasreg = fas->f_reg; EPRINTF1("writing fifo %x bytes\n", length); ASSERT(length <= 15); fas_reg_cmd_write(fas, CMD_FLUSH); for (i = 0; i < length; i++) { fas_reg_write(fas, &fasreg->fas_fifo_data, buf[i]); if (pad) { fas_reg_write(fas, &fasreg->fas_fifo_data, 0); } } } /* * Hardware and Software internal reset routines */ static int fas_init_chip(struct fas *fas, uchar_t initiator_id) { int i; uchar_t clock_conv; uchar_t initial_conf3; uint_t ticks; static char *prop_cfreq = "clock-frequency"; /* * Determine clock frequency of attached FAS chip. */ i = ddi_prop_get_int(DDI_DEV_T_ANY, fas->f_dev, DDI_PROP_DONTPASS, prop_cfreq, -1); clock_conv = (i + FIVE_MEG - 1) / FIVE_MEG; if (clock_conv != CLOCK_40MHZ) { fas_log(fas, CE_WARN, "Bad clock frequency"); return (-1); } fas->f_clock_conv = clock_conv; fas->f_clock_cycle = CLOCK_PERIOD(i); ticks = FAS_CLOCK_TICK(fas); fas->f_stval = FAS_CLOCK_TIMEOUT(ticks, fas_selection_timeout); DPRINTF5("%d mhz, clock_conv %d, clock_cycle %d, ticks %d, stval %d\n", i, fas->f_clock_conv, fas->f_clock_cycle, ticks, fas->f_stval); /* * set up conf registers */ fas->f_fasconf |= FAS_CONF_PAREN; fas->f_fasconf2 = (uchar_t)(FAS_CONF2_FENABLE | FAS_CONF2_XL32); if (initiator_id < NTARGETS) { initial_conf3 = FAS_CONF3_FASTCLK | FAS_CONF3_ODDBYTE_AUTO; } else { initial_conf3 = FAS_CONF3_FASTCLK | FAS_CONF3_ODDBYTE_AUTO | FAS_CONF3_IDBIT3; } for (i = 0; i < NTARGETS_WIDE; i++) { fas->f_fasconf3[i] = initial_conf3; } /* * Avoid resetting the scsi bus since this causes a few seconds * delay per fas in boot and also causes busy conditions in some * tape devices. */ fas_internal_reset(fas, FAS_RESET_SOFTC|FAS_RESET_FAS|FAS_RESET_DMA); /* * initialize period and offset for each target */ for (i = 0; i < NTARGETS_WIDE; i++) { if (fas->f_target_scsi_options[i] & SCSI_OPTIONS_SYNC) { fas->f_offset[i] = fas_default_offset | fas->f_req_ack_delay; } else { fas->f_offset[i] = 0; } if (fas->f_target_scsi_options[i] & SCSI_OPTIONS_FAST) { fas->f_neg_period[i] = (uchar_t)MIN_SYNC_PERIOD(fas); } else { fas->f_neg_period[i] = (uchar_t)CONVERT_PERIOD(DEFAULT_SYNC_PERIOD); } } return (0); } /* * reset bus, chip, dma, or soft state */ static void fas_internal_reset(struct fas *fas, int reset_action) { volatile struct fasreg *fasreg = fas->f_reg; volatile struct dma *dmar = fas->f_dma; if (reset_action & FAS_RESET_SCSIBUS) { fas_reg_cmd_write(fas, CMD_RESET_SCSI); fas_setup_reset_delay(fas); } FAS_FLUSH_DMA_HARD(fas); /* resets and reinits the dma */ /* * NOTE: if dma is aborted while active, indefinite hangs * may occur; it is preferable to stop the target first before * flushing the dma */ if (reset_action & FAS_RESET_DMA) { int burstsizes = fas->f_dma_attr->dma_attr_burstsizes; if (burstsizes & BURST64) { IPRINTF("64 byte burstsize\n"); fas->f_dma_csr |= DMA_BURST64; } else if (burstsizes & BURST32) { IPRINTF("32 byte burstsize\n"); fas->f_dma_csr |= DMA_BURST32; } else { IPRINTF("16 byte burstsize\n"); } if ((fas->f_hm_rev > 0x20) && (fas_enable_sbus64) && (ddi_dma_set_sbus64(fas->f_dmahandle, burstsizes) == DDI_SUCCESS)) { IPRINTF("enabled 64 bit sbus\n"); fas->f_dma_csr |= DMA_WIDE_EN; } } if (reset_action & FAS_RESET_FAS) { /* * 2 NOPs with DMA are required here * id_code is unreliable if we don't do this) */ uchar_t idcode, fcode; int dmarev; fas_reg_cmd_write(fas, CMD_RESET_FAS); fas_reg_cmd_write(fas, CMD_NOP | CMD_DMA); fas_reg_cmd_write(fas, CMD_NOP | CMD_DMA); /* * Re-load chip configurations * Only load registers which are not loaded in fas_startcmd() */ fas_reg_write(fas, &fasreg->fas_clock_conv, (fas->f_clock_conv & CLOCK_MASK)); fas_reg_write(fas, &fasreg->fas_timeout, fas->f_stval); /* * enable default configurations */ fas->f_idcode = idcode = fas_reg_read(fas, &fasreg->fas_id_code); fcode = (uchar_t)(idcode & FAS_FCODE_MASK) >> (uchar_t)3; fas->f_type = FAS366; IPRINTF2("Family code %d, revision %d\n", fcode, (idcode & FAS_REV_MASK)); dmarev = fas_dma_reg_read(fas, &dmar->dma_csr); dmarev = (dmarev >> 11) & 0xf; IPRINTF1("DMA channel revision %d\n", dmarev); fas_reg_write(fas, &fasreg->fas_conf, fas->f_fasconf); fas_reg_write(fas, &fasreg->fas_conf2, fas->f_fasconf2); fas->f_req_ack_delay = DEFAULT_REQ_ACK_DELAY; /* * Just in case... clear interrupt */ (void) fas_reg_read(fas, &fasreg->fas_intr); } if (reset_action & FAS_RESET_SOFTC) { fas->f_wdtr_sent = fas->f_sdtr_sent = 0; fas->f_wide_known = fas->f_sync_known = 0; fas->f_wide_enabled = fas->f_sync_enabled = 0; fas->f_omsglen = 0; fas->f_cur_msgout[0] = fas->f_last_msgout = fas->f_last_msgin = INVALID_MSG; fas->f_abort_msg_sent = fas->f_reset_msg_sent = 0; fas->f_next_slot = 0; fas->f_current_sp = NULL; fas->f_fifolen = 0; fas->f_fasconf3_reg_last = fas->f_offset_reg_last = fas->f_period_reg_last = 0xff; New_state(fas, STATE_FREE); } } #ifdef FASDEBUG /* * check if ncmds still reflects the truth * count all cmds for this driver instance and compare with ncmds */ static void fas_check_ncmds(struct fas *fas) { int slot = 0; ushort_t tag, t; int n, total = 0; do { if (fas->f_active[slot]) { struct fas_cmd *sp = fas->f_readyf[slot]; t = fas->f_active[slot]->f_n_slots; while (sp != 0) { sp = sp->cmd_forw; total++; } for (n = tag = 0; tag < t; tag++) { if (fas->f_active[slot]->f_slot[tag] != 0) { n++; total++; } } ASSERT(n == fas->f_tcmds[slot]); } slot = NEXTSLOT(slot, fas->f_dslot); } while (slot != 0); if (total != fas->f_ncmds) { IPRINTF2("fas_check_ncmds: total=%x, ncmds=%x\n", total, fas->f_ncmds); } ASSERT(fas->f_ncmds >= fas->f_ndisc); } #else #define fas_check_ncmds(fas) #endif /* * SCSA Interface functions * * Visible to the external world via the transport structure. * * fas_scsi_abort: abort a current cmd or all cmds for a target */ /*ARGSUSED*/ static int fas_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt) { struct fas *fas = ADDR2FAS(ap); int rval; IPRINTF2("fas_scsi_abort: target %d.%d\n", ap->a_target, ap->a_lun); mutex_enter(FAS_MUTEX(fas)); rval = fas_do_scsi_abort(ap, pkt); fas_check_waitQ_and_mutex_exit(fas); return (rval); } /* * reset handling: reset bus or target */ /*ARGSUSED*/ static int fas_scsi_reset(struct scsi_address *ap, int level) { struct fas *fas = ADDR2FAS(ap); int rval; IPRINTF3("fas_scsi_reset: target %d.%d, level %d\n", ap->a_target, ap->a_lun, level); mutex_enter(FAS_MUTEX(fas)); rval = fas_do_scsi_reset(ap, level); fas_check_waitQ_and_mutex_exit(fas); return (rval); } /* * entry point for reset notification setup, to register or to cancel. */ static int fas_scsi_reset_notify(struct scsi_address *ap, int flag, void (*callback)(caddr_t), caddr_t arg) { struct fas *fas = ADDR2FAS(ap); return (scsi_hba_reset_notify_setup(ap, flag, callback, arg, &fas->f_mutex, &fas->f_reset_notify_listf)); } /* * capability interface */ /*ARGSUSED*/ static int fas_scsi_getcap(struct scsi_address *ap, char *cap, int whom) { struct fas *fas = ADDR2FAS(ap); DPRINTF3("fas_scsi_getcap: tgt=%x, cap=%s, whom=%x\n", ap->a_target, cap, whom); return (fas_commoncap(ap, cap, 0, whom, 0)); } /*ARGSUSED*/ static int fas_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct fas *fas = ADDR2FAS(ap); IPRINTF4("fas_scsi_setcap: tgt=%x, cap=%s, value=%x, whom=%x\n", ap->a_target, cap, value, whom); return (fas_commoncap(ap, cap, value, whom, 1)); } /* * pkt and dma allocation and deallocation */ /*ARGSUSED*/ static void fas_scsi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct fas_cmd *cmd = PKT2CMD(pkt); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_DMAFREE_START, "fas_scsi_dmafree_start"); if (cmd->cmd_flags & CFLAG_DMAVALID) { /* * Free the mapping. */ (void) ddi_dma_unbind_handle(cmd->cmd_dmahandle); cmd->cmd_flags ^= CFLAG_DMAVALID; } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_DMAFREE_END, "fas_scsi_dmafree_end"); } /*ARGSUSED*/ static void fas_scsi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct fas_cmd *sp = PKT2CMD(pkt); if (sp->cmd_flags & CFLAG_DMAVALID) { if (ddi_dma_sync(sp->cmd_dmahandle, 0, 0, (sp->cmd_flags & CFLAG_DMASEND) ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU) != DDI_SUCCESS) { fas_log(ADDR2FAS(ap), CE_WARN, "sync of pkt (%p) failed", (void *)pkt); } } } /* * initialize pkt and allocate DVMA resources */ static struct scsi_pkt * fas_scsi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(), caddr_t arg) { int kf; int failure = 1; struct fas_cmd *cmd; struct fas *fas = ADDR2FAS(ap); struct fas_cmd *new_cmd; int rval; /* #define FAS_TEST_EXTRN_ALLOC */ #ifdef FAS_TEST_EXTRN_ALLOC cmdlen *= 4; statuslen *= 4; tgtlen *= 4; #endif /* * if no pkt was passed then allocate a pkt first */ if (pkt == NULL) { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_PKTALLOC_START, "fas_scsi_impl_pktalloc_start"); kf = (callback == SLEEP_FUNC)? KM_SLEEP: KM_NOSLEEP; /* * only one size of pkt (with arq). */ cmd = kmem_cache_alloc(fas->f_kmem_cache, kf); if (cmd) { ddi_dma_handle_t save_dma_handle; save_dma_handle = cmd->cmd_dmahandle; bzero(cmd, EXTCMD_SIZE); cmd->cmd_dmahandle = save_dma_handle; pkt = (struct scsi_pkt *)((uchar_t *)cmd + sizeof (struct fas_cmd)); cmd->cmd_pkt = pkt; pkt->pkt_ha_private = (opaque_t)cmd; pkt->pkt_scbp = (opaque_t)&cmd->cmd_scb; pkt->pkt_cdbp = (opaque_t)&cmd->cmd_cdb; pkt->pkt_address = *ap; pkt->pkt_cdbp = (opaque_t)&cmd->cmd_cdb; pkt->pkt_private = cmd->cmd_pkt_private; cmd->cmd_cdblen = cmdlen; cmd->cmd_scblen = statuslen; cmd->cmd_privlen = tgtlen; cmd->cmd_slot = (Tgt(cmd) * NLUNS_PER_TARGET) | Lun(cmd); failure = 0; } if (failure || (cmdlen > sizeof (cmd->cmd_cdb)) || (tgtlen > PKT_PRIV_LEN) || (statuslen > EXTCMDS_STATUS_SIZE)) { if (failure == 0) { /* * if extern alloc fails, all will be * deallocated, including cmd */ failure = fas_pkt_alloc_extern(fas, cmd, cmdlen, tgtlen, statuslen, kf); } if (failure) { /* * nothing to deallocate so just return */ TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_PKTALLOC_END, "fas_scsi_impl_pktalloc_end"); return (NULL); } } new_cmd = cmd; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_PKTALLOC_END, "fas_scsi_impl_pktalloc_end"); } else { cmd = PKT2CMD(pkt); new_cmd = NULL; } /* * Second step of fas_scsi_init_pkt: * bind the buf to the handle */ if (bp && bp->b_bcount != 0 && (cmd->cmd_flags & CFLAG_DMAVALID) == 0) { int cmd_flags, dma_flags; uint_t dmacookie_count; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_DMAGET_START, "fas_scsi_impl_dmaget_start"); cmd_flags = cmd->cmd_flags; if (bp->b_flags & B_READ) { cmd_flags &= ~CFLAG_DMASEND; dma_flags = DDI_DMA_READ | DDI_DMA_PARTIAL; } else { cmd_flags |= CFLAG_DMASEND; dma_flags = DDI_DMA_WRITE | DDI_DMA_PARTIAL; } if (flags & PKT_CONSISTENT) { cmd_flags |= CFLAG_CMDIOPB; dma_flags |= DDI_DMA_CONSISTENT; } /* * bind the handle to the buf */ ASSERT(cmd->cmd_dmahandle != NULL); rval = ddi_dma_buf_bind_handle(cmd->cmd_dmahandle, bp, dma_flags, callback, arg, &cmd->cmd_dmacookie, &dmacookie_count); if (rval && rval != DDI_DMA_PARTIAL_MAP) { switch (rval) { case DDI_DMA_NORESOURCES: bioerror(bp, 0); break; case DDI_DMA_BADATTR: case DDI_DMA_NOMAPPING: bioerror(bp, EFAULT); break; case DDI_DMA_TOOBIG: default: bioerror(bp, EINVAL); break; } cmd->cmd_flags = cmd_flags & ~CFLAG_DMAVALID; if (new_cmd) { fas_scsi_destroy_pkt(ap, pkt); } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_DMAGET_END, "fas_scsi_impl_dmaget_end"); return ((struct scsi_pkt *)NULL); } ASSERT(dmacookie_count == 1); cmd->cmd_dmacount = bp->b_bcount; cmd->cmd_flags = cmd_flags | CFLAG_DMAVALID; ASSERT(cmd->cmd_dmahandle != NULL); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_DMAGET_END, "fas_scsi_impl_dmaget_end"); } return (pkt); } /* * unbind dma resources and deallocate the pkt */ static void fas_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct fas_cmd *sp = PKT2CMD(pkt); struct fas *fas = ADDR2FAS(ap); /* * fas_scsi_impl_dmafree inline to speed things up */ TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_DMAFREE_START, "fas_scsi_impl_dmafree_start"); if (sp->cmd_flags & CFLAG_DMAVALID) { /* * Free the mapping. */ (void) ddi_dma_unbind_handle(sp->cmd_dmahandle); sp->cmd_flags ^= CFLAG_DMAVALID; } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_DMAFREE_END, "fas_scsi_impl_dmafree_end"); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_PKTFREE_START, "fas_scsi_impl_pktfree_start"); if ((sp->cmd_flags & (CFLAG_FREE | CFLAG_CDBEXTERN | CFLAG_PRIVEXTERN | CFLAG_SCBEXTERN)) == 0) { sp->cmd_flags = CFLAG_FREE; kmem_cache_free(fas->f_kmem_cache, (void *)sp); } else { fas_pkt_destroy_extern(fas, sp); } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_SCSI_IMPL_PKTFREE_END, "fas_scsi_impl_pktfree_end"); } /* * allocate and deallocate external pkt space (ie. not part of fas_cmd) for * non-standard length cdb, pkt_private, status areas * if allocation fails, then deallocate all external space and the pkt */ /* ARGSUSED */ static int fas_pkt_alloc_extern(struct fas *fas, struct fas_cmd *sp, int cmdlen, int tgtlen, int statuslen, int kf) { caddr_t cdbp, scbp, tgt; int failure = 0; tgt = cdbp = scbp = NULL; if (cmdlen > sizeof (sp->cmd_cdb)) { if ((cdbp = kmem_zalloc((size_t)cmdlen, kf)) == NULL) { failure++; } else { sp->cmd_pkt->pkt_cdbp = (opaque_t)cdbp; sp->cmd_flags |= CFLAG_CDBEXTERN; } } if (tgtlen > PKT_PRIV_LEN) { if ((tgt = kmem_zalloc(tgtlen, kf)) == NULL) { failure++; } else { sp->cmd_flags |= CFLAG_PRIVEXTERN; sp->cmd_pkt->pkt_private = tgt; } } if (statuslen > EXTCMDS_STATUS_SIZE) { if ((scbp = kmem_zalloc((size_t)statuslen, kf)) == NULL) { failure++; } else { sp->cmd_flags |= CFLAG_SCBEXTERN; sp->cmd_pkt->pkt_scbp = (opaque_t)scbp; } } if (failure) { fas_pkt_destroy_extern(fas, sp); } return (failure); } /* * deallocate external pkt space and deallocate the pkt */ static void fas_pkt_destroy_extern(struct fas *fas, struct fas_cmd *sp) { if (sp->cmd_flags & CFLAG_FREE) { panic("fas_pkt_destroy_extern: freeing free packet"); _NOTE(NOT_REACHED) /* NOTREACHED */ } if (sp->cmd_flags & CFLAG_CDBEXTERN) { kmem_free((caddr_t)sp->cmd_pkt->pkt_cdbp, (size_t)sp->cmd_cdblen); } if (sp->cmd_flags & CFLAG_SCBEXTERN) { kmem_free((caddr_t)sp->cmd_pkt->pkt_scbp, (size_t)sp->cmd_scblen); } if (sp->cmd_flags & CFLAG_PRIVEXTERN) { kmem_free((caddr_t)sp->cmd_pkt->pkt_private, (size_t)sp->cmd_privlen); } sp->cmd_flags = CFLAG_FREE; kmem_cache_free(fas->f_kmem_cache, (void *)sp); } /* * kmem cache constructor and destructor: * When constructing, we bzero the cmd and allocate the dma handle * When destructing, just free the dma handle */ static int fas_kmem_cache_constructor(void *buf, void *cdrarg, int kmflags) { struct fas_cmd *cmd = buf; struct fas *fas = cdrarg; int (*callback)(caddr_t) = (kmflags == KM_SLEEP) ? DDI_DMA_SLEEP: DDI_DMA_DONTWAIT; bzero(buf, EXTCMD_SIZE); /* * allocate a dma handle */ if ((ddi_dma_alloc_handle(fas->f_dev, fas->f_dma_attr, callback, NULL, &cmd->cmd_dmahandle)) != DDI_SUCCESS) { return (-1); } return (0); } /*ARGSUSED*/ static void fas_kmem_cache_destructor(void *buf, void *cdrarg) { struct fas_cmd *cmd = buf; if (cmd->cmd_dmahandle) { ddi_dma_free_handle(&cmd->cmd_dmahandle); } } /* * fas_scsi_start - Accept commands for transport */ static int fas_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct fas_cmd *sp = PKT2CMD(pkt); struct fas *fas = ADDR2FAS(ap); int rval; int intr = 0; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_START_START, "fas_scsi_start_start"); #ifdef FAS_TEST if (fas_transport_busy > 0) { fas_transport_busy--; return (TRAN_BUSY); } if ((fas_transport_busy_rqs > 0) && (*(sp->cmd_pkt->pkt_cdbp) == SCMD_REQUEST_SENSE)) { fas_transport_busy_rqs--; return (TRAN_BUSY); } if (fas_transport_reject > 0) { fas_transport_reject--; return (TRAN_BADPKT); } #endif /* * prepare packet before taking the mutex */ rval = fas_prepare_pkt(fas, sp); if (rval != TRAN_ACCEPT) { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_START_PREPARE_PKT_END, "fas_scsi_start_end (prepare_pkt)"); return (rval); } /* * fas mutex can be held for a long time; therefore, if the mutex is * held, we queue the packet in a waitQ; we now should check * the waitQ on every mutex_exit(FAS_MUTEX(fas)) but we really only * need to do this when the bus is free * don't put NOINTR cmds including proxy cmds in waitQ! These * cmds are handled by fas_runpoll() * if the waitQ is non-empty, queue the pkt anyway to preserve * order * the goal is to queue in waitQ as much as possible so at * interrupt time, we can move the packets to readyQ or start * a packet immediately. It helps to do this at interrupt * time because we can then field more interrupts */ if ((sp->cmd_pkt_flags & FLAG_NOINTR) == 0) { /* * if the bus is not free, we will get an interrupt shortly * so we don't want to take the fas mutex but queue up * the packet in the waitQ * also, if the waitQ is non-empty or there is an interrupt * pending then queue up the packet in the waitQ and let the * interrupt handler empty the waitQ */ mutex_enter(&fas->f_waitQ_mutex); if ((fas->f_state != STATE_FREE) || fas->f_waitf || (intr = INTPENDING(fas))) { goto queue_in_waitQ; } /* * we didn't queue up in the waitQ, so now try to accept * the packet. if we fail to get the fas mutex, go back to * the waitQ again * do not release the waitQ mutex yet because that * leaves a window where the interrupt handler has * emptied the waitQ but not released the fas mutex yet * * the interrupt handler gets the locks in opposite order * but because we do a tryenter, there is no deadlock * * if another thread has the fas mutex then either this * thread or the other may find the bus free and * empty the waitQ */ if (mutex_tryenter(FAS_MUTEX(fas))) { mutex_exit(&fas->f_waitQ_mutex); rval = fas_accept_pkt(fas, sp, TRAN_BUSY_OK); } else { /* * we didn't get the fas mutex so * the packet has to go in the waitQ now */ goto queue_in_waitQ; } } else { /* * for polled cmds, we have to take the mutex and * start the packet using fas_runpoll() */ mutex_enter(FAS_MUTEX(fas)); rval = fas_accept_pkt(fas, sp, TRAN_BUSY_OK); } /* * if the bus is free then empty waitQ and release the mutex * (this should be unlikely that the bus is still free after * accepting the packet. it may be the relatively unusual case * that we are throttling) */ if (fas->f_state == STATE_FREE) { FAS_CHECK_WAITQ_AND_FAS_MUTEX_EXIT(fas); } else { mutex_exit(FAS_MUTEX(fas)); } done: TRACE_1(TR_FAC_SCSI_FAS, TR_FAS_START_END, "fas_scsi_start_end: fas 0x%p", fas); return (rval); queue_in_waitQ: if (fas->f_waitf == NULL) { fas->f_waitb = fas->f_waitf = sp; sp->cmd_forw = NULL; } else { struct fas_cmd *dp = fas->f_waitb; dp->cmd_forw = fas->f_waitb = sp; sp->cmd_forw = NULL; } /* * check again the fas mutex * if there was an interrupt then the interrupt * handler will eventually empty the waitQ */ if ((intr == 0) && (fas->f_state == STATE_FREE) && mutex_tryenter(FAS_MUTEX(fas))) { /* * double check if the bus is still free * (this actually reduced mutex contention a bit) */ if (fas->f_state == STATE_FREE) { fas_empty_waitQ(fas); } mutex_exit(FAS_MUTEX(fas)); } mutex_exit(&fas->f_waitQ_mutex); TRACE_1(TR_FAC_SCSI_FAS, TR_FAS_START_END, "fas_scsi_start_end: fas 0x%p", fas); return (rval); } /* * prepare the pkt: * the pkt may have been resubmitted or just reused so * initialize some fields, reset the dma window, and do some checks */ static int fas_prepare_pkt(struct fas *fas, struct fas_cmd *sp) { struct scsi_pkt *pkt = CMD2PKT(sp); /* * Reinitialize some fields that need it; the packet may * have been resubmitted */ pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state = 0; pkt->pkt_statistics = 0; pkt->pkt_resid = 0; sp->cmd_age = 0; sp->cmd_pkt_flags = pkt->pkt_flags; /* * Copy the cdb pointer to the pkt wrapper area as we * might modify this pointer. Zero status byte */ sp->cmd_cdbp = pkt->pkt_cdbp; *(pkt->pkt_scbp) = 0; if (sp->cmd_flags & CFLAG_DMAVALID) { pkt->pkt_resid = sp->cmd_dmacount; /* * if the pkt was resubmitted then the * windows may be at the wrong number */ if (sp->cmd_cur_win) { sp->cmd_cur_win = 0; if (fas_set_new_window(fas, sp)) { IPRINTF("cannot reset window\n"); return (TRAN_BADPKT); } } sp->cmd_saved_cur_addr = sp->cmd_cur_addr = sp->cmd_dmacookie.dmac_address; /* * the common case is just one window, we worry * about multiple windows when we run out of the * current window */ sp->cmd_nwin = sp->cmd_saved_win = 0; sp->cmd_data_count = sp->cmd_saved_data_count = 0; /* * consistent packets need to be sync'ed first * (only for data going out) */ if ((sp->cmd_flags & (CFLAG_CMDIOPB | CFLAG_DMASEND)) == (CFLAG_CMDIOPB | CFLAG_DMASEND)) { (void) ddi_dma_sync(sp->cmd_dmahandle, 0, (uint_t)0, DDI_DMA_SYNC_FORDEV); } } sp->cmd_actual_cdblen = sp->cmd_cdblen; #ifdef FAS_TEST #ifndef __lock_lint if (fas_test_untagged > 0) { if (TAGGED(Tgt(sp))) { int slot = sp->cmd_slot; sp->cmd_pkt_flags &= ~FLAG_TAGMASK; sp->cmd_pkt_flags &= ~FLAG_NODISCON; sp->cmd_pkt_flags |= 0x80000000; fas_log(fas, CE_NOTE, "starting untagged cmd, target=%d," " tcmds=%d, sp=0x%p, throttle=%d\n", Tgt(sp), fas->f_tcmds[slot], (void *)sp, fas->f_throttle[slot]); fas_test_untagged = -10; } } #endif #endif #ifdef FASDEBUG if (NOTAG(Tgt(sp)) && (pkt->pkt_flags & FLAG_TAGMASK)) { IPRINTF2("tagged packet for non-tagged target %d.%d\n", Tgt(sp), Lun(sp)); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_PREPARE_PKT_TRAN_BADPKT_END, "fas_prepare_pkt_end (tran_badpkt)"); return (TRAN_BADPKT); } /* * the SCSA spec states that it is an error to have no * completion function when FLAG_NOINTR is not set */ if ((pkt->pkt_comp == NULL) && ((pkt->pkt_flags & FLAG_NOINTR) == 0)) { IPRINTF("intr packet with pkt_comp == 0\n"); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_PREPARE_PKT_TRAN_BADPKT_END, "fas_prepare_pkt_end (tran_badpkt)"); return (TRAN_BADPKT); } #endif /* FASDEBUG */ if ((fas->f_target_scsi_options[Tgt(sp)] & SCSI_OPTIONS_DR) == 0) { /* * no need to reset tag bits since tag queueing will * not be enabled if disconnects are disabled */ sp->cmd_pkt_flags |= FLAG_NODISCON; } sp->cmd_flags = (sp->cmd_flags & ~CFLAG_TRANFLAG) | CFLAG_PREPARED | CFLAG_IN_TRANSPORT; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_PREPARE_PKT_TRAN_ACCEPT_END, "fas_prepare_pkt_end (tran_accept)"); return (TRAN_ACCEPT); } /* * emptying the waitQ just before releasing FAS_MUTEX is a bit * tricky; if we release the waitQ mutex and then the FAS_MUTEX, * another thread could queue a cmd in the waitQ, just before * the FAS_MUTEX is released. This cmd is then stuck in the waitQ unless * another cmd comes in or fas_intr() or fas_watch() checks the waitQ. * Therefore, by releasing the FAS_MUTEX before releasing the waitQ mutex, * we prevent fas_scsi_start() filling the waitQ * * By setting NO_TRAN_BUSY, we force fas_accept_pkt() to queue up * the waitQ pkts in the readyQ. * If a QFull condition occurs, the target driver may set its throttle * too high because of the requests queued up in the readyQ but this * is not a big problem. The throttle should be periodically reset anyway. */ static void fas_empty_waitQ(struct fas *fas) { struct fas_cmd *sp; int rval; struct fas_cmd *waitf, *waitb; ASSERT(mutex_owned(&fas->f_waitQ_mutex)); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_EMPTY_WAITQ_START, "fas_empty_waitQ_start"); while (fas->f_waitf) { /* copy waitQ, zero the waitQ and release the mutex */ waitf = fas->f_waitf; waitb = fas->f_waitb; fas->f_waitf = fas->f_waitb = NULL; mutex_exit(&fas->f_waitQ_mutex); do { sp = waitf; waitf = sp->cmd_forw; if (waitb == sp) { waitb = NULL; } rval = fas_accept_pkt(fas, sp, NO_TRAN_BUSY); /* * If the packet was rejected for other reasons then * complete it here */ if (rval != TRAN_ACCEPT) { ASSERT(rval != TRAN_BUSY); fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); if (sp->cmd_pkt->pkt_comp) { sp->cmd_flags |= CFLAG_FINISHED; fas_call_pkt_comp(fas, sp); } } if (INTPENDING(fas)) { /* * stop processing the waitQ and put back * the remaining packets on the waitQ */ mutex_enter(&fas->f_waitQ_mutex); if (waitf) { ASSERT(waitb != NULL); waitb->cmd_forw = fas->f_waitf; fas->f_waitf = waitf; if (fas->f_waitb == NULL) { fas->f_waitb = waitb; } } return; } } while (waitf); mutex_enter(&fas->f_waitQ_mutex); } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_EMPTY_WAITQ_END, "fas_empty_waitQ_end"); } static void fas_move_waitQ_to_readyQ(struct fas *fas) { /* * this may actually start cmds but it is most likely * that if waitQ is not empty that the bus is not free */ ASSERT(mutex_owned(FAS_MUTEX(fas))); mutex_enter(&fas->f_waitQ_mutex); fas_empty_waitQ(fas); mutex_exit(&fas->f_waitQ_mutex); } /* * function wrapper for two frequently used macros. for the non-critical * path we use the function */ static void fas_check_waitQ_and_mutex_exit(struct fas *fas) { _NOTE(LOCK_RELEASED_AS_SIDE_EFFECT(fas->f_mutex)) FAS_CHECK_WAITQ_AND_FAS_MUTEX_EXIT(fas); FAS_EMPTY_CALLBACKQ(fas); } /* * fas_accept_pkt(): * the flag argument is to force fas_accept_pkt to accept the pkt; * the caller cannot take the pkt back and it has to be queued up in * the readyQ */ static int fas_accept_pkt(struct fas *fas, struct fas_cmd *sp, int flag) { short slot = sp->cmd_slot; int rval = TRAN_ACCEPT; TRACE_0(TR_FAC_SCSI_FAS, TR__FAS_START_START, "fas_accept_pkt_start"); ASSERT(mutex_owned(FAS_MUTEX(fas))); ASSERT(fas->f_ncmds >= 0 && fas->f_ndisc >= 0); ASSERT(fas->f_ncmds >= fas->f_ndisc); ASSERT(fas->f_tcmds[slot] >= 0); /* * prepare packet for transport if this hasn't been done yet and * do some checks */ if ((sp->cmd_flags & CFLAG_PREPARED) == 0) { rval = fas_prepare_pkt(fas, sp); if (rval != TRAN_ACCEPT) { IPRINTF1("prepare pkt failed, slot=%x\n", slot); sp->cmd_flags &= ~CFLAG_TRANFLAG; goto done; } } if (Lun(sp)) { EPRINTF("fas_accept_pkt: switching target and lun slot scan\n"); fas->f_dslot = 1; if ((fas->f_active[slot] == NULL) || ((fas->f_active[slot]->f_n_slots != NTAGS) && TAGGED(Tgt(sp)))) { (void) fas_alloc_active_slots(fas, slot, KM_NOSLEEP); } if ((fas->f_active[slot] == NULL) || (NOTAG(Tgt(sp)) && (sp->cmd_pkt_flags & FLAG_TAGMASK))) { IPRINTF("fatal error on non-zero lun pkt\n"); return (TRAN_FATAL_ERROR); } } /* * we accepted the command; increment the count * (we may still reject later if TRAN_BUSY_OK) */ fas_check_ncmds(fas); fas->f_ncmds++; /* * if it is a nointr packet, start it now * (NO_INTR pkts are not queued in the waitQ) */ if (sp->cmd_pkt_flags & FLAG_NOINTR) { EPRINTF("starting a nointr cmd\n"); fas_runpoll(fas, slot, sp); sp->cmd_flags &= ~CFLAG_TRANFLAG; goto done; } /* * reset the throttle if we were draining */ if ((fas->f_tcmds[slot] == 0) && (fas->f_throttle[slot] == DRAIN_THROTTLE)) { DPRINTF("reset throttle\n"); ASSERT(fas->f_reset_delay[Tgt(sp)] == 0); fas_full_throttle(fas, slot); } /* * accept the command: * If no readyQ and no bus free, and throttle is OK, * run cmd immediately. */ #ifdef FASDEBUG fas->f_total_cmds++; #endif if ((fas->f_readyf[slot] == NULL) && (fas->f_state == STATE_FREE) && (fas->f_throttle[slot] > fas->f_tcmds[slot])) { ASSERT(fas->f_current_sp == 0); (void) fas_startcmd(fas, sp); goto exit; } else { /* * If FLAG_HEAD is set, run cmd if target and bus are * available. if first cmd in ready Q is request sense * then insert after this command, there shouldn't be more * than one request sense. */ if (sp->cmd_pkt_flags & FLAG_HEAD) { struct fas_cmd *ssp = fas->f_readyf[slot]; EPRINTF("que head\n"); if (ssp && *(ssp->cmd_pkt->pkt_cdbp) != SCMD_REQUEST_SENSE) { fas_head_of_readyQ(fas, sp); } else if (ssp) { struct fas_cmd *dp = ssp->cmd_forw; ssp->cmd_forw = sp; sp->cmd_forw = dp; if (fas->f_readyb[slot] == ssp) { fas->f_readyb[slot] = sp; } } else { fas->f_readyf[slot] = fas->f_readyb[slot] = sp; sp->cmd_forw = NULL; } /* * for tagged targets, check for qfull condition and * return TRAN_BUSY (if permitted), if throttle has been * exceeded */ } else if (TAGGED(Tgt(sp)) && (fas->f_tcmds[slot] >= fas->f_throttle[slot]) && (fas->f_throttle[slot] > HOLD_THROTTLE) && (flag == TRAN_BUSY_OK)) { IPRINTF2( "transport busy, slot=%x, ncmds=%x\n", slot, fas->f_ncmds); rval = TRAN_BUSY; fas->f_ncmds--; sp->cmd_flags &= ~(CFLAG_PREPARED | CFLAG_IN_TRANSPORT); goto done; /* * append to readyQ or start a new readyQ */ } else if (fas->f_readyf[slot]) { struct fas_cmd *dp = fas->f_readyb[slot]; ASSERT(dp != 0); fas->f_readyb[slot] = sp; sp->cmd_forw = NULL; dp->cmd_forw = sp; } else { fas->f_readyf[slot] = fas->f_readyb[slot] = sp; sp->cmd_forw = NULL; } } done: /* * just in case that the bus is free and we haven't * been able to restart for some reason */ if (fas->f_state == STATE_FREE) { (void) fas_istart(fas); } exit: fas_check_ncmds(fas); ASSERT(mutex_owned(FAS_MUTEX(fas))); TRACE_0(TR_FAC_SCSI_FAS, TR__FAS_START_END, "fas_accept_pkt_end"); return (rval); } /* * allocate a tag byte and check for tag aging */ static char fas_tag_lookup[] = {0, MSG_HEAD_QTAG, MSG_ORDERED_QTAG, 0, MSG_SIMPLE_QTAG}; static int fas_alloc_tag(struct fas *fas, struct fas_cmd *sp) { struct f_slots *tag_slots; int tag; short slot = sp->cmd_slot; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_ALLOC_TAG_START, "fas_alloc_tag_start"); ASSERT(mutex_owned(FAS_MUTEX(fas))); tag_slots = fas->f_active[slot]; ASSERT(tag_slots->f_n_slots == NTAGS); alloc_tag: tag = (fas->f_active[slot]->f_tags)++; if (fas->f_active[slot]->f_tags >= NTAGS) { /* * we reserve tag 0 for non-tagged cmds */ fas->f_active[slot]->f_tags = 1; } EPRINTF1("tagged cmd, tag = %d\n", tag); /* Validate tag, should never fail. */ if (tag_slots->f_slot[tag] == 0) { /* * Store assigned tag and tag queue type. * Note, in case of multiple choice, default to simple queue. */ ASSERT(tag < NTAGS); sp->cmd_tag[1] = (uchar_t)tag; sp->cmd_tag[0] = fas_tag_lookup[((sp->cmd_pkt_flags & FLAG_TAGMASK) >> 12)]; EPRINTF1("tag= %d\n", tag); tag_slots->f_slot[tag] = sp; (fas->f_tcmds[slot])++; ASSERT(mutex_owned(FAS_MUTEX(fas))); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_ALLOC_TAG_END, "fas_alloc_tag_end"); return (0); } else { int age, i; /* * Check tag age. If timeouts enabled and * tag age greater than 1, print warning msg. * If timeouts enabled and tag age greater than * age limit, begin draining tag que to check for * lost tag cmd. */ age = tag_slots->f_slot[tag]->cmd_age++; if (age >= fas->f_scsi_tag_age_limit && tag_slots->f_slot[tag]->cmd_pkt->pkt_time) { IPRINTF2("tag %d in use, age= %d\n", tag, age); DPRINTF("draining tag queue\n"); if (fas->f_reset_delay[Tgt(sp)] == 0) { fas->f_throttle[slot] = DRAIN_THROTTLE; } } /* If tag in use, scan until a free one is found. */ for (i = 1; i < NTAGS; i++) { tag = fas->f_active[slot]->f_tags; if (!tag_slots->f_slot[tag]) { EPRINTF1("found free tag %d\n", tag); break; } if (++(fas->f_active[slot]->f_tags) >= NTAGS) { /* * we reserve tag 0 for non-tagged cmds */ fas->f_active[slot]->f_tags = 1; } EPRINTF1("found in use tag %d\n", tag); } /* * If no free tags, we're in serious trouble. * the target driver submitted more than 255 * requests */ if (tag_slots->f_slot[tag]) { IPRINTF1("slot %x: All tags in use!!!\n", slot); goto fail; } goto alloc_tag; } fail: fas_head_of_readyQ(fas, sp); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_ALLOC_TAG_END, "fas_alloc_tag_end"); return (-1); } /* * Internal Search Routine. * * Search for a command to start. */ static int fas_istart(struct fas *fas) { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_ISTART_START, "fas_istart_start"); EPRINTF("fas_istart:\n"); if (fas->f_state == STATE_FREE && fas->f_ncmds > fas->f_ndisc) { (void) fas_ustart(fas); } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_ISTART_END, "fas_istart_end"); return (ACTION_RETURN); } static int fas_ustart(struct fas *fas) { struct fas_cmd *sp; short slot = fas->f_next_slot; short start_slot = slot; short dslot = fas->f_dslot; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_USTART_START, "fas_ustart_start"); EPRINTF1("fas_ustart: start_slot=%x\n", fas->f_next_slot); ASSERT(fas->f_current_sp == NULL); ASSERT(dslot != 0); if (dslot == NLUNS_PER_TARGET) { ASSERT((slot % NLUNS_PER_TARGET) == 0); } /* * if readyQ not empty and we are not draining, then we * can start another cmd */ do { /* * If all cmds drained from tag Q, back to full throttle and * start queueing up new cmds again. */ if (fas->f_throttle[slot] == DRAIN_THROTTLE && fas->f_tcmds[slot] == 0) { fas_full_throttle(fas, slot); } if (fas->f_readyf[slot] && (fas->f_throttle[slot] > fas->f_tcmds[slot])) { sp = fas->f_readyf[slot]; fas->f_readyf[slot] = sp->cmd_forw; if (sp->cmd_forw == NULL) { fas->f_readyb[slot] = NULL; } fas->f_next_slot = NEXTSLOT(slot, dslot); ASSERT((sp->cmd_pkt_flags & FLAG_NOINTR) == 0); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_USTART_END, "fas_ustart_end"); return (fas_startcmd(fas, sp)); } else { slot = NEXTSLOT(slot, dslot); } } while (slot != start_slot); EPRINTF("fas_ustart: no cmds to start\n"); fas->f_next_slot = NEXTSLOT(slot, dslot); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_USTART_NOT_FOUND_END, "fas_ustart_end (not_found)"); return (FALSE); } /* * Start a command off */ static int fas_startcmd(struct fas *fas, struct fas_cmd *sp) { volatile struct fasreg *fasreg = fas->f_reg; ushort_t nstate; uchar_t cmd, target, lun; ushort_t tshift; volatile uchar_t *tp = fas->f_cmdarea; struct scsi_pkt *pkt = CMD2PKT(sp); int slot = sp->cmd_slot; struct f_slots *slots = fas->f_active[slot]; int i, cdb_len; #define LOAD_CMDP *(tp++) TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_STARTCMD_START, "fas_startcmd_start"); EPRINTF2("fas_startcmd: sp=0x%p flags=%x\n", (void *)sp, sp->cmd_pkt_flags); ASSERT((sp->cmd_flags & CFLAG_FREE) == 0); ASSERT((sp->cmd_flags & CFLAG_COMPLETED) == 0); ASSERT(fas->f_current_sp == NULL && fas->f_state == STATE_FREE); if ((sp->cmd_pkt_flags & FLAG_NOINTR) == 0) { ASSERT(fas->f_throttle[slot] > 0); ASSERT(fas->f_reset_delay[Tgt(sp)] == 0); } target = Tgt(sp); lun = Lun(sp); /* * if a non-tagged cmd is submitted to an active tagged target * then drain before submitting this cmd; SCSI-2 allows RQSENSE * to be untagged */ if (((sp->cmd_pkt_flags & FLAG_TAGMASK) == 0) && TAGGED(target) && fas->f_tcmds[slot] && ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) && (*(sp->cmd_pkt->pkt_cdbp) != SCMD_REQUEST_SENSE)) { if ((sp->cmd_pkt_flags & FLAG_NOINTR) == 0) { struct fas_cmd *dp; IPRINTF("untagged cmd, start draining\n"); if (fas->f_reset_delay[Tgt(sp)] == 0) { fas->f_throttle[slot] = DRAIN_THROTTLE; } dp = fas->f_readyf[slot]; fas->f_readyf[slot] = sp; sp->cmd_forw = dp; if (fas->f_readyb[slot] == NULL) { fas->f_readyb[slot] = sp; } } return (FALSE); } /* * allocate a tag; if no tag available then put request back * on the ready queue and return; eventually a cmd returns and we * get going again or we timeout */ if (TAGGED(target) && (sp->cmd_pkt_flags & FLAG_TAGMASK)) { if (fas_alloc_tag(fas, sp)) { return (FALSE); } } else { /* * tag slot 0 is reserved for non-tagged cmds * and should be empty because we have drained */ if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { ASSERT(fas->f_active[slot]->f_slot[0] == NULL); fas->f_active[slot]->f_slot[0] = sp; sp->cmd_tag[1] = 0; if (*(sp->cmd_pkt->pkt_cdbp) != SCMD_REQUEST_SENSE) { ASSERT(fas->f_tcmds[slot] == 0); /* * don't start any other cmd until this * one is finished. The throttle is reset * later in fas_watch() */ fas->f_throttle[slot] = 1; } (fas->f_tcmds[slot])++; } } fas->f_current_sp = sp; fas->f_omsglen = 0; tshift = 1<f_sdtr_sent = fas->f_wdtr_sent = 0; cdb_len = sp->cmd_actual_cdblen; if (sp->cmd_pkt_flags & FLAG_RENEGOTIATE_WIDE_SYNC) { fas_force_renegotiation(fas, Tgt(sp)); } /* * first send identify message, with or without disconnect priv. */ if (sp->cmd_pkt_flags & FLAG_NODISCON) { LOAD_CMDP = fas->f_last_msgout = MSG_IDENTIFY | lun; ASSERT((sp->cmd_pkt_flags & FLAG_TAGMASK) == 0); } else { LOAD_CMDP = fas->f_last_msgout = MSG_DR_IDENTIFY | lun; } /* * normal case, tagQ and we have negotiated wide and sync * or we don't need to renegotiate because wide and sync * have been disabled * (proxy msg's don't have tag flag set) */ if ((sp->cmd_pkt_flags & FLAG_TAGMASK) && ((fas->f_wide_known | fas->f_nowide) & (fas->f_sync_known | fas->f_nosync) & tshift)) { EPRINTF("tag cmd\n"); ASSERT((sp->cmd_pkt_flags & FLAG_NODISCON) == 0); fas->f_last_msgout = LOAD_CMDP = sp->cmd_tag[0]; LOAD_CMDP = sp->cmd_tag[1]; nstate = STATE_SELECT_NORMAL; cmd = CMD_SEL_ATN3 | CMD_DMA; /* * is this a proxy message */ } else if (sp->cmd_flags & CFLAG_CMDPROXY) { IPRINTF2("proxy cmd, len=%x, msg=%x\n", sp->cmd_cdb[FAS_PROXY_DATA], sp->cmd_cdb[FAS_PROXY_DATA+1]); /* * This is a proxy command. It will have * a message to send as part of post-selection * (e.g, MSG_ABORT or MSG_DEVICE_RESET) */ fas->f_omsglen = sp->cmd_cdb[FAS_PROXY_DATA]; for (i = 0; i < (uint_t)fas->f_omsglen; i++) { fas->f_cur_msgout[i] = sp->cmd_cdb[FAS_PROXY_DATA+1+i]; } sp->cmd_cdb[FAS_PROXY_RESULT] = FALSE; cdb_len = 0; cmd = CMD_SEL_STOP | CMD_DMA; nstate = STATE_SELECT_N_SENDMSG; /* * always negotiate wide first and sync after wide */ } else if (((fas->f_wide_known | fas->f_nowide) & tshift) == 0) { int i = 0; /* First the tag message bytes */ if (sp->cmd_pkt_flags & FLAG_TAGMASK) { fas->f_cur_msgout[i++] = sp->cmd_tag[0]; fas->f_cur_msgout[i++] = sp->cmd_tag[1]; } /* * Set up to send wide negotiating message. This is getting * a bit tricky as we dma out the identify message and * send the other messages via the fifo buffer. */ EPRINTF1("cmd with wdtr msg, tag=%x\n", sp->cmd_tag[1]); fas_make_wdtr(fas, i, target, FAS_XFER_WIDTH); cdb_len = 0; nstate = STATE_SELECT_N_SENDMSG; cmd = CMD_SEL_STOP | CMD_DMA; /* * negotiate sync xfer rate */ } else if (((fas->f_sync_known | fas->f_nosync) & tshift) == 0) { int i = 0; /* * Set up to send sync negotiating message. This is getting * a bit tricky as we dma out the identify message and * send the other messages via the fifo buffer. */ if (sp->cmd_pkt_flags & FLAG_TAGMASK) { fas->f_cur_msgout[i++] = sp->cmd_tag[0]; fas->f_cur_msgout[i++] = sp->cmd_tag[1]; } fas_make_sdtr(fas, i, target); cdb_len = 0; cmd = CMD_SEL_STOP | CMD_DMA; nstate = STATE_SELECT_N_SENDMSG; /* * normal cmds, no negotiations and not a proxy and no TQ */ } else { ASSERT((sp->cmd_pkt_flags & FLAG_TAGMASK) == 0); EPRINTF("std. cmd\n"); nstate = STATE_SELECT_NORMAL; cmd = CMD_SEL_ATN | CMD_DMA; } /* * Now load cdb (if any) */ for (i = 0; i < cdb_len; i++) { LOAD_CMDP = sp->cmd_cdbp[i]; } /* * calculate total dma amount: */ fas->f_lastcount = (uintptr_t)tp - (uintptr_t)fas->f_cmdarea; /* * load target id and enable bus id encoding and 32 bit counter */ fas_reg_write(fas, (uchar_t *)&fasreg->fas_busid, (target & 0xf) | FAS_BUSID_ENCODID | FAS_BUSID_32BIT_COUNTER); FAS_SET_PERIOD_OFFSET_CONF3_REGS(fas, target); fas_reg_cmd_write(fas, CMD_FLUSH); FAS_DMA_READ(fas, fas->f_lastcount, fas->f_dmacookie.dmac_address, 16, cmd); New_state(fas, (int)nstate); #ifdef FASDEBUG if (DDEBUGGING) { fas_dump_cmd(fas, sp); } #endif /* FASDEBUG */ /* * if timeout == 0, then it has no effect on the timeout * handling; we deal with this when an actual timeout occurs. */ if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { ASSERT(fas->f_tcmds[slot] >= 1); } i = pkt->pkt_time - slots->f_timebase; if (i == 0) { EPRINTF("dup timeout\n"); (slots->f_dups)++; slots->f_timeout = slots->f_timebase; } else if (i > 0) { EPRINTF("new timeout\n"); slots->f_timeout = slots->f_timebase = pkt->pkt_time; slots->f_dups = 1; } fas_check_ncmds(fas); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_STARTCMD_END, "fas_startcmd_end"); return (TRUE); } /* * Interrupt Entry Point. * Poll interrupts until they go away */ static uint_t fas_intr(caddr_t arg) { struct fas *fas = (struct fas *)arg; int rval = DDI_INTR_UNCLAIMED; int kstat_updated = 0; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_POLL_START, "fas_intr_start"); do { mutex_enter(FAS_MUTEX(fas)); do { if (fas_intr_svc(fas)) { /* * do not return immediately here because * we have to guarantee to always empty * the waitQ and callbackQ in the interrupt * handler */ if (fas->f_polled_intr) { rval = DDI_INTR_CLAIMED; fas->f_polled_intr = 0; } } else { rval = DDI_INTR_CLAIMED; } } while (INTPENDING(fas)); if (!kstat_updated && fas->f_intr_kstat && rval == DDI_INTR_CLAIMED) { FAS_KSTAT_INTR(fas); kstat_updated++; } /* * check and empty the waitQ and the callbackQ */ FAS_CHECK_WAITQ_AND_FAS_MUTEX_EXIT(fas); FAS_EMPTY_CALLBACKQ(fas); } while (INTPENDING(fas)); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_POLL_END, "fas_intr_end"); return (rval); } /* * General interrupt service routine. */ static char *dma_bits = DMA_BITS; static int fas_intr_svc(struct fas *fas) { static int (*evec[])(struct fas *fas) = { fas_finish_select, fas_reconnect, fas_phasemanage, fas_finish, fas_reset_recovery, fas_istart, fas_abort_curcmd, fas_reset_bus, fas_reset_bus, fas_handle_selection }; int action; uchar_t intr, stat; volatile struct fasreg *fasreg = fas->f_reg; int i = 0; TRACE_0(TR_FAC_SCSI_FAS, TR_FASSVC_START, "fas_intr_svc_start"); /* * A read of FAS interrupt register clears interrupt, * so any other volatile information needs to be latched * up prior to reading the interrupt register. */ fas->f_stat = fas_reg_read(fas, &fasreg->fas_stat); EPRINTF2("fas_intr_svc: state=%x stat=%x\n", fas->f_state, fas->f_stat); /* * this wasn't our interrupt? */ if ((fas->f_stat & FAS_STAT_IPEND) == 0) { if (fas_check_dma_error(fas)) { action = ACTION_RESET; goto start_action; } return (-1); } /* * if we are reset state, handle this first */ if (fas->f_state == ACTS_RESET) { action = ACTION_FINRST; goto start_action; } /* * check for gross error. fas366 hardware seems to register * the gross error bit when a parity error is found. Make sure * to ignore the gross error bit when a parity error is detected. */ if ((fas->f_stat & FAS_STAT_GERR) && (fas->f_stat & FAS_STAT_PERR) == 0) { action = fas_handle_gross_err(fas); goto start_action; } /* * now it is finally safe to read the interrupt register * if we haven't done so yet * Note: we don't read step register here but only in * fas_finish_select(). It is not entirely safe but saves * redundant PIOs or extra code in this critical path */ fas->f_intr = intr = fas_reg_read(fas, (uchar_t *)&fasreg->fas_intr); /* * read the fifo if there is something there or still in the * input shuttle */ stat = fas->f_stat & FAS_PHASE_MASK; if ((intr & FAS_INT_RESEL) || ((stat != FAS_PHASE_DATA_IN) && (stat != FAS_PHASE_DATA_OUT) && ((fas->f_state & STATE_SELECTING) == 0) && (fas->f_state != ACTS_DATA_DONE) && (fas->f_state != ACTS_C_CMPLT))) { fas->f_stat2 = fas_reg_read(fas, &fasreg->fas_stat2); if (((fas->f_stat2 & FAS_STAT2_EMPTY) == 0) || (fas->f_stat2 & FAS_STAT2_ISHUTTLE)) { fas_read_fifo(fas); } } EPRINTF2("fas_intr_svc: intr=%x, stat=%x\n", fas->f_intr, fas->f_stat); EPRINTF2("dmacsr=%b\n", fas->f_dma->dma_csr, dma_bits); /* * Based upon the current state of the host adapter driver * we should be able to figure out what to do with an interrupt. * * The FAS asserts an interrupt with one or more of 8 possible * bits set in its interrupt register. These conditions are * SCSI bus reset detected, an illegal command fed to the FAS, * one of DISCONNECT, BUS SERVICE, FUNCTION COMPLETE conditions * for the FAS, a Reselection interrupt, or one of Selection * or Selection with Attention. * * Of these possible interrupts, we can deal with some right * here and now, irrespective of the current state of the driver. * * take care of the most likely interrupts first and call the action * immediately */ if ((intr & (FAS_INT_RESET|FAS_INT_ILLEGAL|FAS_INT_SEL|FAS_INT_SELATN| FAS_INT_RESEL)) == 0) { /* * The rest of the reasons for an interrupt can * be handled based purely on the state that the driver * is currently in now. */ if (fas->f_state & STATE_SELECTING) { action = fas_finish_select(fas); } else if (fas->f_state & STATE_ITPHASES) { action = fas_phasemanage(fas); } else { fas_log(fas, CE_WARN, "spurious interrupt"); action = ACTION_RETURN; } } else if ((intr & FAS_INT_RESEL) && ((intr & (FAS_INT_RESET|FAS_INT_ILLEGAL|FAS_INT_SEL|FAS_INT_SELATN)) == 0)) { if ((fas->f_state & STATE_SELECTING) == 0) { ASSERT(fas->f_state == STATE_FREE); action = fas_reconnect(fas); } else { action = fas_reselect_preempt(fas); } } else if (intr & (FAS_INT_RESET | FAS_INT_ILLEGAL)) { action = fas_illegal_cmd_or_bus_reset(fas); } else if (intr & (FAS_INT_SEL|FAS_INT_SELATN)) { action = ACTION_SELECT; } start_action: while (action != ACTION_RETURN) { ASSERT((action >= 0) && (action <= ACTION_SELECT)); TRACE_3(TR_FAC_SCSI_FAS, TR_FASSVC_ACTION_CALL, "fas_intr_svc call: fas 0x%p, action %d (%d)", fas, action, i); i++; action = (*evec[action])(fas); } exit: TRACE_0(TR_FAC_SCSI_FAS, TR_FASSVC_END, "fas_intr_svc_end"); return (0); } /* * Manage phase transitions. */ static int fas_phasemanage(struct fas *fas) { ushort_t state; int action; static int (*pvecs[])(struct fas *fas) = { fas_handle_cmd_start, fas_handle_cmd_done, fas_handle_msg_out_start, fas_handle_msg_out_done, fas_handle_msg_in_start, fas_handle_more_msgin, fas_handle_msg_in_done, fas_handle_clearing, fas_handle_data_start, fas_handle_data_done, fas_handle_c_cmplt, fas_reconnect, fas_handle_unknown, fas_reset_recovery }; int i = 0; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_PHASEMANAGE_START, "fas_phasemanage_start"); do { EPRINTF1("fas_phasemanage: %s\n", fas_state_name(fas->f_state & STATE_ITPHASES)); TRACE_2(TR_FAC_SCSI_FAS, TR_FAS_PHASEMANAGE_CALL, "fas_phasemanage_call: fas 0x%p (%d)", fas, i++); state = fas->f_state; if (!(state == STATE_FREE || state > ACTS_ENDVEC)) { ASSERT(pvecs[state-1] != NULL); action = (*pvecs[state-1]) (fas); } else { fas_log(fas, CE_WARN, "lost state in phasemanage"); action = ACTION_ABORT_ALLCMDS; } } while (action == ACTION_PHASEMANAGE); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_PHASEMANAGE_END, "fas_phasemanage_end"); return (action); } /* * remove a cmd from active list and if timeout flag is set, then * adjust timeouts; if a the same cmd will be resubmitted soon, don't * bother to adjust timeouts (ie. don't set this flag) */ static void fas_remove_cmd(struct fas *fas, struct fas_cmd *sp, int new_timeout_flag) { int tag = sp->cmd_tag[1]; int slot = sp->cmd_slot; struct f_slots *tag_slots = fas->f_active[slot]; ASSERT(sp != NULL); EPRINTF4("remove tag %d slot %d for target %d.%d\n", tag, slot, Tgt(sp), Lun(sp)); if (sp == tag_slots->f_slot[tag]) { tag_slots->f_slot[tag] = NULL; fas->f_tcmds[slot]--; } if (fas->f_current_sp == sp) { fas->f_current_sp = NULL; } ASSERT(sp != fas->f_active[sp->cmd_slot]->f_slot[sp->cmd_tag[1]]); if (new_timeout_flag != NEW_TIMEOUT) { return; } /* * Figure out what to set tag Q timeout for... * * Optimize: If we have duplicate's of same timeout * we're using, then we'll use it again until we run * out of duplicates. This should be the normal case * for block and raw I/O. * If no duplicates, we have to scan through tag que and * find the longest timeout value and use it. This is * going to take a while... */ if (sp->cmd_pkt->pkt_time == tag_slots->f_timebase) { if (--(tag_slots->f_dups) <= 0) { if (fas->f_tcmds[slot]) { struct fas_cmd *ssp; uint_t n = 0; ushort_t t = tag_slots->f_n_slots; ushort_t i; /* * This crude check assumes we don't do * this too often which seems reasonable * for block and raw I/O. */ for (i = 0; i < t; i++) { ssp = tag_slots->f_slot[i]; if (ssp && (ssp->cmd_pkt->pkt_time > n)) { n = ssp->cmd_pkt->pkt_time; tag_slots->f_dups = 1; } else if (ssp && (ssp->cmd_pkt->pkt_time == n)) { tag_slots->f_dups++; } } tag_slots->f_timebase = n; EPRINTF1("searching, new_timeout= %d\n", n); } else { tag_slots->f_dups = 0; tag_slots->f_timebase = 0; } } } tag_slots->f_timeout = tag_slots->f_timebase; ASSERT(fas->f_ncmds >= fas->f_ndisc); } /* * decrement f_ncmds and f_ndisc for this cmd before completing */ static void fas_decrement_ncmds(struct fas *fas, struct fas_cmd *sp) { ASSERT((sp->cmd_flags & CFLAG_FREE) == 0); if ((sp->cmd_flags & CFLAG_FINISHED) == 0) { fas->f_ncmds--; if (sp->cmd_flags & CFLAG_CMDDISC) { fas->f_ndisc--; } sp->cmd_flags |= CFLAG_FINISHED; sp->cmd_flags &= ~CFLAG_CMDDISC; } ASSERT((fas->f_ncmds >= 0) && (fas->f_ndisc >= 0)); ASSERT(fas->f_ncmds >= fas->f_ndisc); } /* * Most commonly called phase handlers: * * Finish routines */ static int fas_finish(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; struct scsi_pkt *pkt = CMD2PKT(sp); int action = ACTION_SEARCH; struct scsi_status *status = (struct scsi_status *)sp->cmd_pkt->pkt_scbp; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_START, "fas_finish_start"); EPRINTF("fas_finish\n"); #ifdef FAS_TEST if (fas_test_stop && (sp->cmd_pkt_flags & 0x80000000)) { debug_enter("untagged cmd completed"); } #endif /* * immediately enable reselects */ fas_reg_cmd_write(fas, CMD_EN_RESEL); if (status->sts_chk) { /* * In the case that we are getting a check condition * clear our knowledge of synchronous capabilities. * This will unambiguously force a renegotiation * prior to any possible data transfer (we hope), * including the data transfer for a UNIT ATTENTION * condition generated by somebody powering on and * off a target. */ fas_force_renegotiation(fas, Tgt(sp)); } /* * backoff sync/wide if there were parity errors */ if (sp->cmd_pkt->pkt_statistics & STAT_PERR) { fas_sync_wide_backoff(fas, sp, sp->cmd_slot); #ifdef FAS_TEST if (fas_test_stop) { debug_enter("parity error"); } #endif } /* * Free from active list and update counts * We need to clean up this cmd now, just in case fas_ustart() * hits a reset or other fatal transport error */ fas_check_ncmds(fas); fas_remove_cmd(fas, sp, NEW_TIMEOUT); fas_decrement_ncmds(fas, sp); fas_check_ncmds(fas); /* * go to state free and try to start a new cmd now */ New_state(fas, STATE_FREE); if ((fas->f_ncmds > fas->f_ndisc) && (*((char *)status) == 0) && (INTPENDING(fas) == 0)) { if (fas_ustart(fas)) { action = ACTION_RETURN; } } /* * if there was a data xfer then calculate residue and * sync data for consistent memory xfers */ if (pkt->pkt_state & STATE_XFERRED_DATA) { pkt->pkt_resid = sp->cmd_dmacount - sp->cmd_data_count; if (sp->cmd_flags & CFLAG_CMDIOPB) { (void) ddi_dma_sync(sp->cmd_dmahandle, 0, (uint_t)0, DDI_DMA_SYNC_FORCPU); } if (pkt->pkt_resid) { IPRINTF3("%d.%d finishes with %ld resid\n", Tgt(sp), Lun(sp), pkt->pkt_resid); } } if (sp->cmd_pkt_flags & FLAG_NOINTR) { fas_call_pkt_comp(fas, sp); action = ACTION_RETURN; } else { /* * start an autorequest sense if there was a check condition. * if arq has not been enabled, fas_handle_sts_chk will do * do the callback */ if (status->sts_chk) { if (fas_handle_sts_chk(fas, sp)) { /* * we can't start an arq because one is * already in progress. the target is * probably confused */ action = ACTION_ABORT_CURCMD; } } else if ((*((char *)status) & STATUS_MASK) == STATUS_QFULL) { fas_handle_qfull(fas, sp); } else { #ifdef FAS_TEST if (fas_arqs_failure && (status->sts_chk == 0)) { struct scsi_arq_status *arqstat; status->sts_chk = 1; arqstat = (struct scsi_arq_status *) (sp->cmd_pkt->pkt_scbp); arqstat->sts_rqpkt_reason = CMD_TRAN_ERR; sp->cmd_pkt->pkt_state |= STATE_ARQ_DONE; fas_arqs_failure = 0; } if (fas_tran_err) { sp->cmd_pkt->pkt_reason = CMD_TRAN_ERR; fas_tran_err = 0; } #endif fas_call_pkt_comp(fas, sp); } } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_END, "fas_finish_end"); return (action); } /* * Complete the process of selecting a target */ static int fas_finish_select(struct fas *fas) { volatile struct dma *dmar = fas->f_dma; struct fas_cmd *sp = fas->f_current_sp; uchar_t intr = fas->f_intr; uchar_t step; step = fas_reg_read(fas, &fas->f_reg->fas_step) & FAS_STEP_MASK; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_SELECT_START, "fas_finish_select_start"); EPRINTF("fas_finish_select:\n"); ASSERT(sp != 0); /* * Check for DMA gate array errors */ if ((fas->f_dma_csr = fas_dma_reg_read(fas, &dmar->dma_csr)) & DMA_ERRPEND) { /* * It would be desirable to set the ATN* line and attempt to * do the whole schmear of INITIATOR DETECTED ERROR here, * but that is too hard to do at present. */ fas_log(fas, CE_WARN, "Unrecoverable DMA error during selection"); fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_SELECT_RESET1_END, "fas_finish_select_end (ACTION_RESET1)"); return (ACTION_RESET); } /* * Shut off DMA gate array */ FAS_FLUSH_DMA(fas); /* * Did something respond to selection? */ if (intr == (FAS_INT_BUS|FAS_INT_FCMP)) { /* * We succesfully selected a target (we think). * Now we figure out how botched things are * based upon the kind of selection we were * doing and the state of the step register. */ switch (step) { case FAS_STEP_ARBSEL: /* * In this case, we selected the target, but went * neither into MESSAGE OUT nor COMMAND phase. * However, this isn't a fatal error, so we just * drive on. * * This might be a good point to note that we have * a target that appears to not accomodate * disconnecting, * but it really isn't worth the effort to distinguish * such targets fasecially from others. */ /* FALLTHROUGH */ case FAS_STEP_SENTID: /* * In this case, we selected the target and sent * message byte and have stopped with ATN* still on. * This case should only occur if we use the SELECT * AND STOP command. */ /* FALLTHROUGH */ case FAS_STEP_NOTCMD: /* * In this case, we either didn't transition to command * phase, or, * if we were using the SELECT WITH ATN3 command, * we possibly didn't send all message bytes. */ break; case FAS_STEP_PCMD: /* * In this case, not all command bytes transferred. */ /* FALLTHROUGH */ case FAS_STEP_DONE: /* * This is the usual 'good' completion point. * If we we sent message byte(s), we subtract * off the number of message bytes that were * ahead of the command. */ sp->cmd_pkt->pkt_state |= STATE_SENT_CMD; break; default: fas_log(fas, CE_WARN, "bad sequence step (0x%x) in selection", step); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_SELECT_RESET3_END, "fas_finish_select_end (ACTION_RESET3)"); return (ACTION_RESET); } /* * OR in common state... */ sp->cmd_pkt->pkt_state |= (STATE_GOT_BUS|STATE_GOT_TARGET); /* * data pointer initialization has already been done */ New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_SELECT_ACTION3_END, "fas_finish_select_end (action3)"); return (fas_handle_unknown(fas)); } else if (intr == FAS_INT_DISCON) { /* * make sure we negotiate when this target comes * on line later on */ fas_force_renegotiation(fas, Tgt(sp)); fas->f_sdtr_sent = fas->f_wdtr_sent = 0; sp->cmd_pkt->pkt_state |= STATE_GOT_BUS; /* * Set the throttle to DRAIN_THROTTLE to make * sure any disconnected commands will get timed out * incase the drive dies */ if (fas->f_reset_delay[Tgt(sp)] == 0) { fas->f_throttle[sp->cmd_slot] = DRAIN_THROTTLE; } fas_set_pkt_reason(fas, sp, CMD_INCOMPLETE, 0); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_SELECT_FINISH_END, "fas_finish_select_end (ACTION_FINISH)"); return (ACTION_FINISH); } else { fas_printstate(fas, "undetermined selection failure"); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_FINISH_SELECT_RESET2_END, "fas_finish_select_end (ACTION_RESET2)"); return (ACTION_RESET); } _NOTE(NOT_REACHED) /* NOTREACHED */ } /* * a selection got preempted by a reselection; shut down dma * and put back cmd in the ready queue unless NOINTR */ static int fas_reselect_preempt(struct fas *fas) { int rval; /* * A reselection attempt glotzed our selection attempt. * we put request back in the ready queue */ struct fas_cmd *sp = fas->f_current_sp; /* * Shut off DMA gate array */ FAS_FLUSH_DMA(fas); /* * service the reconnect now and clean up later */ New_state(fas, STATE_FREE); rval = fas_reconnect(fas); /* * If selection for a non-tagged command is preempted, the * command could be stuck because throttle was set to DRAIN, * and a disconnected command timeout follows. */ if ((sp->cmd_pkt_flags & FLAG_TAGMASK) == 0) fas->f_throttle[sp->cmd_slot] = 1; if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { fas_remove_cmd(fas, sp, NEW_TIMEOUT); } /* * if we attempted to renegotiate on this cmd, undo this now */ if (fas->f_wdtr_sent) { fas->f_wide_known &= ~(1<f_wdtr_sent = 0; } if (fas->f_sdtr_sent) { fas->f_sync_known &= ~(1<f_sdtr_sent = 0; } fas_head_of_readyQ(fas, sp); return (rval); } /* * Handle the reconnection of a target */ static int fas_reconnect(struct fas *fas) { volatile struct fasreg *fasreg = fas->f_reg; struct fas_cmd *sp = NULL; uchar_t target, lun; uchar_t tmp; uchar_t slot; char *bad_reselect = NULL; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_RECONNECT_START, "fas_reconnect_start"); EPRINTF("fas_reconnect:\n"); fas_check_ncmds(fas); switch (fas->f_state) { default: /* * Pick up target id from fifo * * There should only be the reselecting target's id * and an identify message in the fifo. */ target = fas->f_fifo[0]; /* * we know the target so update period, conf3, * offset reg, if necessary, and accept the msg */ FAS_SET_PERIOD_OFFSET_CONF3_REGS(fas, target); /* * now we can accept the message. an untagged * target will go immediately into data phase so * the period/offset/conf3 registers need to be * updated before accepting the message */ fas_reg_cmd_write(fas, CMD_MSG_ACPT); if (fas->f_fifolen != 2) { bad_reselect = "bad reselect bytes"; break; } /* * normal initial reconnect; we get another interrupt later * for the tag */ New_state(fas, ACTS_RESEL); if (fas->f_stat & FAS_STAT_PERR) { break; } /* * Check sanity of message. */ tmp = fas->f_fifo[1]; fas->f_last_msgin = tmp; if (!(IS_IDENTIFY_MSG(tmp)) || (tmp & INI_CAN_DISCON)) { bad_reselect = "bad identify msg"; break; } lun = tmp & (NLUNS_PER_TARGET-1); EPRINTF2("fas_reconnect: target=%x, idmsg=%x\n", target, tmp); fas->f_resel_slot = slot = (target * NLUNS_PER_TARGET) | lun; fas_reg_write(fas, (uchar_t *)&fasreg->fas_busid, (target & 0xf) | FAS_BUSID_ENCODID | FAS_BUSID_32BIT_COUNTER); /* * If tag queueing in use, DMA in tag. * Otherwise, we're ready to go. * if tag 0 slot is non-empty, a non-tagged cmd is * reconnecting */ if (TAGGED(target) && fas->f_tcmds[slot] && (fas->f_active[slot]->f_slot[0] == NULL)) { volatile uchar_t *c = (uchar_t *)fas->f_cmdarea; /* * If we've been doing tagged queueing and this * request doesn't do it, * maybe it was disabled for this one. This is rather * dangerous as it blows all pending tagged cmds away. * But if target is confused, then we'll blow up * shortly. */ *c++ = INVALID_MSG; *c = INVALID_MSG; FAS_DMA_WRITE_SETUP(fas, 2, fas->f_dmacookie.dmac_address); /* * For tagged queuing, we should still be in msgin * phase. * If not, then either we aren't running tagged * queueing like we thought or the target died. */ if (INTPENDING(fas) == 0) { EPRINTF1("slow reconnect, slot=%x\n", slot); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_RECONNECT_RETURN1_END, "fas_reconnect_end (_RETURN1)"); return (ACTION_RETURN); } fas->f_stat = fas_reg_read(fas, &fasreg->fas_stat); fas->f_intr = fas_reg_read(fas, &fasreg->fas_intr); if (fas->f_intr & (FAS_INT_ILLEGAL | FAS_INT_RESET)) { return (fas_illegal_cmd_or_bus_reset(fas)); } if ((fas->f_stat & FAS_PHASE_MASK) != FAS_PHASE_MSG_IN) { bad_reselect = "not in msgin phase"; break; } if (fas->f_intr & FAS_INT_DISCON) { bad_reselect = "unexpected bus free"; break; } } else { fas->f_current_sp = sp = fas->f_active[slot]->f_slot[0]; break; } /*FALLTHROUGH*/ case ACTS_RESEL: { volatile uchar_t *c = (uchar_t *)fas->f_cmdarea; struct f_slots *tag_slots; int id, tag; uint_t i; slot = fas->f_resel_slot; target = slot/NLUNS_PER_TARGET; if ((fas->f_stat & FAS_PHASE_MASK) != FAS_PHASE_MSG_IN) { IPRINTF1("no tag for slot %x\n", slot); if (fas->f_intr & ~(FAS_INT_BUS | FAS_INT_FCMP)) { New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_RECONNECT_PHASEMANAGE_END, "fas_reconnect_end (_PHASEMANAGE)"); return (ACTION_PHASEMANAGE); } else { bad_reselect = "not in msgin phase"; break; } } fas_reg_cmd_write(fas, CMD_TRAN_INFO|CMD_DMA); fas_dma_reg_write(fas, &fas->f_dma->dma_csr, fas->f_dma_csr); fas_reg_cmd_write(fas, CMD_MSG_ACPT); for (i = 0; i < (uint_t)RECONNECT_TAG_RCV_TIMEOUT; i++) { /* * timeout is not very accurate but this * should take no time at all */ if (INTPENDING(fas)) { fas->f_stat = fas_reg_read(fas, (uchar_t *)&fas->f_reg->fas_stat); fas->f_intr = fas_reg_read(fas, (uchar_t *)&fas->f_reg->fas_intr); if (fas->f_intr & (FAS_INT_RESET | FAS_INT_ILLEGAL)) { return ( fas_illegal_cmd_or_bus_reset (fas)); } if (fas->f_intr & FAS_INT_FCMP) { break; } } } if (i == (uint_t)RECONNECT_TAG_RCV_TIMEOUT) { bad_reselect = "timeout on receiving tag msg"; break; } FAS_FLUSH_DMA(fas); /* * we should really do a sync here but that * hurts performance too much; we'll just hang * around till the tag byte flips * This is necessary on any system with an * XBox */ if (*c == INVALID_MSG) { EPRINTF( "fas_reconnect: invalid msg, polling\n"); for (i = 0; i < 1000000; i++) { if (*c != INVALID_MSG) break; } } if (fas->f_stat & FAS_STAT_PERR) { break; } if ((fas->f_stat & FAS_STAT_XZERO) == 0 || (id = *c++) < MSG_SIMPLE_QTAG || id > MSG_ORDERED_QTAG) { /* * Target agreed to do tagged queueing * and lied! * This problem implies the drive firmware is * broken. */ bad_reselect = "botched tag"; break; } tag = *c; /* Set ptr to reconnecting scsi pkt */ tag_slots = fas->f_active[slot]; if (tag_slots != NULL) { sp = tag_slots->f_slot[tag]; } else { bad_reselect = "Invalid tag"; break; } fas->f_current_sp = sp; } } if (fas->f_stat & FAS_STAT_PERR) { sp = NULL; bad_reselect = "Parity error in reconnect msg's"; } if ((sp == NULL || #ifdef FAS_TEST (fas_atest_reconn & (1<cmd_flags & (CFLAG_CMDDISC|CFLAG_CMDPROXY)) == 0)) { /* * this shouldn't really happen, so it is better * to reset the bus; some disks accept the abort * and then still reconnect */ if (bad_reselect == NULL) { bad_reselect = "no command"; } #ifdef FAS_TEST if (sp && !(fas_atest_reconn & (1<cmd_flags & CFLAG_CMDPROXY) { /* * If we got here, we were already attempting to * run a polled proxy command for this target. * Set ATN and, copy in the message, and drive * on (ignoring any parity error on the identify). */ IPRINTF1("fas_reconnect: fielding proxy cmd for %d\n", target); fas_assert_atn(fas); fas->f_omsglen = sp->cmd_cdb[FAS_PROXY_DATA]; tmp = 0; while (tmp < fas->f_omsglen) { fas->f_cur_msgout[tmp] = sp->cmd_cdb[FAS_PROXY_DATA+1+tmp]; tmp++; } sp->cmd_cdb[FAS_PROXY_RESULT] = FALSE; /* * pretend that the disconnected cmd is still disconnected * (this prevents ndisc from going negative) */ fas->f_ndisc++; ASSERT((fas->f_ncmds >= 0) && (fas->f_ndisc >= 0)); ASSERT(fas->f_ncmds >= fas->f_ndisc); } ASSERT(fas->f_resel_slot == slot); ASSERT(fas->f_ndisc > 0); fas->f_ndisc--; sp->cmd_flags &= ~CFLAG_CMDDISC; New_state(fas, ACTS_UNKNOWN); /* * A reconnect may imply a restore pointers operation * Note that some older disks (Micropolis in Pbox) do not * send a save data ptr on disconnect if all data has been * xferred. So, we cannot restore ptrs yet here. */ if ((sp->cmd_flags & CFLAG_DMAVALID) && (sp->cmd_data_count != sp->cmd_saved_data_count)) { sp->cmd_flags |= CFLAG_RESTORE_PTRS; } /* * Return to await the FUNCTION COMPLETE interrupt we * should get out of accepting the IDENTIFY message. */ EPRINTF2("Reconnecting %d.%d\n", target, slot % NLUNS_PER_TARGET); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_RECONNECT_RETURN2_END, "fas_reconnect_end (_RETURN2)"); return (ACTION_RETURN); bad: if (sp && (fas->f_stat & FAS_STAT_PERR)) { sp->cmd_pkt->pkt_statistics |= STAT_PERR; } fas_log(fas, CE_WARN, "target %x: failed reselection (%s)", target, bad_reselect); #ifdef FASDEBUG fas_printstate(fas, "failed reselection"); #endif TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_RECONNECT_RESET5_END, "fas_reconnect_end (_RESET5)"); return (ACTION_RESET); } /* * handle unknown bus phase * we don't know what to expect so check status register for current * phase */ int fas_handle_unknown(struct fas *fas) { TRACE_1(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_START, "fas_handle_unknown_start: fas 0x%p", fas); EPRINTF("fas_handle_unknown:\n"); if ((fas->f_intr & FAS_INT_DISCON) == 0) { /* * we call actions here rather than returning to phasemanage * (this is the most frequently called action) */ switch (fas->f_stat & FAS_PHASE_MASK) { case FAS_PHASE_DATA_IN: case FAS_PHASE_DATA_OUT: New_state(fas, ACTS_DATA); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_PHASE_DATA_END, "fas_handle_unknown_end (phase_data)"); return (fas_handle_data_start(fas)); case FAS_PHASE_MSG_OUT: New_state(fas, ACTS_MSG_OUT); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_PHASE_MSG_OUT_END, "fas_handle_unknown_end (phase_msg_out)"); return (fas_handle_msg_out_start(fas)); case FAS_PHASE_MSG_IN: New_state(fas, ACTS_MSG_IN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_PHASE_MSG_IN_END, "fas_handle_unknown_end (phase_msg_in)"); return (fas_handle_msg_in_start(fas)); case FAS_PHASE_STATUS: fas_reg_cmd_write(fas, CMD_FLUSH); #ifdef FAS_TEST if (fas_ptest_status & (1<f_current_sp))) { fas_assert_atn(fas); } #endif /* FAS_TEST */ fas_reg_cmd_write(fas, CMD_COMP_SEQ); New_state(fas, ACTS_C_CMPLT); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_PHASE_STATUS_END, "fas_handle_unknown_end (phase_status)"); return (fas_handle_c_cmplt(fas)); case FAS_PHASE_COMMAND: New_state(fas, ACTS_CMD_START); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_PHASE_CMD_END, "fas_handle_unknown_end (phase_cmd)"); return (fas_handle_cmd_start(fas)); } fas_printstate(fas, "Unknown bus phase"); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_RESET_END, "fas_handle_unknown_end (reset)"); return (ACTION_RESET); } else { /* * Okay. What to do now? Let's try (for the time being) * assuming that the target went south and dropped busy, * as a disconnect implies that either we received * a completion or a disconnect message, or that we * had sent an ABORT OPERATION or BUS DEVICE RESET * message. In either case, we expected the disconnect * and should have fielded it elsewhere. * * If we see a chip disconnect here, this is an unexpected * loss of BSY*. Clean up the state of the chip and return. * */ int msgout = fas->f_cur_msgout[0]; struct fas_cmd *sp = fas->f_current_sp; int target = Tgt(sp); if (msgout == MSG_HEAD_QTAG || msgout == MSG_SIMPLE_QTAG) { msgout = fas->f_cur_msgout[2]; } EPRINTF4("msgout: %x %x %x, last_msgout=%x\n", fas->f_cur_msgout[0], fas->f_cur_msgout[1], fas->f_cur_msgout[2], fas->f_last_msgout); if (msgout == MSG_ABORT || msgout == MSG_ABORT_TAG || msgout == MSG_DEVICE_RESET) { IPRINTF2("Successful %s message to target %d\n", scsi_mname(msgout), Tgt(sp)); if (sp->cmd_flags & CFLAG_CMDPROXY) { sp->cmd_cdb[FAS_PROXY_RESULT] = TRUE; } if (msgout == MSG_ABORT || msgout == MSG_ABORT_TAG) { fas->f_abort_msg_sent++; if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { fas_set_pkt_reason(fas, sp, CMD_ABORTED, STAT_ABORTED); } } else if (msgout == MSG_DEVICE_RESET) { fas->f_reset_msg_sent++; if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { fas_set_pkt_reason(fas, sp, CMD_RESET, STAT_DEV_RESET); } fas_force_renegotiation(fas, target); } } else { if ((fas->f_last_msgout == MSG_EXTENDED) && (fas->f_last_msgin == MSG_REJECT)) { /* * the target rejected the negotiations, * so resubmit again (no_sync/no_wide * is now set) */ New_state(fas, STATE_FREE); fas_reg_cmd_write(fas, CMD_EN_RESEL); fas_remove_cmd(fas, sp, NEW_TIMEOUT); fas_decrement_ncmds(fas, sp); fas_check_ncmds(fas); sp->cmd_flags &= ~CFLAG_TRANFLAG; (void) fas_accept_pkt(fas, sp, NO_TRAN_BUSY); fas_check_ncmds(fas); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_INT_DISCON_END, "fas_handle_unknown_end (int_discon)"); return (ACTION_SEARCH); } else if (fas->f_last_msgout == MSG_EXTENDED) { /* * target dropped off the bus during * negotiations */ fas_reset_sync_wide(fas); fas->f_sdtr_sent = fas->f_wdtr_sent = 0; } fas_set_pkt_reason(fas, sp, CMD_UNX_BUS_FREE, 0); #ifdef FASDEBUG fas_printstate(fas, "unexpected bus free"); #endif } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_UNKNOWN_INT_DISCON_END, "fas_handle_unknown_end (int_discon)"); return (ACTION_FINISH); } _NOTE(NOT_REACHED) /* NOTREACHED */ } /* * handle target disconnecting */ static int fas_handle_clearing(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CLEARING_START, "fas_handle_clearing_start"); EPRINTF("fas_handle_clearing:\n"); if (fas->f_laststate == ACTS_C_CMPLT || fas->f_laststate == ACTS_MSG_IN_DONE) { if (INTPENDING(fas)) { volatile struct fasreg *fasreg = fas->f_reg; fas->f_stat = fas_reg_read(fas, (uchar_t *)&fasreg->fas_stat); fas->f_intr = fas_reg_read(fas, (uchar_t *)&fasreg->fas_intr); if (fas->f_intr & (FAS_INT_RESET | FAS_INT_ILLEGAL)) { return (fas_illegal_cmd_or_bus_reset(fas)); } } else { /* * change e_laststate for the next time around */ fas->f_laststate = ACTS_CLEARING; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CLEARING_RETURN1_END, "fas_handle_clearing_end (ACTION_RETURN1)"); return (ACTION_RETURN); } } if (fas->f_intr == FAS_INT_DISCON) { /* * At this point the FAS chip has disconnected. The bus should * be either quiet or someone may be attempting a reselection * of us (or somebody else). Call the routine that sets the * chip back to a correct and known state. * If the last message in was a disconnect, search * for new work to do, else return to call fas_finish() */ fas->f_last_msgout = 0xff; fas->f_omsglen = 0; if (fas->f_last_msgin == MSG_DISCONNECT) { fas_reg_cmd_write(fas, CMD_EN_RESEL); New_state(fas, STATE_FREE); ASSERT(fas->f_current_sp != NULL); EPRINTF2("disconnecting %d.%d\n", Tgt(sp), Lun(sp)); sp->cmd_pkt->pkt_statistics |= STAT_DISCON; sp->cmd_flags |= CFLAG_CMDDISC; if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { fas->f_ndisc++; } ASSERT((fas->f_ncmds >= 0) && (fas->f_ndisc >= 0)); ASSERT(fas->f_ncmds >= fas->f_ndisc); fas->f_current_sp = NULL; /* * start a cmd here to save time */ if ((fas->f_ncmds > fas->f_ndisc) && fas_ustart(fas)) { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CLEARING_RETURN2_END, "fas_handle_clearing_end (ACTION_RETURN2)"); return (ACTION_RETURN); } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CLEARING_RETURN3_END, "fas_handle_clearing_end (ACTION_RETURN3)"); return (ACTION_RETURN); } else { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CLEARING_END, "fas_handle_clearing_end"); return (fas_finish(fas)); } } else { /* * If the target didn't disconnect from the * bus, that is a gross fatal error. * XXX this can be caused by asserting ATN * XXX check bus phase and if msgout, send a message */ fas_log(fas, CE_WARN, "Target %d didn't disconnect after sending %s", Tgt(sp), scsi_mname(fas->f_last_msgin)); fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); #ifdef FASDEBUG IPRINTF4("msgout: %x %x %x, last_msgout=%x\n", fas->f_cur_msgout[0], fas->f_cur_msgout[1], fas->f_cur_msgout[2], fas->f_last_msgout); IPRINTF1("last msgin=%x\n", fas->f_last_msgin); #endif TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CLEARING_ABORT_END, "fas_handle_clearing_end (ACTION_ABORT_CURCMD)"); return (ACTION_ABORT_ALLCMDS); } } /* * handle data phase start */ static int fas_handle_data_start(struct fas *fas) { uint64_t end; uint32_t amt; struct fas_cmd *sp = fas->f_current_sp; int sending, phase; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_START, "fas_handle_data_start"); EPRINTF("fas_handle_data_start:\n"); if ((sp->cmd_flags & CFLAG_DMAVALID) == 0) { fas_printstate(fas, "unexpected data phase"); bad: fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_ABORT1_END, "fas_handle_data_end (ACTION_ABORT_CURCMD1)"); return (ACTION_ABORT_CURCMD); } else { sending = (sp->cmd_flags & CFLAG_DMASEND)? 1 : 0; } if (sp->cmd_flags & CFLAG_RESTORE_PTRS) { if (fas_restore_pointers(fas, sp)) { return (ACTION_ABORT_CURCMD); } sp->cmd_flags &= ~CFLAG_RESTORE_PTRS; } /* * And make sure our DMA pointers are in good shape. * * Because SCSI is SCSI, the current DMA pointer has got to be * greater than or equal to our DMA base address. All other cases * that might have affected this always set curaddr to be >= * to the DMA base address. */ ASSERT(sp->cmd_cur_addr >= sp->cmd_dmacookie.dmac_address); end = (uint64_t)sp->cmd_dmacookie.dmac_address + (uint64_t)sp->cmd_dmacookie.dmac_size; DPRINTF5( "cmd_data_count=%x, dmacount=%x, curaddr=%x, end=%" PRIx64 ", nwin=%x\n", sp->cmd_data_count, sp->cmd_dmacount, sp->cmd_cur_addr, end, sp->cmd_nwin); DPRINTF2("dmac_address = %x, dmac_size=%lx\n", sp->cmd_dmacookie.dmac_address, sp->cmd_dmacookie.dmac_size); if (sp->cmd_cur_addr >= end) { if (fas_next_window(fas, sp, end)) { goto bad; } end = (uint64_t)sp->cmd_dmacookie.dmac_address + (uint64_t)sp->cmd_dmacookie.dmac_size; DPRINTF2("dmac_address=%x, dmac_size=%lx\n", sp->cmd_dmacookie.dmac_address, sp->cmd_dmacookie.dmac_size); } amt = end - sp->cmd_cur_addr; if (fas->f_dma_attr->dma_attr_count_max < amt) { amt = fas->f_dma_attr->dma_attr_count_max; } DPRINTF3("amt=%x, end=%lx, cur_addr=%x\n", amt, end, sp->cmd_cur_addr); #ifdef FASDEBUG /* * Make sure that we don't cross a boundary we can't handle */ end = (uint64_t)sp->cmd_cur_addr + (uint64_t)amt - 1; if ((end & ~fas->f_dma_attr->dma_attr_seg) != (sp->cmd_cur_addr & ~fas->f_dma_attr->dma_attr_seg)) { EPRINTF3("curaddr %x curaddr+amt %" PRIx64 " cntr_max %" PRIx64 "\n", sp->cmd_cur_addr, end, fas->f_dma_attr->dma_attr_seg); amt = (end & ~fas->f_dma_attr->dma_attr_seg) - sp->cmd_cur_addr; if (amt == 0 || amt > fas->f_dma_attr->dma_attr_count_max) { fas_log(fas, CE_WARN, "illegal dma boundary? %x", amt); goto bad; } } #endif end = (uint64_t)sp->cmd_dmacookie.dmac_address + (uint64_t)sp->cmd_dmacookie.dmac_size - (uint64_t)sp->cmd_cur_addr; if (amt > end) { EPRINTF4("ovflow amt %x s.b. %" PRIx64 " curaddr %x count %x\n", amt, end, sp->cmd_cur_addr, sp->cmd_dmacount); amt = (uint32_t)end; } fas->f_lastcount = amt; EPRINTF4("%d.%d cmd 0x%x to xfer %x\n", Tgt(sp), Lun(sp), sp->cmd_pkt->pkt_cdbp[0], amt); phase = fas->f_stat & FAS_PHASE_MASK; if ((phase == FAS_PHASE_DATA_IN) && !sending) { FAS_DMA_WRITE(fas, amt, sp->cmd_cur_addr, CMD_TRAN_INFO|CMD_DMA); } else if ((phase == FAS_PHASE_DATA_OUT) && sending) { FAS_DMA_READ(fas, amt, sp->cmd_cur_addr, amt, CMD_TRAN_INFO|CMD_DMA); } else { fas_log(fas, CE_WARN, "unwanted data xfer direction for Target %d", Tgt(sp)); fas_set_pkt_reason(fas, sp, CMD_DMA_DERR, 0); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_ABORT2_END, "fas_handle_data_end (ACTION_ABORT_CURCMD2)"); return (ACTION_ABORT_CURCMD); } #ifdef FAS_TEST if (!sending && (fas_ptest_data_in & (1<f_reg; volatile struct dma *dmar = fas->f_dma; struct fas_cmd *sp = fas->f_current_sp; uint32_t xfer_amt; char was_sending; uchar_t stat, fifoamt, tgt; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_DONE_START, "fas_handle_data_done_start"); EPRINTF("fas_handle_data_done\n"); tgt = Tgt(sp); stat = fas->f_stat; was_sending = (sp->cmd_flags & CFLAG_DMASEND) ? 1 : 0; /* * Check for DMA errors (parity or memory fault) */ if ((fas->f_dma_csr = fas_dma_reg_read(fas, &dmar->dma_csr)) & DMA_ERRPEND) { /* * It would be desirable to set the ATN* line and attempt to * do the whole schmear of INITIATOR DETECTED ERROR here, * but that is too hard to do at present. */ fas_log(fas, CE_WARN, "Unrecoverable DMA error on dma %s", (was_sending) ? "send" : "receive"); fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_DONE_RESET_END, "fas_handle_data_done_end (ACTION_RESET)"); return (ACTION_RESET); } /* * Data Receive conditions: * * Check for parity errors. If we have a parity error upon * receive, the FAS chip has asserted ATN* for us already. */ if (!was_sending) { #ifdef FAS_TEST if (fas_ptest_data_in & (1< 1) { debug_enter("ptest_data_in"); } } #endif /* FAS_TEST */ if (stat & FAS_STAT_PERR) { fas_log(fas, CE_WARN, "SCSI bus DATA IN phase parity error"); fas->f_cur_msgout[0] = MSG_INITIATOR_ERROR; fas->f_omsglen = 1; sp->cmd_pkt->pkt_statistics |= STAT_PERR; sp->cmd_pkt->pkt_reason = CMD_TRAN_ERR; } } FAS_FLUSH_DMA(fas); /* * Check to make sure we're still connected to the target. * If the target dropped the bus, that is a fatal error. * We don't even attempt to count what we were transferring * here. Let fas_handle_unknown clean up for us. */ if (fas->f_intr != FAS_INT_BUS) { New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_DONE_PHASEMANAGE_END, "fas_handle_data_done_end (ACTION_PHASEMANAGE)"); return (ACTION_PHASEMANAGE); } /* * Figure out how far we got. * Latch up fifo amount first and double if wide has been enabled */ fifoamt = FIFO_CNT(fas); if (fas->f_wide_enabled & (1<f_lastcount; } else { GET_FAS_COUNT(fasreg, xfer_amt); xfer_amt = fas->f_lastcount - xfer_amt; } DPRINTF4("fifoamt=%x, xfer_amt=%x, lastcount=%x, stat=%x\n", fifoamt, xfer_amt, fas->f_lastcount, stat); /* * Unconditionally knock off by the amount left * in the fifo if we were sending out the SCSI bus. * * If we were receiving from the SCSI bus, believe * what the chip told us (either XZERO or by the * value calculated from the counter register). * The reason we don't look at the fifo for * incoming data is that in synchronous mode * the fifo may have further data bytes, and * for async mode we assume that all data in * the fifo will have been transferred before * the fas asserts an interrupt. */ if (was_sending) { xfer_amt -= fifoamt; } #ifdef FASDEBUG { int phase = stat & FAS_PHASE_MASK; fas->f_stat2 = fas_reg_read(fas, (uchar_t *)&fasreg->fas_stat2); if (((fas->f_stat & FAS_STAT_XZERO) == 0) && (phase != FAS_PHASE_DATA_IN) && (phase != FAS_PHASE_DATA_OUT) && (fas->f_stat2 & FAS_STAT2_ISHUTTLE)) { fas_log(fas, CE_WARN, "input shuttle not empty at end of data phase"); fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_DONE_RESET_END, "fas_handle_data_done_end (ACTION_RESET)"); return (ACTION_RESET); } } #endif /* FASDEBUG */ /* * If this was a synchronous transfer, flag it. * Also check for the errata condition of long * last REQ/ pulse for some synchronous targets */ if (fas->f_offset[tgt]) { /* * flag that a synchronous data xfer took place */ sp->cmd_pkt->pkt_statistics |= STAT_SYNC; if (was_sending) fas_reg_cmd_write(fas, CMD_FLUSH); } else { /* * If we aren't doing Synchronous Data Transfers, * definitely offload the fifo. */ fas_reg_cmd_write(fas, CMD_FLUSH); } /* * adjust pointers... */ DPRINTF3("before:cmd_data_count=%x, cmd_cur_addr=%x, xfer_amt=%x\n", sp->cmd_data_count, sp->cmd_cur_addr, xfer_amt); sp->cmd_data_count += xfer_amt; sp->cmd_cur_addr += xfer_amt; sp->cmd_pkt->pkt_state |= STATE_XFERRED_DATA; New_state(fas, ACTS_UNKNOWN); DPRINTF3("after:cmd_data_count=%x, cmd_cur_addr=%x, xfer_amt=%x\n", sp->cmd_data_count, sp->cmd_cur_addr, xfer_amt); stat &= FAS_PHASE_MASK; if (stat == FAS_PHASE_DATA_IN || stat == FAS_PHASE_DATA_OUT) { fas->f_state = ACTS_DATA; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_DONE_ACTION1_END, "fas_handle_data_done_end (action1)"); return (fas_handle_data_start(fas)); } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_DATA_DONE_ACTION2_END, "fas_handle_data_done_end (action2)"); return (fas_handle_unknown(fas)); } static char msginperr[] = "SCSI bus MESSAGE IN phase parity error"; static int fas_handle_c_cmplt(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; volatile struct fasreg *fasreg = fas->f_reg; uchar_t sts, msg, intr, perr; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_START, "fas_handle_c_cmplt_start"); EPRINTF("fas_handle_c_cmplt:\n"); /* * if target is fast, we can get cmd. completion by the time we get * here. Otherwise, we'll have to taken an interrupt. */ if (fas->f_laststate == ACTS_UNKNOWN) { if (INTPENDING(fas)) { fas->f_stat = fas_reg_read(fas, (uchar_t *)&fasreg->fas_stat); intr = fas_reg_read(fas, (uchar_t *)&fasreg->fas_intr); fas->f_intr = intr; if (fas->f_intr & (FAS_INT_RESET | FAS_INT_ILLEGAL)) { return (fas_illegal_cmd_or_bus_reset(fas)); } } else { /* * change f_laststate for the next time around */ fas->f_laststate = ACTS_C_CMPLT; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_RETURN1_END, "fas_handle_c_cmplt_end (ACTION_RETURN1)"); return (ACTION_RETURN); } } else { intr = fas->f_intr; } #ifdef FAS_TEST if (fas_ptest_status & (1<f_stat |= FAS_STAT_PERR; if (fas_test_stop > 1) { debug_enter("ptest_status"); } } else if ((fas_ptest_msgin & (1<f_stat |= FAS_STAT_PERR; if (fas_test_stop > 1) { debug_enter("ptest_completion"); } } #endif /* FAS_TEST */ if (intr == FAS_INT_DISCON) { New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_ACTION1_END, "fas_handle_c_cmplt_end (action1)"); return (fas_handle_unknown(fas)); } if ((perr = (fas->f_stat & FAS_STAT_PERR)) != 0) { fas_assert_atn(fas); sp->cmd_pkt->pkt_statistics |= STAT_PERR; } /* * do a msg accept now and read the fifo data */ if (intr & FAS_INT_FCMP) { /* * The FAS manuals state that this sequence completes * with a BUS SERVICE interrupt if just the status * byte was received, else a FUNCTION COMPLETE interrupt * if both status and a message was received. * * if we give the MSG_ACT before reading the msg byte * we get the status byte again and if the status is zero * then we won't detect a failure */ *(sp->cmd_pkt->pkt_scbp) = sts = fas_reg_read(fas, (uchar_t *)&fasreg->fas_fifo_data); fas->f_last_msgin = fas->f_imsgarea[0] = msg = fas_reg_read(fas, (uchar_t *)&fasreg->fas_fifo_data); fas_reg_cmd_write(fas, CMD_MSG_ACPT); sp->cmd_pkt->pkt_state |= STATE_GOT_STATUS; /* * The manuals also state that ATN* is asserted if * bad parity is detected. * * The one case that we cannot handle is where we detect * bad parity for the status byte, but the target refuses * to go to MESSAGE OUT phase right away. This means that * if that happens, we will misconstrue the parity error * to be for the completion message, not the status byte. */ if (perr) { fas_log(fas, CE_WARN, msginperr); sp->cmd_pkt->pkt_statistics |= STAT_PERR; fas->f_cur_msgout[0] = MSG_MSG_PARITY; fas->f_omsglen = 1; New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_ACTION5_END, "fas_handle_c_cmplt_end (action5)"); return (ACTION_RETURN); } } else if (intr == FAS_INT_BUS) { /* * We only got the status byte. */ sts = fas_reg_read(fas, (uchar_t *)&fasreg->fas_fifo_data); sp->cmd_pkt->pkt_state |= STATE_GOT_STATUS; *(sp->cmd_pkt->pkt_scbp) = sts; msg = INVALID_MSG; IPRINTF1("fas_handle_cmd_cmplt: sts=%x, no msg byte\n", sts); if (perr) { /* * If we get a parity error on a status byte * assume that it was a CHECK CONDITION */ sts = STATUS_CHECK; fas_log(fas, CE_WARN, "SCSI bus STATUS phase parity error"); fas->f_cur_msgout[0] = MSG_INITIATOR_ERROR; fas->f_omsglen = 1; New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_ACTION5_END, "fas_handle_c_cmplt_end (action5)"); return (fas_handle_unknown(fas)); } } else { msg = sts = INVALID_MSG; IPRINTF("fas_handle_cmd_cmplt: unexpected intr\n"); New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_ACTION2_END, "fas_handle_c_cmplt_end (action2)"); return (fas_handle_unknown(fas)); } EPRINTF2("fas_handle_c_cmplt: status=%x, msg=%x\n", sts, msg); EPRINTF1("Completion Message=%s\n", scsi_mname(msg)); if (msg == MSG_COMMAND_COMPLETE) { /* * Actually, if the message was a 'linked command * complete' message, the target isn't going to be * clearing the bus. */ New_state(fas, ACTS_CLEARING); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_ACTION4_END, "fas_handle_c_cmplt_end (action4)"); return (fas_handle_clearing(fas)); } else { fas->f_imsglen = 1; fas->f_imsgindex = 1; New_state(fas, ACTS_MSG_IN_DONE); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_C_CMPLT_ACTION3_END, "fas_handle_c_cmplt_end (action3)"); return (fas_handle_msg_in_done(fas)); } } /* * prepare for accepting a message byte from the fifo */ static int fas_handle_msg_in_start(struct fas *fas) { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_START, "fas_handle_msg_in_start"); EPRINTF("fas_handle_msg_in_start\n"); /* * Pick up a message byte. * Clear the FIFO so we * don't get confused. */ if (!FIFO_EMPTY(fas)) { fas_reg_cmd_write(fas, CMD_FLUSH); } fas_reg_cmd_write(fas, CMD_TRAN_INFO); fas->f_imsglen = 1; fas->f_imsgindex = 0; New_state(fas, ACTS_MSG_IN_DONE); /* * give a little extra time by returning to phasemanage */ TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_END, "fas_handle_msg_in_end (ACTION_PHASEMANAGE)"); return (ACTION_PHASEMANAGE); } /* * We come here after issuing a MSG_ACCEPT * command and are expecting more message bytes. * The FAS should be asserting a BUS SERVICE * interrupt status, but may have asserted * a different interrupt in the case that * the target disconnected and dropped BSY*. * * In the case that we are eating up message * bytes (and throwing them away unread) because * we have ATN* asserted (we are trying to send * a message), we do not consider it an error * if the phase has changed out of MESSAGE IN. */ static int fas_handle_more_msgin(struct fas *fas) { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MORE_MSGIN_START, "fas_handle_more_msgin_start"); EPRINTF("fas_handle_more_msgin\n"); if (fas->f_intr & FAS_INT_BUS) { if ((fas->f_stat & FAS_PHASE_MASK) == FAS_PHASE_MSG_IN) { /* * Fetch another byte of a message in. */ fas_reg_cmd_write(fas, CMD_TRAN_INFO); New_state(fas, ACTS_MSG_IN_DONE); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MORE_MSGIN_RETURN1_END, "fas_handle_more_msgin_end (ACTION_RETURN)"); return (ACTION_RETURN); } /* * If we were gobbling up a message and we have * changed phases, handle this silently, else * complain. In either case, we return to let * fas_phasemanage() handle things. * * If it wasn't a BUS SERVICE interrupt, * let fas_phasemanage() find out if the * chip disconnected. */ if (fas->f_imsglen != 0) { fas_log(fas, CE_WARN, "Premature end of extended message"); } } New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MORE_MSGIN_RETURN2_END, "fas_handle_more_msgin_end (action)"); return (fas_handle_unknown(fas)); } static int fas_handle_msg_in_done(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; volatile struct fasreg *fasreg = fas->f_reg; int sndmsg = 0; uchar_t msgin; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_DONE_START, "fas_handle_msg_in_done_start"); EPRINTF("fas_handle_msg_in_done:\n"); if (fas->f_laststate == ACTS_MSG_IN) { if (INTPENDING(fas)) { fas->f_stat = fas_reg_read(fas, (uchar_t *)&fasreg->fas_stat); fas->f_stat2 = fas_reg_read(fas, (uchar_t *)&fasreg->fas_stat2); fas_read_fifo(fas); fas->f_intr = fas_reg_read(fas, (uchar_t *)&fasreg->fas_intr); if (fas->f_intr & (FAS_INT_RESET | FAS_INT_ILLEGAL)) { return (fas_illegal_cmd_or_bus_reset(fas)); } } else { /* * change f_laststate for the next time around */ fas->f_laststate = ACTS_MSG_IN_DONE; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_DONE_RETURN1_END, "fas_handle_msg_in_done_end (ACTION_RETURN1)"); return (ACTION_RETURN); } } /* * the most common case is a disconnect message. we do * a fast path for this condition and if it fails then * we go for the detailed error handling */ #ifndef FAS_TEST if (((fas->f_laststate == ACTS_MSG_IN) || (fas->f_laststate == ACTS_MSG_IN_DONE)) && ((fas->f_intr & FAS_INT_DISCON) == 0) && ((fas->f_stat & FAS_STAT_PERR) == 0) && ((sp->cmd_pkt_flags & FLAG_NODISCON) == 0)) { if ((fas->f_fifolen == 1) && (fas->f_imsglen == 1) && (fas->f_fifo[0] == MSG_DISCONNECT)) { fas_reg_cmd_write(fas, CMD_MSG_ACPT); fas->f_imsgarea[fas->f_imsgindex++] = fas->f_fifo[0]; fas->f_last_msgin = MSG_DISCONNECT; New_state(fas, ACTS_CLEARING); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_DONE_ACTION_END, "fas_handle_msg_in_done_end (action)"); return (fas_handle_clearing(fas)); } } #endif /* not FAS_TEST */ /* * We can be called here for both the case where * we had requested the FAS chip to fetch a message * byte from the target (at the target's request). * We can also be called in the case where we had * been using the CMD_COMP_SEQ command to pick up * both a status byte and a completion message from * a target, but where the message wasn't one of * COMMAND COMPLETE, LINKED COMMAND COMPLETE, or * LINKED COMMAND COMPLETE (with flag). This is a * legal (albeit extremely unusual) SCSI bus trans- * -ition, so we have to handle it. */ if (fas->f_laststate != ACTS_C_CMPLT) { #ifdef FAS_TEST reloop: #endif /* FAS_TEST */ if (fas->f_intr & FAS_INT_DISCON) { fas_log(fas, CE_WARN, "premature end of input message"); New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_DONE_PHASEMANAGE_END, "fas_handle_msg_in_done_end (ACTION_PHASEMANAGE)"); return (ACTION_PHASEMANAGE); } /* * Note that if f_imsglen is zero, then we are skipping * input message bytes, so there is no reason to look for * parity errors. */ if (fas->f_imsglen != 0 && (fas->f_stat & FAS_STAT_PERR)) { fas_log(fas, CE_WARN, msginperr); sndmsg = MSG_MSG_PARITY; sp->cmd_pkt->pkt_statistics |= STAT_PERR; fas_reg_cmd_write(fas, CMD_FLUSH); } else if ((msgin = fas->f_fifolen) != 1) { /* * If we have got more than one or 0 bytes in the fifo, * that is a gross screwup, and we should let the * target know that we have completely fouled up. */ fas_printf(fas, "fifocount=%x", msgin); fas_printstate(fas, "input message botch"); sndmsg = MSG_INITIATOR_ERROR; fas_reg_cmd_write(fas, CMD_FLUSH); fas_log(fas, CE_WARN, "input message botch"); } else if (fas->f_imsglen == 0) { /* * If we are in the middle of gobbling up and throwing * away a message (due to a previous message input * error), drive on. */ msgin = fas_reg_read(fas, (uchar_t *)&fasreg->fas_fifo_data); New_state(fas, ACTS_MSG_IN_MORE); } else { msgin = fas->f_fifo[0]; fas->f_imsgarea[fas->f_imsgindex++] = msgin; } } else { /* * In this case, we have been called (from * fas_handle_c_cmplt()) with the message * already stored in the message array. */ msgin = fas->f_imsgarea[0]; } /* * Process this message byte (but not if we are * going to be trying to send back some error * anyway) */ if (sndmsg == 0 && fas->f_imsglen != 0) { if (fas->f_imsgindex < fas->f_imsglen) { EPRINTF2("message byte %d: 0x%x\n", fas->f_imsgindex-1, fas->f_imsgarea[fas->f_imsgindex-1]); New_state(fas, ACTS_MSG_IN_MORE); } else if (fas->f_imsglen == 1) { #ifdef FAS_TEST if ((fas_ptest_msgin & (1<f_stat |= FAS_STAT_PERR; fas->f_imsgindex -= 1; if (fas_test_stop > 1) { debug_enter("ptest msgin"); } goto reloop; } #endif /* FAS_TEST */ sndmsg = fas_onebyte_msg(fas); } else if (fas->f_imsglen == 2) { #ifdef FAS_TEST if (fas_ptest_emsgin & (1<f_stat |= FAS_STAT_PERR; fas->f_imsgindex -= 1; if (fas_test_stop > 1) { debug_enter("ptest emsgin"); } goto reloop; } #endif /* FAS_TEST */ if (fas->f_imsgarea[0] == MSG_EXTENDED) { static char *tool = "Extended message 0x%x is too long"; /* * Is the incoming message too long * to be stored in our local array? */ if ((int)(msgin+2) > IMSGSIZE) { fas_log(fas, CE_WARN, tool, fas->f_imsgarea[0]); sndmsg = MSG_REJECT; } else { fas->f_imsglen = msgin + 2; New_state(fas, ACTS_MSG_IN_MORE); } } else { sndmsg = fas_twobyte_msg(fas); } } else { sndmsg = fas_multibyte_msg(fas); } } if (sndmsg < 0) { /* * If sndmsg is less than zero, one of the subsidiary * routines needs to return some other state than * ACTION_RETURN. */ TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_DONE_SNDMSG_END, "fas_handle_msg_in_done_end (-sndmsg)"); return (-sndmsg); } else if (sndmsg > 0) { if (IS_1BYTE_MSG(sndmsg)) { fas->f_omsglen = 1; } fas->f_cur_msgout[0] = (uchar_t)sndmsg; /* * The target is not guaranteed to go to message out * phase, period. Moreover, until the entire incoming * message is transferred, the target may (and likely * will) continue to transfer message bytes (which * we will have to ignore). * * In order to do this, we'll go to 'infinite' * message in handling by setting the current input * message length to a sentinel of zero. * * This works regardless of the message we are trying * to send out. At the point in time which we want * to send a message in response to an incoming message * we do not care any more about the incoming message. * * If we are sending a message in response to detecting * a parity error on input, the FAS chip has already * set ATN* for us, but it doesn't hurt to set it here * again anyhow. */ fas_assert_atn(fas); New_state(fas, ACTS_MSG_IN_MORE); fas->f_imsglen = 0; } fas_reg_cmd_write(fas, CMD_FLUSH); fas_reg_cmd_write(fas, CMD_MSG_ACPT); if ((fas->f_laststate == ACTS_MSG_IN_DONE) && (fas->f_state == ACTS_CLEARING)) { TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_DONE_ACTION_END, "fas_handle_msg_in_done_end (action)"); return (fas_handle_clearing(fas)); } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_IN_DONE_RETURN2_END, "fas_handle_msg_in_done_end (ACTION_RETURN2)"); return (ACTION_RETURN); } static int fas_onebyte_msg(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; int msgout = 0; uchar_t msgin = fas->f_last_msgin = fas->f_imsgarea[0]; int tgt = Tgt(sp); EPRINTF("fas_onebyte_msg\n"); if (msgin & MSG_IDENTIFY) { /* * How did we get here? We should only see identify * messages on a reconnection, but we'll handle this * fine here (just in case we get this) as long as * we believe that this is a valid identify message. * * For this to be a valid incoming message, * bits 6-4 must must be zero. Also, the * bit that says that I'm an initiator and * can support disconnection cannot possibly * be set here. */ char garbled = ((msgin & (BAD_IDENTIFY|INI_CAN_DISCON)) != 0); fas_log(fas, CE_WARN, "%s message 0x%x from Target %d", garbled ? "Garbled" : "Identify", msgin, tgt); if (garbled) { /* * If it's a garbled message, * try and tell the target... */ msgout = MSG_INITIATOR_ERROR; } else { New_state(fas, ACTS_UNKNOWN); } return (msgout); } else if (IS_2BYTE_MSG(msgin) || IS_EXTENDED_MSG(msgin)) { fas->f_imsglen = 2; New_state(fas, ACTS_MSG_IN_MORE); return (0); } New_state(fas, ACTS_UNKNOWN); switch (msgin) { case MSG_DISCONNECT: /* * If we 'cannot' disconnect- reject this message. * Note that we only key off of the pkt_flags here- * the FLAG_NODISCON was set in fas_accept_pkt() if * no disconnect was enabled in scsi_options */ if (sp->cmd_pkt_flags & FLAG_NODISCON) { msgout = MSG_REJECT; break; } /* FALLTHROUGH */ case MSG_COMMAND_COMPLETE: fas->f_state = ACTS_CLEARING; break; case MSG_NOP: break; /* XXX Make it a MSG_REJECT handler */ case MSG_REJECT: { uchar_t reason = 0; uchar_t lastmsg = fas->f_last_msgout; /* * The target is rejecting the last message we sent. * * If the last message we attempted to send out was an * extended message, we were trying to negotiate sync * xfers- and we're okay. * * Otherwise, a target has rejected a message that * it should have handled. We will abort the operation * in progress and set the pkt_reason value here to * show why we have completed. The process of aborting * may be via a message or may be via a bus reset (as * a last resort). */ msgout = (TAGGED(tgt)? MSG_ABORT_TAG : MSG_ABORT); switch (lastmsg) { case MSG_EXTENDED: if (fas->f_wdtr_sent) { /* * Disable wide, Target rejected * out WDTR message */ fas_set_wide_conf3(fas, tgt, 0); fas->f_nowide |= (1<f_wdtr_sent = 0; /* * we still want to negotiate sync */ if ((fas->f_nosync & (1<f_sdtr_sent) { fas_reg_cmd_write(fas, CMD_CLR_ATN); fas_revert_to_async(fas, tgt); fas->f_nosync |= (1<f_sdtr_sent = 0; } msgout = 0; break; case MSG_NOP: reason = CMD_NOP_FAIL; break; case MSG_INITIATOR_ERROR: reason = CMD_IDE_FAIL; break; case MSG_MSG_PARITY: reason = CMD_PER_FAIL; break; case MSG_REJECT: reason = CMD_REJECT_FAIL; break; /* XXX - abort not good, queue full handling or drain (?) */ case MSG_SIMPLE_QTAG: case MSG_ORDERED_QTAG: case MSG_HEAD_QTAG: msgout = MSG_ABORT; reason = CMD_TAG_REJECT; break; case MSG_DEVICE_RESET: reason = CMD_BDR_FAIL; msgout = -ACTION_ABORT_CURCMD; break; case MSG_ABORT: case MSG_ABORT_TAG: /* * If an RESET/ABORT OPERATION message is rejected * it is time to yank the chain on the bus... */ reason = CMD_ABORT_FAIL; msgout = -ACTION_ABORT_CURCMD; break; default: if (IS_IDENTIFY_MSG(lastmsg)) { if (TAGGED(tgt)) { /* * this often happens when the * target rejected our tag */ reason = CMD_TAG_REJECT; } else { reason = CMD_ID_FAIL; } } else { reason = CMD_TRAN_ERR; msgout = -ACTION_ABORT_CURCMD; } break; } if (msgout) { fas_log(fas, CE_WARN, "Target %d rejects our message '%s'", tgt, scsi_mname(lastmsg)); fas_set_pkt_reason(fas, sp, reason, 0); } break; } case MSG_RESTORE_PTRS: sp->cmd_cdbp = sp->cmd_pkt->pkt_cdbp; if (sp->cmd_data_count != sp->cmd_saved_data_count) { if (fas_restore_pointers(fas, sp)) { msgout = -ACTION_ABORT_CURCMD; } else if ((sp->cmd_pkt->pkt_reason & CMD_TRAN_ERR) && (sp->cmd_pkt->pkt_statistics & STAT_PERR) && (sp->cmd_cur_win == 0) && (sp->cmd_data_count == 0)) { sp->cmd_pkt->pkt_reason &= ~CMD_TRAN_ERR; } } break; case MSG_SAVE_DATA_PTR: sp->cmd_saved_data_count = sp->cmd_data_count; sp->cmd_saved_win = sp->cmd_cur_win; sp->cmd_saved_cur_addr = sp->cmd_cur_addr; break; /* These don't make sense for us, and */ /* will be rejected */ /* case MSG_INITIATOR_ERROR */ /* case MSG_ABORT */ /* case MSG_MSG_PARITY */ /* case MSG_DEVICE_RESET */ default: msgout = MSG_REJECT; fas_log(fas, CE_WARN, "Rejecting message '%s' from Target %d", scsi_mname(msgin), tgt); break; } EPRINTF1("Message in: %s\n", scsi_mname(msgin)); return (msgout); } /* * phase handlers that are rarely used */ static int fas_handle_cmd_start(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; volatile uchar_t *tp = fas->f_cmdarea; int i; int amt = sp->cmd_cdblen; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CMD_START_START, "fas_handle_cmd_start_start"); EPRINTF("fas_handle_cmd: send cmd\n"); for (i = 0; i < amt; i++) { *tp++ = sp->cmd_cdbp[i]; } fas_reg_cmd_write(fas, CMD_FLUSH); FAS_DMA_READ(fas, amt, fas->f_dmacookie.dmac_address, amt, CMD_TRAN_INFO|CMD_DMA); fas->f_lastcount = amt; New_state(fas, ACTS_CMD_DONE); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CMD_START_END, "fas_handle_cmd_start_end"); return (ACTION_RETURN); } static int fas_handle_cmd_done(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; uchar_t intr = fas->f_intr; volatile struct dma *dmar = fas->f_dma; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CMD_DONE_START, "fas_handle_cmd_done_start"); EPRINTF("fas_handle_cmd_done\n"); /* * We should have gotten a BUS SERVICE interrupt. * If it isn't that, and it isn't a DISCONNECT * interrupt, we have a "cannot happen" situation. */ if ((intr & FAS_INT_BUS) == 0) { if ((intr & FAS_INT_DISCON) == 0) { fas_printstate(fas, "cmd transmission error"); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CMD_DONE_ABORT1_END, "fas_handle_cmd_done_end (abort1)"); return (ACTION_ABORT_CURCMD); } } else { sp->cmd_pkt->pkt_state |= STATE_SENT_CMD; } fas->f_dma_csr = fas_dma_reg_read(fas, &dmar->dma_csr); FAS_FLUSH_DMA(fas); New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_CMD_DONE_END, "fas_handle_cmd_done_end"); return (fas_handle_unknown(fas)); } /* * Begin to send a message out */ static int fas_handle_msg_out_start(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; uchar_t *msgout = fas->f_cur_msgout; uchar_t amt = fas->f_omsglen; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_OUT_START, "fas_handle_msg_out_start"); EPRINTF("fas_handle_msg_out_start\n"); /* * Check to make *sure* that we are really * in MESSAGE OUT phase. If the last state * was ACTS_MSG_OUT_DONE, then we are trying * to resend a message that the target stated * had a parity error in it. * * If this is the case, and mark completion reason as CMD_NOMSGOUT. * XXX: Right now, we just *drive* on. Should we abort the command? */ if ((fas->f_stat & FAS_PHASE_MASK) != FAS_PHASE_MSG_OUT && fas->f_laststate == ACTS_MSG_OUT_DONE) { fas_log(fas, CE_WARN, "Target %d refused message resend", Tgt(sp)); fas_set_pkt_reason(fas, sp, CMD_NOMSGOUT, 0); New_state(fas, ACTS_UNKNOWN); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_OUT_PHASEMANAGE_END, "fas_handle_msg_out_end (ACTION_PHASEMANAGE)"); return (ACTION_PHASEMANAGE); } /* * Clean the fifo. */ fas_reg_cmd_write(fas, CMD_FLUSH); if (amt == 0) { /* * no msg to send */ *msgout = MSG_NOP; amt = fas->f_omsglen = 1; } /* * If msg only 1 byte, just dump it in the fifo and go. For * multi-byte msgs, dma them to save time. If we have no * msg to send and we're in msg out phase, send a NOP. */ fas->f_last_msgout = *msgout; /* * There is a bug in the fas366 that occasionaly * deasserts the ATN signal prematurely when we send * the sync/wide negotiation bytes out using DMA. The * workaround here is to send the negotiation bytes out * using PIO */ fas_write_fifo(fas, msgout, fas->f_omsglen, 1); fas_reg_cmd_write(fas, CMD_TRAN_INFO); EPRINTF2("amt=%x, last_msgout=%x\n", amt, fas->f_last_msgout); New_state(fas, ACTS_MSG_OUT_DONE); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_OUT_END, "fas_handle_msg_out_end"); return (ACTION_RETURN); } static int fas_handle_msg_out_done(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; uchar_t msgout, phase; int target = Tgt(sp); int amt = fas->f_omsglen; int action; TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_OUT_DONE_START, "fas_handle_msg_out_done_start"); msgout = fas->f_cur_msgout[0]; if ((msgout == MSG_HEAD_QTAG) || (msgout == MSG_SIMPLE_QTAG)) { msgout = fas->f_cur_msgout[2]; } EPRINTF4("msgout: %x %x %x, last_msgout=%x\n", fas->f_cur_msgout[0], fas->f_cur_msgout[1], fas->f_cur_msgout[2], fas->f_last_msgout); EPRINTF1("fas_handle_msgout_done: msgout=%x\n", msgout); /* * flush fifo, just in case some bytes were not sent */ fas_reg_cmd_write(fas, CMD_FLUSH); /* * If the FAS disconnected, then the message we sent caused * the target to decide to drop BSY* and clear the bus. */ if (fas->f_intr == FAS_INT_DISCON) { if (msgout == MSG_DEVICE_RESET || msgout == MSG_ABORT || msgout == MSG_ABORT_TAG) { /* * If we sent a device reset msg, then we need to do * a synch negotiate again unless we have already * inhibited synch. */ if (msgout == MSG_ABORT || msgout == MSG_ABORT_TAG) { fas->f_abort_msg_sent++; if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { fas_set_pkt_reason(fas, sp, CMD_ABORTED, STAT_ABORTED); } } else if (msgout == MSG_DEVICE_RESET) { fas->f_reset_msg_sent++; if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { fas_set_pkt_reason(fas, sp, CMD_RESET, STAT_DEV_RESET); } fas_force_renegotiation(fas, Tgt(sp)); } EPRINTF2("Successful %s message to target %d\n", scsi_mname(msgout), target); if (sp->cmd_flags & CFLAG_CMDPROXY) { sp->cmd_cdb[FAS_PROXY_RESULT] = TRUE; } TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_OUT_DONE_FINISH_END, "fas_handle_msg_out_done_end (ACTION_FINISH)"); return (ACTION_FINISH); } /* * If the target dropped busy on any other message, it * wasn't expected. We will let the code in fas_phasemanage() * handle this unexpected bus free event. */ goto out; } /* * What phase have we transitioned to? */ phase = fas->f_stat & FAS_PHASE_MASK; /* * If we finish sending a message out, and we are * still in message out phase, then the target has * detected one or more parity errors in the message * we just sent and it is asking us to resend the * previous message. */ if ((fas->f_intr & FAS_INT_BUS) && phase == FAS_PHASE_MSG_OUT) { /* * As per SCSI-2 specification, if the message to * be re-sent is greater than one byte, then we * have to set ATN*. */ if (amt > 1) { fas_assert_atn(fas); } fas_log(fas, CE_WARN, "SCSI bus MESSAGE OUT phase parity error"); sp->cmd_pkt->pkt_statistics |= STAT_PERR; New_state(fas, ACTS_MSG_OUT); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_OUT_DONE_PHASEMANAGE_END, "fas_handle_msg_out_done_end (ACTION_PHASEMANAGE)"); return (ACTION_PHASEMANAGE); } out: fas->f_last_msgout = msgout; fas->f_omsglen = 0; New_state(fas, ACTS_UNKNOWN); action = fas_handle_unknown(fas); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_HANDLE_MSG_OUT_DONE_END, "fas_handle_msg_out_done_end"); return (action); } static int fas_twobyte_msg(struct fas *fas) { struct fas_cmd *sp = fas->f_current_sp; if ((fas->f_imsgarea[0] == MSG_IGNORE_WIDE_RESID) && (fas->f_imsgarea[1] == 1)) { int xfer_amt; /* * Knock off one byte if there * is a last transfer and is even number of bytes */ xfer_amt = sp->cmd_data_count - sp->cmd_saved_data_count; if (xfer_amt && (!(xfer_amt & 1))) { ASSERT(sp->cmd_data_count > 0); sp->cmd_data_count--; sp->cmd_cur_addr--; } IPRINTF1("ignore wide resid %d\n", fas->f_imsgarea[1]); New_state(fas, ACTS_UNKNOWN); return (0); } fas_log(fas, CE_WARN, "Two byte message '%s' 0x%x rejected", scsi_mname(fas->f_imsgarea[0]), fas->f_imsgarea[1]); return (MSG_REJECT); } /* * handle receiving extended messages */ static int fas_multibyte_msg(struct fas *fas) { #ifdef FASDEBUG static char *mbs = "Target %d now Synchronous at %d.%d MB/s max transmit rate\n"; static char *mbs1 = "Target %d now Synchronous at %d.0%d MB/s max transmit rate\n"; static char *mbs2 = "Target %d now Synchronous at %d.00%d MB/s max transmit rate\n"; #endif struct fas_cmd *sp = fas->f_current_sp; volatile struct fasreg *fasreg = fas->f_reg; uchar_t emsg = fas->f_imsgarea[2]; int tgt = Tgt(sp); int msgout = 0; EPRINTF("fas_multibyte_msg:\n"); if (emsg == MSG_SYNCHRONOUS) { uint_t period, offset, regval; uint_t minsync, maxsync, clockval; uint_t xfer_freq, xfer_div, xfer_mod, xfer_rate; period = fas->f_imsgarea[3] & 0xff; offset = fas->f_imsgarea[4] & 0xff; minsync = MIN_SYNC_PERIOD(fas); maxsync = MAX_SYNC_PERIOD(fas); DPRINTF5("sync msg received: %x %x %x %x %x\n", fas->f_imsgarea[0], fas->f_imsgarea[1], fas->f_imsgarea[2], fas->f_imsgarea[3], fas->f_imsgarea[4]); DPRINTF3("received period %d offset %d from tgt %d\n", period, offset, tgt); DPRINTF3("calculated minsync %d, maxsync %d for tgt %d\n", minsync, maxsync, tgt); DPRINTF2("sync period %d, neg period %d\n", fas->f_sync_period[tgt], fas->f_neg_period[tgt]); if ((++(fas->f_sdtr_sent)) & 1) { /* * In cases where the target negotiates synchronous * mode before we do, and we either have sync mode * disabled, or this target is known to be a weak * signal target, we send back a message indicating * a desire to stay in asynchronous mode (the SCSI-2 * spec states that if we have synchronous capability * then we cannot reject a SYNCHRONOUS DATA TRANSFER * REQUEST message). */ IPRINTF1("SYNC negotiation initiated by target %d\n", tgt); msgout = MSG_EXTENDED; period = period ? max(period, MIN_SYNC_PERIOD(fas)) : 0; if (fas->f_backoff & (1<f_neg_period[tgt]) : 0; } offset = min(offset, fas_default_offset); } xfer_freq = regval = 0; /* * If the target's offset is bigger than ours, * the target has violated the scsi protocol. */ if (offset > fas_default_offset) { period = offset = 0; msgout = MSG_REJECT; } if (offset && (period > maxsync)) { /* * We cannot transmit data in synchronous * mode this slow, so convert to asynchronous * mode. */ msgout = MSG_EXTENDED; period = offset = 0; } else if (offset && (period < minsync)) { /* * If the target's period is less than ours, * the target has violated the scsi protocol. */ period = offset = 0; msgout = MSG_REJECT; } else if (offset) { /* * Conversion method for received PERIOD value * to the number of input clock ticks to the FAS. * * We adjust the input period value such that * we always will transmit data *not* faster * than the period value received. */ clockval = fas->f_clock_cycle / 1000; regval = (((period << 2) + clockval - 1) / clockval); /* * correction if xfer rate <= 5MB/sec * XXX do we need this? */ if (regval && (period >= FASTSCSI_THRESHOLD)) { regval--; } } fas->f_offset[tgt] = offset; fas->f_neg_period[tgt] = period; /* * Is is now safe to produce a responce to a target * initiated sdtr. period and offset have been checked. */ if (msgout == MSG_EXTENDED) { fas_make_sdtr(fas, 0, tgt); period = fas->f_neg_period[tgt]; offset = (fas->f_offset[tgt] & 0xf); } if (offset) { fas->f_sync_period[tgt] = regval & SYNC_PERIOD_MASK; fas_reg_write(fas, (uchar_t *)&fasreg->fas_sync_period, fas->f_sync_period[tgt]); fas->f_offset[tgt] = offset | fas->f_req_ack_delay; fas_reg_write(fas, (uchar_t *)&fasreg->fas_sync_offset, fas->f_offset[tgt]); /* * if transferring > 5 MB/sec then enable * fastscsi in conf3 */ if (period < FASTSCSI_THRESHOLD) { fas->f_fasconf3[tgt] |= FAS_CONF3_FASTSCSI; } else { fas->f_fasconf3[tgt] &= ~FAS_CONF3_FASTSCSI; } fas_reg_write(fas, (uchar_t *)&fasreg->fas_conf3, fas->f_fasconf3[tgt]); DPRINTF4("period %d (%d), offset %d to tgt %d\n", period, fas->f_sync_period[tgt] & SYNC_PERIOD_MASK, fas->f_offset[tgt] & 0xf, tgt); DPRINTF1("req/ack delay = %x\n", fas->f_req_ack_delay); DPRINTF1("conf3 = %x\n", fas->f_fasconf3[tgt]); #ifdef FASDEBUG /* * Convert input clock cycle per * byte to nanoseconds per byte. * (ns/b), and convert that to * k-bytes/second. */ xfer_freq = FAS_SYNC_KBPS((regval * fas->f_clock_cycle) / 1000); xfer_rate = ((fas->f_nowide & (1< 99) { IPRINTF3(mbs, tgt, xfer_div, xfer_mod); } else if (xfer_mod > 9) { IPRINTF3(mbs1, tgt, xfer_div, xfer_mod); } else { IPRINTF3(mbs2, tgt, xfer_div, xfer_mod); } #endif fas->f_sync_enabled |= (1<f_nosync |= (1<f_props_update |= (1<f_imsgarea[3] & 0xff; DPRINTF4("wide msg received: %x %x %x %x\n", fas->f_imsgarea[0], fas->f_imsgarea[1], fas->f_imsgarea[2], fas->f_imsgarea[3]); /* always renegotiate sync after wide */ msgout = MSG_EXTENDED; if ((++(fas->f_wdtr_sent)) & 1) { IPRINTF1("Wide negotiation initiated by target %d\n", tgt); /* * allow wide neg even if the target driver hasn't * enabled wide yet. */ fas->f_nowide &= ~(1<f_sync_known &= ~(1<f_wdtr_sent = 0; if ((fas->f_nosync & (1<f_props_update |= (1<f_reg; fas->f_sync_period[tgt] = 0; fas_reg_write(fas, (uchar_t *)&fasreg->fas_sync_period, 0); fas->f_offset[tgt] = 0; fas_reg_write(fas, (uchar_t *)&fasreg->fas_sync_offset, 0); fas->f_fasconf3[tgt] &= ~FAS_CONF3_FASTSCSI; fas_reg_write(fas, &fasreg->fas_conf3, fas->f_fasconf3[tgt]); fas->f_sync_enabled &= ~(1<cmd_data_count != sp->cmd_saved_data_count) { sp->cmd_data_count = sp->cmd_saved_data_count; sp->cmd_cur_addr = sp->cmd_saved_cur_addr; if (sp->cmd_cur_win != sp->cmd_saved_win) { sp->cmd_cur_win = sp->cmd_saved_win; if (fas_set_new_window(fas, sp)) { return (-1); } } DPRINTF1("curaddr=%x\n", sp->cmd_cur_addr); } return (0); } static int fas_set_new_window(struct fas *fas, struct fas_cmd *sp) { off_t offset; size_t len; uint_t count; if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_cur_win, &offset, &len, &sp->cmd_dmacookie, &count) != DDI_SUCCESS) { return (-1); } DPRINTF4("new window %x: off=%lx, len=%lx, count=%x\n", sp->cmd_cur_win, offset, len, count); ASSERT(count == 1); return (0); } static int fas_next_window(struct fas *fas, struct fas_cmd *sp, uint64_t end) { /* are there more windows? */ if (sp->cmd_nwin == 0) { uint_t nwin = 0; (void) ddi_dma_numwin(sp->cmd_dmahandle, &nwin); sp->cmd_nwin = (uchar_t)nwin; } DPRINTF5( "cmd_data_count=%x, dmacount=%x, curaddr=%x, end=%lx, nwin=%x\n", sp->cmd_data_count, sp->cmd_dmacount, sp->cmd_cur_addr, end, sp->cmd_nwin); if (sp->cmd_cur_win < sp->cmd_nwin) { sp->cmd_cur_win++; if (fas_set_new_window(fas, sp)) { fas_printstate(fas, "cannot set new window"); sp->cmd_cur_win--; return (-1); } /* * if there are no more windows, we have a data overrun condition */ } else { int slot = sp->cmd_slot; fas_printstate(fas, "data transfer overrun"); fas_set_pkt_reason(fas, sp, CMD_DATA_OVR, 0); /* * if we get data transfer overruns, assume we have * a weak scsi bus. Note that this won't catch consistent * underruns or other noise related syndromes. */ fas_sync_wide_backoff(fas, sp, slot); return (-1); } sp->cmd_cur_addr = sp->cmd_dmacookie.dmac_address; DPRINTF1("cur_addr=%x\n", sp->cmd_cur_addr); return (0); } /* * dma error handler */ static int fas_check_dma_error(struct fas *fas) { /* * was there a dma error that caused fas_intr_svc() to be called? */ if (fas->f_dma->dma_csr & DMA_ERRPEND) { /* * It would be desirable to set the ATN* line and attempt to * do the whole schmear of INITIATOR DETECTED ERROR here, * but that is too hard to do at present. */ fas_log(fas, CE_WARN, "Unrecoverable DMA error"); fas_printstate(fas, "dma error"); fas_set_pkt_reason(fas, fas->f_current_sp, CMD_TRAN_ERR, 0); return (-1); } return (0); } /* * check for gross error or spurious interrupt */ static int fas_handle_gross_err(struct fas *fas) { volatile struct fasreg *fasreg = fas->f_reg; fas_log(fas, CE_WARN, "gross error in fas status (%x)", fas->f_stat); IPRINTF5("fas_cmd=%x, stat=%x, intr=%x, step=%x, fifoflag=%x\n", fasreg->fas_cmd, fas->f_stat, fas->f_intr, fasreg->fas_step, fasreg->fas_fifo_flag); fas_set_pkt_reason(fas, fas->f_current_sp, CMD_TRAN_ERR, 0); fas_internal_reset(fas, FAS_RESET_FAS); return (ACTION_RESET); } /* * handle illegal cmd interrupt or (external) bus reset cleanup */ static int fas_illegal_cmd_or_bus_reset(struct fas *fas) { /* * If we detect a SCSI reset, we blow away the current * command (if there is one) and all disconnected commands * because we now don't know the state of them at all. */ ASSERT(fas->f_intr & (FAS_INT_ILLEGAL | FAS_INT_RESET)); if (fas->f_intr & FAS_INT_RESET) { return (ACTION_FINRST); } /* * Illegal cmd to fas: * This should not happen. The one situation where * we can get an ILLEGAL COMMAND interrupt is due to * a bug in the FAS366 during reselection which we * should be handling in fas_reconnect(). */ if (fas->f_intr & FAS_INT_ILLEGAL) { IPRINTF1("lastcmd=%x\n", fas->f_reg->fas_cmd); fas_printstate(fas, "ILLEGAL bit set"); return (ACTION_RESET); } /*NOTREACHED*/ return (ACTION_RETURN); } /* * set throttles for all luns of this target */ static void fas_set_throttles(struct fas *fas, int slot, int n, int what) { int i; /* * if the bus is draining/quiesced, no changes to the throttles * are allowed. Not allowing change of throttles during draining * limits error recovery but will reduce draining time * * all throttles should have been set to HOLD_THROTTLE */ if (fas->f_softstate & (FAS_SS_QUIESCED | FAS_SS_DRAINING)) { return; } ASSERT((n == 1) || (n == N_SLOTS) || (n == NLUNS_PER_TARGET)); ASSERT((slot + n) <= N_SLOTS); if (n == NLUNS_PER_TARGET) { slot &= ~(NLUNS_PER_TARGET - 1); } for (i = slot; i < (slot + n); i++) { if (what == HOLD_THROTTLE) { fas->f_throttle[i] = HOLD_THROTTLE; } else if ((fas->f_reset_delay[i/NLUNS_PER_TARGET]) == 0) { if (what == MAX_THROTTLE) { int tshift = 1 << (i/NLUNS_PER_TARGET); fas->f_throttle[i] = (short) ((fas->f_notag & tshift)? 1 : what); } else { fas->f_throttle[i] = what; } } } } static void fas_set_all_lun_throttles(struct fas *fas, int slot, int what) { /* * fas_set_throttle will adjust slot to starting at LUN 0 */ fas_set_throttles(fas, slot, NLUNS_PER_TARGET, what); } static void fas_full_throttle(struct fas *fas, int slot) { fas_set_throttles(fas, slot, 1, MAX_THROTTLE); } /* * run a polled cmd */ static void fas_runpoll(struct fas *fas, short slot, struct fas_cmd *sp) { int limit, i, n; int timeout = 0; DPRINTF4("runpoll: slot=%x, cmd=%x, current_sp=0x%p, tcmds=%x\n", slot, *((uchar_t *)sp->cmd_pkt->pkt_cdbp), (void *)fas->f_current_sp, fas->f_tcmds[slot]); TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_RUNPOLL_START, "fas_runpoll_start"); /* * wait for cmd to complete * don't start new cmds so set throttles to HOLD_THROTTLE */ while ((sp->cmd_flags & CFLAG_COMPLETED) == 0) { if (!(sp->cmd_flags & CFLAG_CMDPROXY)) { fas_set_all_lun_throttles(fas, slot, HOLD_THROTTLE); } if ((fas->f_state != STATE_FREE) || INTPENDING(fas)) { if (fas_dopoll(fas, POLL_TIMEOUT) <= 0) { IPRINTF("runpoll: timeout on draining\n"); goto bad; } } ASSERT(fas->f_state == STATE_FREE); ASSERT(fas->f_current_sp == NULL); /* * if this is not a proxy cmd, don't start the cmd * without draining the active cmd(s) * for proxy cmds, we zap the active cmd and assume * that the caller will take care of this * For tagged cmds, wait with submitting a non-tagged * cmd until the queue has been drained * If the cmd is a request sense, then draining won't * help since we are in contingence allegiance condition */ if (!(sp->cmd_flags & CFLAG_CMDPROXY)) { uchar_t *cmdp = (uchar_t *)sp->cmd_pkt->pkt_cdbp; if ((fas->f_tcmds[slot]) && (NOTAG(Tgt(sp)) || (((sp->cmd_pkt_flags & FLAG_TAGMASK) == 0) && (*cmdp != SCMD_REQUEST_SENSE)))) { if (timeout < POLL_TIMEOUT) { timeout += 100; drv_usecwait(100); continue; } else { fas_log(fas, CE_WARN, "polled cmd failed (target busy)"); goto cleanup; } } } /* * If the draining of active commands killed the * the current polled command, we're done.. */ if (sp->cmd_flags & CFLAG_COMPLETED) { break; } /* * ensure we are not accessing a target too quickly * after a reset. the throttles get set back later * by the reset delay watch; hopefully, we don't go * thru this loop more than once */ if (fas->f_reset_delay[slot/NLUNS_PER_TARGET]) { IPRINTF1("reset delay set for slot %x\n", slot); drv_usecwait(fas->f_scsi_reset_delay * 1000); for (i = 0; i < NTARGETS_WIDE; i++) { if (fas->f_reset_delay[i]) { int s = i * NLUNS_PER_TARGET; int e = s + NLUNS_PER_TARGET; fas->f_reset_delay[i] = 0; for (; s < e; s++) { fas_full_throttle(fas, s); } } } } /* * fas_startcmd() will return false if preempted * or draining */ if (fas_startcmd(fas, sp) != TRUE) { IPRINTF("runpoll: cannot start new cmds\n"); ASSERT(fas->f_current_sp != sp); continue; } /* * We're now 'running' this command. * * fas_dopoll will always return when * fas->f_state is STATE_FREE, and */ limit = sp->cmd_pkt->pkt_time * 1000000; if (limit == 0) { limit = POLL_TIMEOUT; } /* * if the cmd disconnected, the first call to fas_dopoll * will return with bus free; we go thru the loop one more * time and wait limit usec for the target to reconnect */ for (i = 0; i <= POLL_TIMEOUT; i += 100) { if ((n = fas_dopoll(fas, limit)) <= 0) { IPRINTF("runpoll: timeout on polling\n"); goto bad; } /* * If a preemption occurred that caused this * command to actually not start, go around * the loop again. If CFLAG_COMPLETED is set, the * command completed */ if ((sp->cmd_flags & CFLAG_COMPLETED) || (sp->cmd_pkt->pkt_state == 0)) { break; } /* * the bus may have gone free because the target * disconnected; go thru the loop again */ ASSERT(fas->f_state == STATE_FREE); if (n == 0) { /* * bump i, we have waited limit usecs in * fas_dopoll */ i += limit - 100; } } if ((sp->cmd_flags & CFLAG_COMPLETED) == 0) { if (i > POLL_TIMEOUT) { IPRINTF("polled timeout on disc. cmd\n"); goto bad; } if (sp->cmd_pkt->pkt_state) { /* * don't go thru the loop again; the cmd * was already started */ IPRINTF("fas_runpoll: cmd started??\n"); goto bad; } } } /* * blindly restore throttles which is preferable over * leaving throttle hanging at 0 and noone to clear it */ if (!(sp->cmd_flags & CFLAG_CMDPROXY)) { fas_set_all_lun_throttles(fas, slot, MAX_THROTTLE); } /* * ensure that the cmd is completely removed */ fas_remove_cmd(fas, sp, 0); /* * If we stored up commands to do, start them off now. */ if ((fas->f_state == STATE_FREE) && (!(sp->cmd_flags & CFLAG_CMDPROXY))) { (void) fas_ustart(fas); } exit: TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_RUNPOLL_END, "fas_runpoll_end"); return; bad: fas_log(fas, CE_WARN, "Polled cmd failed"); #ifdef FASDEBUG fas_printstate(fas, "fas_runpoll: polled cmd failed"); #endif /* FASDEBUG */ cleanup: fas_set_all_lun_throttles(fas, slot, MAX_THROTTLE); /* * clean up all traces of this sp because fas_runpoll will return * before fas_reset_recovery() cleans up */ fas_remove_cmd(fas, sp, NEW_TIMEOUT); fas_decrement_ncmds(fas, sp); fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); if ((sp->cmd_flags & CFLAG_CMDPROXY) == 0) { (void) fas_reset_bus(fas); } goto exit; } /* * Poll for command completion (i.e., no interrupts) * limit is in usec (and will not be very accurate) * * the assumption is that we only run polled cmds in interrupt context * as scsi_transport will filter out FLAG_NOINTR */ static int fas_dopoll(struct fas *fas, int limit) { int i, n; /* * timeout is not very accurate since we don't know how * long the poll takes * also if the packet gets started fairly late, we may * timeout prematurely * fas_dopoll always returns if e_state transitions to STATE_FREE */ TRACE_0(TR_FAC_SCSI_FAS, TR_FAS_DOPOLL_START, "fas_dopoll_start"); if (limit == 0) { limit = POLL_TIMEOUT; } for (n = i = 0; i < limit; i += 100) { if (INTPENDING(fas)) { fas->f_polled_intr = 1; n++; (void) fas_intr_svc(fas); if (fas->f_state == STATE_FREE) break; } drv_usecwait(100); } if (i >= limit && fas->f_state != STATE_FREE) { fas_printstate(fas, "polled command timeout"); n = -1; } TRACE_1(TR_FAC_SCSI_FAS, TR_FAS_DOPOLL_END, "fas_dopoll_end: rval %x", n); return (n); } /* * prepare a sync negotiation message */ static void fas_make_sdtr(struct fas *fas, int msgout_offset, int target) { uchar_t *p = fas->f_cur_msgout + msgout_offset; ushort_t tshift = 1<f_backoff & tshift) { period = fas->f_neg_period[target]; } /* * If this is a responce to a target initiated sdtr, * use the agreed upon values. */ if (fas->f_sdtr_sent & 1) { period = fas->f_neg_period[target]; offset = fas->f_offset[target]; } /* * If the target driver disabled * sync then make offset = 0 */ if (fas->f_force_async & tshift) { offset = 0; } /* * sanity check of period and offset */ if (fas->f_target_scsi_options[target] & SCSI_OPTIONS_FAST) { if (period < (uchar_t)(DEFAULT_FASTSYNC_PERIOD/4)) { period = (uchar_t)(DEFAULT_FASTSYNC_PERIOD/4); } } else if (fas->f_target_scsi_options[target] & SCSI_OPTIONS_SYNC) { if (period < (uchar_t)(DEFAULT_SYNC_PERIOD/4)) { period = (uchar_t)(DEFAULT_SYNC_PERIOD/4); } } else { fas->f_nosync |= tshift; } if (fas->f_nosync & tshift) { offset = 0; } if ((uchar_t)(offset & 0xf) > fas_default_offset) { offset = fas_default_offset | fas->f_req_ack_delay; } fas->f_neg_period[target] = (uchar_t)period; fas->f_offset[target] = (uchar_t)offset; *p++ = (uchar_t)MSG_EXTENDED; *p++ = (uchar_t)3; *p++ = (uchar_t)MSG_SYNCHRONOUS; *p++ = period; *p++ = offset & 0xf; fas->f_omsglen = 5 + msgout_offset; IPRINTF2("fas_make_sdtr: period = %x, offset = %x\n", period, offset); /* * increment sdtr flag, odd value indicates that we initiated * the negotiation */ fas->f_sdtr_sent++; /* * the target may reject the optional sync message so * to avoid negotiating on every cmd, set sync known here * we should not negotiate wide after sync again */ fas->f_sync_known |= 1<f_wide_known |= 1<f_cur_msgout + msgout_offset; if (((fas->f_target_scsi_options[target] & SCSI_OPTIONS_WIDE) == 0) || (fas->f_nowide & (1<f_nowide |= 1<f_force_narrow & (1<f_omsglen = 4 + msgout_offset; IPRINTF1("fas_make_wdtr: width=%x\n", width); /* * increment wdtr flag, odd value indicates that we initiated * the negotiation */ fas->f_wdtr_sent++; /* * the target may reject the optional wide message so * to avoid negotiating on every cmd, set wide known here */ fas->f_wide_known |= 1<a_target * NLUNS_PER_TARGET | ap->a_lun; struct buf *bp; struct arq_private_data *arq_data; /* * if one exists, don't create another */ if (fas->f_arq_pkt[slot] != 0) { return (0); } /* * it would be nicer if we could allow the target driver * to specify the size but this is easier and OK for most * drivers to use SENSE_LENGTH * Allocate a request sense packet. */ bp = scsi_alloc_consistent_buf(ap, (struct buf *)NULL, SENSE_LENGTH, B_READ, SLEEP_FUNC, NULL); rqpktp = PKT2CMD(scsi_init_pkt(ap, NULL, bp, CDB_GROUP0, 1, PKT_PRIV_LEN, PKT_CONSISTENT, SLEEP_FUNC, NULL)); arq_data = (struct arq_private_data *)(rqpktp->cmd_pkt->pkt_private); arq_data->arq_save_bp = bp; RQ_MAKECOM_G0((CMD2PKT(rqpktp)), FLAG_SENSING | FLAG_HEAD | FLAG_NODISCON, (char)SCMD_REQUEST_SENSE, 0, (char)SENSE_LENGTH); rqpktp->cmd_flags |= CFLAG_CMDARQ; rqpktp->cmd_slot = slot; rqpktp->cmd_pkt->pkt_ha_private = rqpktp; fas->f_arq_pkt[slot] = rqpktp; /* * we need a function ptr here so abort/reset can * defer callbacks; fas_call_pkt_comp() calls * fas_complete_arq_pkt() directly without releasing the lock * However, since we are not calling back directly thru * pkt_comp, don't check this with warlock */ #ifndef __lock_lint rqpktp->cmd_pkt->pkt_comp = (void (*)(struct scsi_pkt *))fas_complete_arq_pkt; #endif return (0); } static int fas_delete_arq_pkt(struct fas *fas, struct scsi_address *ap) { struct fas_cmd *rqpktp; int slot = ap->a_target * NLUNS_PER_TARGET | ap->a_lun; /* * if there is still a pkt saved or no rqpkt * then we cannot deallocate or there is nothing to do */ if ((rqpktp = fas->f_arq_pkt[slot]) != NULL) { struct arq_private_data *arq_data = (struct arq_private_data *)(rqpktp->cmd_pkt->pkt_private); struct buf *bp = arq_data->arq_save_bp; /* * is arq pkt in use? */ if (arq_data->arq_save_sp) { return (-1); } scsi_destroy_pkt(CMD2PKT(rqpktp)); scsi_free_consistent_buf(bp); fas->f_arq_pkt[slot] = 0; } return (0); } /* * complete an arq packet by copying over transport info and the actual * request sense data; called with mutex held from fas_call_pkt_comp() */ void fas_complete_arq_pkt(struct scsi_pkt *pkt) { struct fas *fas = ADDR2FAS(&pkt->pkt_address); struct fas_cmd *sp = pkt->pkt_ha_private; struct scsi_arq_status *arqstat; struct arq_private_data *arq_data = (struct arq_private_data *)sp->cmd_pkt->pkt_private; struct fas_cmd *ssp = arq_data->arq_save_sp; struct buf *bp = arq_data->arq_save_bp; int slot = sp->cmd_slot; DPRINTF1("completing arq pkt sp=0x%p\n", (void *)sp); ASSERT(sp == fas->f_arq_pkt[slot]); ASSERT(arq_data->arq_save_sp != NULL); ASSERT(ssp != fas->f_active[sp->cmd_slot]->f_slot[sp->cmd_tag[1]]); arqstat = (struct scsi_arq_status *)(ssp->cmd_pkt->pkt_scbp); arqstat->sts_rqpkt_status = *((struct scsi_status *) (sp->cmd_pkt->pkt_scbp)); arqstat->sts_rqpkt_reason = sp->cmd_pkt->pkt_reason; arqstat->sts_rqpkt_state = sp->cmd_pkt->pkt_state; arqstat->sts_rqpkt_statistics = sp->cmd_pkt->pkt_statistics; arqstat->sts_rqpkt_resid = sp->cmd_pkt->pkt_resid; arqstat->sts_sensedata = *((struct scsi_extended_sense *)bp->b_un.b_addr); ssp->cmd_pkt->pkt_state |= STATE_ARQ_DONE; arq_data->arq_save_sp = NULL; /* * ASC=0x47 is parity error */ if (arqstat->sts_sensedata.es_key == KEY_ABORTED_COMMAND && arqstat->sts_sensedata.es_add_code == 0x47) { fas_sync_wide_backoff(fas, sp, slot); } fas_call_pkt_comp(fas, ssp); } /* * handle check condition and start an arq packet */ static int fas_handle_sts_chk(struct fas *fas, struct fas_cmd *sp) { struct fas_cmd *arqsp = fas->f_arq_pkt[sp->cmd_slot]; struct arq_private_data *arq_data; struct buf *bp; if ((arqsp == NULL) || (arqsp == sp) || (sp->cmd_scblen < sizeof (struct scsi_arq_status))) { IPRINTF("no arq packet or cannot arq on arq pkt\n"); fas_call_pkt_comp(fas, sp); return (0); } arq_data = (struct arq_private_data *)arqsp->cmd_pkt->pkt_private; bp = arq_data->arq_save_bp; ASSERT(sp->cmd_flags & CFLAG_FINISHED); ASSERT(sp != fas->f_active[sp->cmd_slot]->f_slot[sp->cmd_tag[1]]); DPRINTF3("start arq for slot=%x, arqsp=0x%p, rqpkt=0x%p\n", sp->cmd_slot, (void *)arqsp, (void *)fas->f_arq_pkt[sp->cmd_slot]); if (arq_data->arq_save_sp != NULL) { IPRINTF("auto request sense already in progress\n"); goto fail; } arq_data->arq_save_sp = sp; bzero(bp->b_un.b_addr, sizeof (struct scsi_extended_sense)); /* * copy the timeout from the original packet by lack of a better * value * we could take the residue of the timeout but that could cause * premature timeouts perhaps */ arqsp->cmd_pkt->pkt_time = sp->cmd_pkt->pkt_time; arqsp->cmd_flags &= ~CFLAG_TRANFLAG; ASSERT(arqsp->cmd_pkt->pkt_comp != NULL); /* * make sure that auto request sense always goes out * after queue full and after throttle was set to draining */ fas_full_throttle(fas, sp->cmd_slot); (void) fas_accept_pkt(fas, arqsp, NO_TRAN_BUSY); return (0); fail: fas_set_pkt_reason(fas, sp, CMD_TRAN_ERR, 0); fas_log(fas, CE_WARN, "auto request sense failed\n"); fas_dump_cmd(fas, sp); fas_call_pkt_comp(fas, sp); return (-1); } /* * handle qfull condition */ static void fas_handle_qfull(struct fas *fas, struct fas_cmd *sp) { int slot = sp->cmd_slot; if ((++sp->cmd_qfull_retries > fas->f_qfull_retries[Tgt(sp)]) || (fas->f_qfull_retries[Tgt(sp)] == 0)) { /* * We have exhausted the retries on QFULL, or, * the target driver has indicated that it * wants to handle QFULL itself by setting * qfull-retries capability to 0. In either case * we want the target driver's QFULL handling * to kick in. We do this by having pkt_reason * as CMD_CMPLT and pkt_scbp as STATUS_QFULL. */ IPRINTF2("%d.%d: status queue full, retries over\n", Tgt(sp), Lun(sp)); fas_set_all_lun_throttles(fas, slot, DRAIN_THROTTLE); fas_call_pkt_comp(fas, sp); } else { if (fas->f_reset_delay[Tgt(sp)] == 0) { fas->f_throttle[slot] = max((fas->f_tcmds[slot] - 2), 0); } IPRINTF3("%d.%d: status queue full, new throttle = %d, " "retrying\n", Tgt(sp), Lun(sp), fas->f_throttle[slot]); sp->cmd_pkt->pkt_flags |= FLAG_HEAD; sp->cmd_flags &= ~CFLAG_TRANFLAG; (void) fas_accept_pkt(fas, sp, NO_TRAN_BUSY); /* * when target gives queue full status with no commands * outstanding (f_tcmds[] == 0), throttle is set to 0 * (HOLD_THROTTLE), and the queue full handling starts * (see psarc/1994/313); if there are commands outstanding, * the throttle is set to (f_tcmds[] - 2) */ if (fas->f_throttle[slot] == HOLD_THROTTLE) { /* * By setting throttle to QFULL_THROTTLE, we * avoid submitting new commands and in * fas_restart_cmd find out slots which need * their throttles to be cleared. */ fas_set_all_lun_throttles(fas, slot, QFULL_THROTTLE); if (fas->f_restart_cmd_timeid == 0) { fas->f_restart_cmd_timeid = timeout(fas_restart_cmd, fas, fas->f_qfull_retry_interval[Tgt(sp)]); } } } } /* * invoked from timeout() to restart qfull cmds with throttle == 0 */ static void fas_restart_cmd(void *fas_arg) { struct fas *fas = fas_arg; int i; IPRINTF("fas_restart_cmd:\n"); mutex_enter(FAS_MUTEX(fas)); fas->f_restart_cmd_timeid = 0; for (i = 0; i < N_SLOTS; i += NLUNS_PER_TARGET) { if (fas->f_reset_delay[i/NLUNS_PER_TARGET] == 0) { if (fas->f_throttle[i] == QFULL_THROTTLE) { fas_set_all_lun_throttles(fas, i, MAX_THROTTLE); } } } (void) fas_ustart(fas); mutex_exit(FAS_MUTEX(fas)); } /* * Timeout handling: * Command watchdog routines */ /*ARGSUSED*/ static void fas_watch(void *arg) { struct fas *fas; ushort_t props_update = 0; rw_enter(&fas_global_rwlock, RW_READER); for (fas = fas_head; fas != (struct fas *)NULL; fas = fas->f_next) { mutex_enter(FAS_MUTEX(fas)); IPRINTF2("ncmds=%x, ndisc=%x\n", fas->f_ncmds, fas->f_ndisc); #ifdef FAS_PIO_COUNTS if (fas->f_total_cmds) { int n = fas->f_total_cmds; fas_log(fas, CE_NOTE, "total=%d, cmds=%d fas-rd=%d, fas-wrt=%d, dma-rd=%d, dma-wrt=%d\n", fas->f_total_cmds, fas->f_reg_cmds/n, fas->f_reg_reads/n, fas->f_reg_writes/n, fas->f_reg_dma_reads/n, fas->f_reg_dma_writes/n); fas->f_reg_reads = fas->f_reg_writes = fas->f_reg_dma_reads = fas->f_reg_dma_writes = fas->f_reg_cmds = fas->f_total_cmds = 0; } #endif if (fas->f_ncmds) { int i; fas_watchsubr(fas); /* * reset throttle. the throttle may have been * too low if queue full was caused by * another initiator * Only reset throttle if no cmd active in slot 0 * (untagged cmd) */ #ifdef FAS_TEST if (fas_enable_untagged) { fas_test_untagged++; } #endif for (i = 0; i < N_SLOTS; i++) { if ((fas->f_throttle[i] > HOLD_THROTTLE) && (fas->f_active[i] && (fas->f_active[i]->f_slot[0] == NULL))) { fas_full_throttle(fas, i); } } } if (fas->f_props_update) { int i; /* * f_mutex will be released and reentered in * fas_props_update(). * Hence we save the fas->f_props_update now and * set to 0 indicating that property has been * updated. This will avoid a race condition with * any thread that runs in interrupt context that * attempts to set the f_props_update to non-zero value */ props_update = fas->f_props_update; fas->f_props_update = 0; for (i = 0; i < NTARGETS_WIDE; i++) { if (props_update & (1<f_dslot == 0)? 1 : fas->f_dslot); struct f_slots *tag_slots; for (slot = 0; slot < N_SLOTS; slot += d) { #ifdef FAS_TEST if (fas_btest) { fas_btest = 0; (void) fas_reset_bus(fas); return; } if (fas_force_timeout && fas->f_tcmds[slot]) { fas_cmd_timeout(fas, slot); fas_force_timeout = 0; return; } fas_test_reset(fas, slot); fas_test_abort(fas, slot); #endif /* FAS_TEST */ /* * check tagged cmds first */ tag_slots = fas->f_active[slot]; DPRINTF3( "fas_watchsubr: slot %x: tcmds=%x, timeout=%x\n", slot, fas->f_tcmds[slot], tag_slots->f_timeout); if ((fas->f_tcmds[slot] > 0) && (tag_slots->f_timebase)) { if (tag_slots->f_timebase <= fas_scsi_watchdog_tick) { tag_slots->f_timebase += fas_scsi_watchdog_tick; continue; } tag_slots->f_timeout -= fas_scsi_watchdog_tick; if (tag_slots->f_timeout < 0) { fas_cmd_timeout(fas, slot); return; } if ((tag_slots->f_timeout) <= fas_scsi_watchdog_tick) { IPRINTF1("pending timeout on slot=%x\n", slot); IPRINTF("draining all queues\n"); fas_set_throttles(fas, 0, N_SLOTS, DRAIN_THROTTLE); } } } } /* * timeout recovery */ static void fas_cmd_timeout(struct fas *fas, int slot) { int d = ((fas->f_dslot == 0)? 1 : fas->f_dslot); int target, lun, i, n, tag, ncmds; struct fas_cmd *sp = NULL; struct fas_cmd *ssp; ASSERT(fas->f_tcmds[slot]); #ifdef FAS_TEST if (fas_test_stop) { debug_enter("timeout"); } #endif /* * set throttle back; no more draining necessary */ for (i = 0; i < N_SLOTS; i += d) { if (fas->f_throttle[i] == DRAIN_THROTTLE) { fas_full_throttle(fas, i); } } if (NOTAG(slot/NLUNS_PER_TARGET)) { sp = fas->f_active[slot]->f_slot[0]; } /* * if no interrupt pending for next second then the current * cmd must be stuck; switch slot and sp to current slot and cmd */ if (fas->f_current_sp && fas->f_state != STATE_FREE) { for (i = 0; (i < 10000) && (INTPENDING(fas) == 0); i++) { drv_usecwait(100); } if (INTPENDING(fas) == 0) { slot = fas->f_current_sp->cmd_slot; sp = fas->f_current_sp; } } target = slot / NLUNS_PER_TARGET; lun = slot % NLUNS_PER_TARGET; /* * update all outstanding pkts for this slot */ n = fas->f_active[slot]->f_n_slots; for (ncmds = tag = 0; tag < n; tag++) { ssp = fas->f_active[slot]->f_slot[tag]; if (ssp && ssp->cmd_pkt->pkt_time) { fas_set_pkt_reason(fas, ssp, CMD_TIMEOUT, STAT_TIMEOUT | STAT_ABORTED); fas_short_dump_cmd(fas, ssp); ncmds++; } } /* * no timed-out cmds here? */ if (ncmds == 0) { return; } /* * dump all we know about this timeout */ if (sp) { if (sp->cmd_flags & CFLAG_CMDDISC) { fas_log(fas, CE_WARN, "Disconnected command timeout for Target %d.%d", target, lun); } else { ASSERT(sp == fas->f_current_sp); fas_log(fas, CE_WARN, "Connected command timeout for Target %d.%d", target, lun); /* * Current command timeout appears to relate often * to noisy SCSI in synchronous mode. */ if (fas->f_state == ACTS_DATA_DONE) { fas_sync_wide_backoff(fas, sp, slot); } } #ifdef FASDEBUG fas_printstate(fas, "timeout"); #endif } else { fas_log(fas, CE_WARN, "Disconnected tagged cmd(s) (%d) timeout for Target %d.%d", fas->f_tcmds[slot], target, lun); } if (fas_abort_cmd(fas, sp, slot) == ACTION_SEARCH) { (void) fas_istart(fas); } } /* * fas_sync_wide_backoff() increases sync period and enables slow * cable mode. * the second time, we revert back to narrow/async * we count on a bus reset to disable wide in the target and will * never renegotiate wide again */ static void fas_sync_wide_backoff(struct fas *fas, struct fas_cmd *sp, int slot) { char phase; ushort_t state = fas->f_state; uchar_t tgt = slot / NLUNS_PER_TARGET; uint_t tshift = 1 << tgt; phase = fas_reg_read(fas, &fas->f_reg->fas_stat); phase &= FAS_PHASE_MASK; IPRINTF4( "fas_sync_wide_backoff: target %d: state=%x, phase=%x, sp=0x%p\n", tgt, state, phase, (void *)sp); #ifdef FASDEBUG if (fas_no_sync_wide_backoff) { return; } #endif /* * if this not the first time or sync is disabled * thru scsi_options then disable wide */ if ((fas->f_backoff & tshift) || (fas->f_nosync & tshift)) { /* * disable wide for just this target */ if ((fas->f_nowide & tshift) == 0) { fas_log(fas, CE_WARN, "Target %d disabled wide SCSI mode", tgt); } /* * do not reset the bit in f_nowide because that * would not force a renegotiation of wide * and do not change any register value yet because * we may have reconnects before the renegotiations */ fas->f_target_scsi_options[tgt] &= ~SCSI_OPTIONS_WIDE; } /* * reduce xfer rate. if this is the first time, reduce by * 100%. second time, disable sync and wide. */ if (fas->f_offset[tgt] != 0) { /* * do not reset the bit in f_nosync because that * would not force a renegotiation of sync */ if (fas->f_backoff & tshift) { if ((fas->f_nosync & tshift) == 0) { fas_log(fas, CE_WARN, "Target %d reverting to async. mode", tgt); } fas->f_target_scsi_options[tgt] &= ~(SCSI_OPTIONS_SYNC | SCSI_OPTIONS_FAST); } else { /* increase period by 100% */ fas->f_neg_period[tgt] *= 2; fas_log(fas, CE_WARN, "Target %d reducing sync. transfer rate", tgt); } } fas->f_backoff |= tshift; /* * always enable slow cable mode, if not already enabled */ if ((fas->f_fasconf & FAS_CONF_SLOWMODE) == 0) { fas->f_fasconf |= FAS_CONF_SLOWMODE; fas_reg_write(fas, &fas->f_reg->fas_conf, fas->f_fasconf); IPRINTF("Reverting to slow SCSI cable mode\n"); } /* * Force sync renegotiation and update properties */ fas_force_renegotiation(fas, tgt); fas->f_props_update |= (1<f_current_sp; int tgt = Tgt(sp); if (fas->f_wdtr_sent) { IPRINTF("wide neg message rejected or bus free\n"); fas->f_nowide |= (1<f_fasconf3[tgt] &= ~FAS_CONF3_WIDE; fas_reg_write(fas, &fas->f_reg->fas_conf3, fas->f_fasconf3[tgt]); /* * clear offset just in case it goes to * data phase */ fas_reg_write(fas, (uchar_t *)&fas->f_reg->fas_sync_offset, 0); } else if (fas->f_sdtr_sent) { volatile struct fasreg *fasreg = fas->f_reg; IPRINTF("sync neg message rejected or bus free\n"); fas->f_nosync |= (1<f_offset[tgt] = 0; fas->f_sync_period[tgt] = 0; fas_reg_write(fas, (uchar_t *)&fasreg->fas_sync_period, 0); fas_reg_write(fas, (uchar_t *)&fasreg->fas_sync_offset, 0); fas->f_offset[tgt] = 0; fas->f_fasconf3[tgt] &= ~FAS_CONF3_FASTSCSI; fas_reg_write(fas, &fasreg->fas_conf3, fas->f_fasconf3[tgt]); } fas_force_renegotiation(fas, tgt); } /* * force wide and sync renegotiation */ static void fas_force_renegotiation(struct fas *fas, int target) { ushort_t tshift = 1<f_sync_known &= ~tshift; fas->f_sync_enabled &= ~tshift; fas->f_wide_known &= ~tshift; fas->f_wide_enabled &= ~tshift; } /* * update conf3 register for wide negotiation */ static void fas_set_wide_conf3(struct fas *fas, int target, int width) { ASSERT(width <= 1); switch (width) { case 0: fas->f_fasconf3[target] &= ~FAS_CONF3_WIDE; break; case 1: fas->f_fasconf3[target] |= FAS_CONF3_WIDE; fas->f_wide_enabled |= (1<f_reg->fas_conf3, fas->f_fasconf3[target]); fas->f_fasconf3_reg_last = fas->f_fasconf3[target]; } /* * Abort command handling * * abort current cmd, either by device reset or immediately with bus reset * (usually an abort msg doesn't completely solve the problem, therefore * a device or bus reset is recommended) */ static int fas_abort_curcmd(struct fas *fas) { if (fas->f_current_sp) { return (fas_abort_cmd(fas, fas->f_current_sp, fas->f_current_sp->cmd_slot)); } else { return (fas_reset_bus(fas)); } } static int fas_abort_cmd(struct fas *fas, struct fas_cmd *sp, int slot) { struct scsi_address ap; ap.a_hba_tran = fas->f_tran; ap.a_target = slot / NLUNS_PER_TARGET; ap.a_lun = slot % NLUNS_PER_TARGET; IPRINTF1("abort cmd 0x%p\n", (void *)sp); /* * attempting to abort a connected cmd is usually fruitless, so * only try disconnected cmds * a reset is preferable over an abort (see 1161701) */ if ((fas->f_current_sp && (fas->f_current_sp->cmd_slot != slot)) || (fas->f_state == STATE_FREE)) { IPRINTF2("attempting to reset target %d.%d\n", ap.a_target, ap.a_lun); if (fas_do_scsi_reset(&ap, RESET_TARGET)) { return (ACTION_SEARCH); } } /* * if the target won't listen, then a retry is useless * there is also the possibility that the cmd still completed while * we were trying to reset and the target driver may have done a * device reset which has blown away this sp. * well, we've tried, now pull the chain */ IPRINTF("aborting all cmds by bus reset\n"); return (fas_reset_bus(fas)); } /* * fas_do_scsi_abort() assumes that we already have the mutex. * during the abort, we hold the mutex and prevent callbacks by setting * completion pointer to NULL. this will also avoid that a target driver * attempts to do a scsi_abort/reset while we are aborting. * because the completion pointer is NULL we can still update the * packet after completion * the throttle for this slot is cleared either by fas_abort_connected_cmd * or fas_runpoll which prevents new cmds from starting while aborting */ static int fas_do_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt) { struct fas *fas = ADDR2FAS(ap); struct fas_cmd *sp; int rval = FALSE; short slot; struct fas_cmd *cur_sp = fas->f_current_sp; void (*cur_savec)(), (*sp_savec)(); int sp_tagged_flag, abort_msg; if (pkt) { sp = PKT2CMD(pkt); slot = sp->cmd_slot; ASSERT(slot == ((ap->a_target * NLUNS_PER_TARGET) | ap->a_lun)); } else { sp = NULL; slot = (ap->a_target * NLUNS_PER_TARGET) | ap->a_lun; } fas_move_waitQ_to_readyQ(fas); /* * If no specific command was passed, all cmds here will be aborted * If a specific command was passed as an argument (to be aborted) * only the specified command will be aborted */ ASSERT(mutex_owned(FAS_MUTEX(fas))); IPRINTF4("fas_scsi_abort for slot %x, " "sp=0x%p, pkt_flags=%x, cur_sp=0x%p\n", slot, (void *)sp, (sp? sp->cmd_pkt_flags : 0), (void *)cur_sp); /* * first check if the cmd is in the ready queue or * in the active queue */ if (sp) { IPRINTF3("aborting one command 0x%p for %d.%d\n", (void *)sp, ap->a_target, ap->a_lun); rval = fas_remove_from_readyQ(fas, sp, slot); if (rval) { IPRINTF("aborted one ready cmd\n"); fas_set_pkt_reason(fas, sp, CMD_ABORTED, STAT_ABORTED); fas_decrement_ncmds(fas, sp); fas_call_pkt_comp(fas, sp); goto exit; } else if ((sp != fas->f_active[slot]->f_slot[sp->cmd_tag[1]])) { IPRINTF("cmd doesn't exist here\n"); rval = TRUE; goto exit; } } /* * hold off any new commands while attempting to abort * an active cmd */ fas_set_throttles(fas, slot, 1, HOLD_THROTTLE); if (cur_sp) { /* * prevent completion on current cmd */ cur_savec = cur_sp->cmd_pkt->pkt_comp; cur_sp->cmd_pkt->pkt_comp = NULL; } if (sp) { /* * the cmd exists here. is it connected or disconnected? * if connected but still selecting then can't abort now. * prevent completion on this cmd */ sp_tagged_flag = (sp->cmd_pkt_flags & FLAG_TAGMASK); abort_msg = (sp_tagged_flag? MSG_ABORT_TAG : MSG_ABORT); sp_savec = sp->cmd_pkt->pkt_comp; sp->cmd_pkt->pkt_comp = NULL; /* connected but not selecting? */ if ((sp == cur_sp) && (fas->f_state != STATE_FREE) && (sp->cmd_pkt->pkt_state)) { rval = fas_abort_connected_cmd(fas, sp, abort_msg); } /* if abort connected cmd failed, try abort disconnected */ if ((rval == 0) && (sp->cmd_flags & CFLAG_CMDDISC) && ((sp->cmd_flags & CFLAG_COMPLETED) == 0)) { rval = fas_abort_disconnected_cmd(fas, ap, sp, abort_msg, slot); } if (rval) { sp->cmd_flags |= CFLAG_COMPLETED; fas_set_pkt_reason(fas, sp, CMD_ABORTED, STAT_ABORTED); } sp->cmd_pkt->pkt_comp = sp_savec; } else { IPRINTF2("aborting all commands for %d.%d\n", ap->a_target, ap->a_lun); abort_msg = MSG_ABORT; /* active and not selecting ? */ if (cur_sp && (fas->f_state != STATE_FREE) && (cur_sp->cmd_slot == slot) && cur_sp->cmd_pkt->pkt_state) { rval = fas_abort_connected_cmd(fas, cur_sp, abort_msg); } if (rval == 0) { rval = fas_abort_disconnected_cmd(fas, ap, NULL, abort_msg, slot); } } done: /* complete the current sp */ if (cur_sp) { cur_sp->cmd_pkt->pkt_comp = cur_savec; if (cur_sp->cmd_flags & CFLAG_COMPLETED) { fas_remove_cmd(fas, cur_sp, NEW_TIMEOUT); cur_sp->cmd_flags &= ~CFLAG_COMPLETED; fas_decrement_ncmds(fas, cur_sp); fas_call_pkt_comp(fas, cur_sp); } } /* complete the sp passed as 2nd arg */ if (sp && (sp != cur_sp) && (sp->cmd_flags & CFLAG_COMPLETED)) { sp->cmd_flags &= ~CFLAG_COMPLETED; fas_remove_cmd(fas, sp, NEW_TIMEOUT); fas_decrement_ncmds(fas, sp); fas_call_pkt_comp(fas, sp); } /* clean up all cmds for this slot */ if (rval && (abort_msg == MSG_ABORT)) { /* * mark all commands here as aborted * abort msg has been accepted, now cleanup queues; */ fas_mark_packets(fas, slot, CMD_ABORTED, STAT_ABORTED); fas_flush_tagQ(fas, slot); fas_flush_readyQ(fas, slot); } fas_set_throttles(fas, slot, 1, MAX_THROTTLE); exit: if (fas->f_state == STATE_FREE) { (void) fas_ustart(fas); } ASSERT(mutex_owned(FAS_MUTEX(fas))); #ifdef FASDEBUG if (rval && fas_test_stop) { debug_enter("abort succeeded"); } #endif return (rval); } /* * mark all packets with new reason and update statistics */ static void fas_mark_packets(struct fas *fas, int slot, uchar_t reason, uint_t stat) { struct fas_cmd *sp = fas->f_readyf[slot]; while (sp != 0) { fas_set_pkt_reason(fas, sp, reason, STAT_ABORTED); sp = sp->cmd_forw; } if (fas->f_tcmds[slot]) { int n = 0; ushort_t tag; for (tag = 0; tag < fas->f_active[slot]->f_n_slots; tag++) { if ((sp = fas->f_active[slot]->f_slot[tag]) != 0) { fas_set_pkt_reason(fas, sp, reason, stat); n++; } } ASSERT(fas->f_tcmds[slot] == n); } } /* * set pkt_reason and OR in pkt_statistics flag */ static void fas_set_pkt_reason(struct fas *fas, struct fas_cmd *sp, uchar_t reason, uint_t stat) { if (sp) { if (sp->cmd_pkt->pkt_reason == CMD_CMPLT) { sp->cmd_pkt->pkt_reason = reason; } sp->cmd_pkt->pkt_statistics |= stat; IPRINTF3("sp=0x%p, pkt_reason=%x, pkt_stat=%x\n", (void *)sp, reason, sp->cmd_pkt->pkt_statistics); } } /* * delete specified cmd from the ready queue */ static int fas_remove_from_readyQ(struct fas *fas, struct fas_cmd *sp, int slot) { struct fas_cmd *ssp, *psp; /* * command has not been started yet and is still in the ready queue */ if (sp) { ASSERT(fas->f_ncmds > 0); /* * find packet on the ready queue and remove it */ for (psp = NULL, ssp = fas->f_readyf[slot]; ssp != NULL; psp = ssp, ssp = ssp->cmd_forw) { if (ssp == sp) { if (fas->f_readyf[slot] == sp) { fas->f_readyf[slot] = sp->cmd_forw; } else { psp->cmd_forw = sp->cmd_forw; } if (fas->f_readyb[slot] == sp) { fas->f_readyb[slot] = psp; } return (TRUE); } } } return (FALSE); } /* * add cmd to to head of the readyQ * due to tag allocation failure or preemption we have to return * this cmd to the readyQ */ static void fas_head_of_readyQ(struct fas *fas, struct fas_cmd *sp) { /* * never return a NOINTR pkt to the readyQ * (fas_runpoll will resubmit) */ if ((sp->cmd_pkt_flags & FLAG_NOINTR) == 0) { struct fas_cmd *dp; int slot = sp->cmd_slot; dp = fas->f_readyf[slot]; fas->f_readyf[slot] = sp; sp->cmd_forw = dp; if (fas->f_readyb[slot] == NULL) { fas->f_readyb[slot] = sp; } } } /* * flush cmds in ready queue */ static void fas_flush_readyQ(struct fas *fas, int slot) { if (fas->f_readyf[slot]) { struct fas_cmd *sp, *nsp; IPRINTF1("flushing ready queue, slot=%x\n", slot); ASSERT(fas->f_ncmds > 0); sp = fas->f_readyf[slot]; fas->f_readyf[slot] = fas->f_readyb[slot] = NULL; while (sp != 0) { /* * save the forward pointer before calling * the completion routine */ nsp = sp->cmd_forw; ASSERT((sp->cmd_flags & CFLAG_FREE) == 0); ASSERT(Tgt(sp) == slot/NLUNS_PER_TARGET); fas_decrement_ncmds(fas, sp); fas_call_pkt_comp(fas, sp); sp = nsp; } fas_check_ncmds(fas); } } /* * cleanup the tag queue * preserve some order by starting with the oldest tag */ static void fas_flush_tagQ(struct fas *fas, int slot) { ushort_t tag, starttag; struct fas_cmd *sp; struct f_slots *tagque = fas->f_active[slot]; if (tagque == NULL) { return; } DPRINTF2("flushing entire tag queue, slot=%x, tcmds=%x\n", slot, fas->f_tcmds[slot]); #ifdef FASDEBUG { int n = 0; for (tag = 0; tag < fas->f_active[slot]->f_n_slots; tag++) { if ((sp = tagque->f_slot[tag]) != 0) { n++; ASSERT((sp->cmd_flags & CFLAG_FREE) == 0); if (sp->cmd_pkt->pkt_reason == CMD_CMPLT) { if ((sp->cmd_flags & CFLAG_FINISHED) == 0) { debug_enter("fas_flush_tagQ"); } } } } ASSERT(fas->f_tcmds[slot] == n); } #endif tag = starttag = fas->f_active[slot]->f_tags; do { if ((sp = tagque->f_slot[tag]) != 0) { fas_flush_cmd(fas, sp, 0, 0); } tag = ((ushort_t)(tag + 1)) % (ushort_t)fas->f_active[slot]->f_n_slots; } while (tag != starttag); ASSERT(fas->f_tcmds[slot] == 0); EPRINTF2("ncmds = %x, ndisc=%x\n", fas->f_ncmds, fas->f_ndisc); fas_check_ncmds(fas); } /* * cleanup one active command */ static void fas_flush_cmd(struct fas *fas, struct fas_cmd *sp, uchar_t reason, uint_t stat) { short slot = sp->cmd_slot; ASSERT(fas->f_ncmds > 0); ASSERT((sp->cmd_flags & CFLAG_FREE) == 0); ASSERT(sp == fas->f_active[slot]->f_slot[sp->cmd_tag[1]]); fas_remove_cmd(fas, sp, NEW_TIMEOUT); fas_decrement_ncmds(fas, sp); fas_set_pkt_reason(fas, sp, reason, stat); fas_call_pkt_comp(fas, sp); EPRINTF2("ncmds = %x, ndisc=%x\n", fas->f_ncmds, fas->f_ndisc); fas_check_ncmds(fas); } /* * prepare a proxy cmd (a cmd sent on behalf of the target driver, * usually for error recovery or abort/reset) */ static void fas_makeproxy_cmd(struct fas_cmd *sp, struct scsi_address *ap, struct scsi_pkt *pkt, int nmsgs, ...) { va_list vap; int i; ASSERT(nmsgs <= (CDB_GROUP5 - CDB_GROUP0 - 3)); bzero(sp, sizeof (*sp)); bzero(pkt, scsi_pkt_size()); pkt->pkt_address = *ap; pkt->pkt_cdbp = (opaque_t)&sp->cmd_cdb[0]; pkt->pkt_scbp = (opaque_t)&sp->cmd_scb; pkt->pkt_ha_private = (opaque_t)sp; sp->cmd_pkt = pkt; sp->cmd_scblen = 1; sp->cmd_pkt_flags = pkt->pkt_flags = FLAG_NOINTR; sp->cmd_flags = CFLAG_CMDPROXY; sp->cmd_cdb[FAS_PROXY_TYPE] = FAS_PROXY_SNDMSG; sp->cmd_cdb[FAS_PROXY_RESULT] = FALSE; sp->cmd_cdb[FAS_PROXY_DATA] = (char)nmsgs; va_start(vap, nmsgs); for (i = 0; i < nmsgs; i++) { sp->cmd_cdb[FAS_PROXY_DATA + 1 + i] = (uchar_t)va_arg(vap, int); } va_end(vap); } /* * send a proxy cmd and check the result */ static int fas_do_proxy_cmd(struct fas *fas, struct fas_cmd *sp, struct scsi_address *ap, char *what) { int rval; IPRINTF3("Sending proxy %s message to %d.%d\n", what, ap->a_target, ap->a_lun); if (fas_accept_pkt(fas, sp, TRAN_BUSY_OK) == TRAN_ACCEPT && sp->cmd_pkt->pkt_reason == CMD_CMPLT && sp->cmd_cdb[FAS_PROXY_RESULT] == TRUE) { IPRINTF3("Proxy %s succeeded for %d.%d\n", what, ap->a_target, ap->a_lun); ASSERT(fas->f_current_sp != sp); rval = TRUE; } else { IPRINTF5( "Proxy %s failed for %d.%d, result=%x, reason=%x\n", what, ap->a_target, ap->a_lun, sp->cmd_cdb[FAS_PROXY_RESULT], sp->cmd_pkt->pkt_reason); ASSERT(fas->f_current_sp != sp); rval = FALSE; } return (rval); } /* * abort a connected command by sending an abort msg; hold off on * starting new cmds by setting throttles to HOLD_THROTTLE */ static int fas_abort_connected_cmd(struct fas *fas, struct fas_cmd *sp, uchar_t msg) { int rval = FALSE; int flags = sp->cmd_pkt_flags; /* * if reset delay active we cannot access the target. */ if (fas->f_reset_delay[Tgt(sp)]) { return (rval); } /* * only abort while in data phase; otherwise we mess up msg phase */ if (!((fas->f_state == ACTS_DATA) || (fas->f_state == ACTS_DATA_DONE))) { return (rval); } IPRINTF3("Sending abort message %s to connected %d.%d\n", scsi_mname(msg), Tgt(sp), Lun(sp)); fas->f_abort_msg_sent = 0; fas->f_omsglen = 1; fas->f_cur_msgout[0] = msg; sp->cmd_pkt_flags |= FLAG_NOINTR; fas_assert_atn(fas); (void) fas_dopoll(fas, SHORT_POLL_TIMEOUT); /* * now check if the msg was taken * e_abort is set in fas_handle_msg_out_done when the abort * msg has actually gone out (ie. msg out phase occurred */ if (fas->f_abort_msg_sent && (sp->cmd_flags & CFLAG_COMPLETED)) { IPRINTF2("target %d.%d aborted\n", Tgt(sp), Lun(sp)); rval = TRUE; } else { IPRINTF2("target %d.%d did not abort\n", Tgt(sp), Lun(sp)); } sp->cmd_pkt_flags = flags; fas->f_omsglen = 0; return (rval); } /* * abort a disconnected command; if it is a tagged command, we need * to include the tag */ static int fas_abort_disconnected_cmd(struct fas *fas, struct scsi_address *ap, struct fas_cmd *sp, uchar_t msg, int slot) { auto struct fas_cmd local; struct fas_cmd *proxy_cmdp = &local; struct scsi_pkt *pkt; int rval; int target = ap->a_target; /* * if reset delay is active, we cannot start a selection * and there shouldn't be a cmd outstanding */ if (fas->f_reset_delay[target] != 0) { return (FALSE); } if (sp) ASSERT(sp->cmd_slot == slot); IPRINTF1("aborting disconnected tagged cmd(s) with %s\n", scsi_mname(msg)); pkt = kmem_alloc(scsi_pkt_size(), KM_SLEEP); if (sp && (TAGGED(target) && (msg == MSG_ABORT_TAG))) { int tag = sp->cmd_tag[1]; ASSERT(sp == fas->f_active[slot]->f_slot[tag]); fas_makeproxy_cmd(proxy_cmdp, ap, pkt, 3, MSG_SIMPLE_QTAG, tag, msg); } else { fas_makeproxy_cmd(proxy_cmdp, ap, pkt, 1, msg); } rval = fas_do_proxy_cmd(fas, proxy_cmdp, ap, scsi_mname(msg)); kmem_free(pkt, scsi_pkt_size()); return (rval); } /* * reset handling: * fas_do_scsi_reset assumes that we have already entered the mutex */ static int fas_do_scsi_reset(struct scsi_address *ap, int level) { int rval = FALSE; struct fas *fas = ADDR2FAS(ap); short slot = (ap->a_target * NLUNS_PER_TARGET) | ap->a_lun; ASSERT(mutex_owned(FAS_MUTEX(fas))); IPRINTF3("fas_scsi_reset for slot %x, level=%x, tcmds=%x\n", slot, level, fas->f_tcmds[slot]); fas_move_waitQ_to_readyQ(fas); if (level == RESET_ALL) { /* * We know that fas_reset_bus() returns ACTION_RETURN. */ (void) fas_reset_bus(fas); /* * Now call fas_dopoll() to field the reset interrupt * which will then call fas_reset_recovery which will * call the completion function for all commands. */ if (fas_dopoll(fas, SHORT_POLL_TIMEOUT) <= 0) { /* * reset fas */ fas_internal_reset(fas, FAS_RESET_FAS); (void) fas_reset_bus(fas); if (fas_dopoll(fas, SHORT_POLL_TIMEOUT) <= 0) { fas_log(fas, CE_WARN, "reset scsi bus failed"); New_state(fas, STATE_FREE); } else { rval = TRUE; } } else { rval = TRUE; } } else { struct fas_cmd *cur_sp = fas->f_current_sp; void (*savec)() = NULL; /* * prevent new commands from starting */ fas_set_all_lun_throttles(fas, slot, HOLD_THROTTLE); /* * zero pkt_comp so it won't complete during the reset and * we can still update the packet after the reset. */ if (cur_sp) { savec = cur_sp->cmd_pkt->pkt_comp; cur_sp->cmd_pkt->pkt_comp = NULL; } /* * is this a connected cmd but not selecting? */ if (cur_sp && (fas->f_state != STATE_FREE) && (cur_sp->cmd_pkt->pkt_state != 0) && (ap->a_target == (Tgt(cur_sp)))) { rval = fas_reset_connected_cmd(fas, ap); } /* * if not connected or fas_reset_connected_cmd() failed, * attempt a reset_disconnected_cmd */ if (rval == FALSE) { rval = fas_reset_disconnected_cmd(fas, ap); } /* * cleanup if reset was successful * complete the current sp first. */ if (cur_sp) { cur_sp->cmd_pkt->pkt_comp = savec; if (cur_sp->cmd_flags & CFLAG_COMPLETED) { if (ap->a_target == (Tgt(cur_sp))) { fas_set_pkt_reason(fas, cur_sp, CMD_RESET, STAT_DEV_RESET); } fas_remove_cmd(fas, cur_sp, NEW_TIMEOUT); cur_sp->cmd_flags &= ~CFLAG_COMPLETED; fas_decrement_ncmds(fas, cur_sp); fas_call_pkt_comp(fas, cur_sp); } } if (rval == TRUE) { fas_reset_cleanup(fas, slot); } else { IPRINTF1("fas_scsi_reset failed for slot %x\n", slot); /* * restore throttles to max throttle, regardless * of what it was (fas_set_throttles() will deal * with reset delay active) * restoring to the old throttle is not * a such a good idea */ fas_set_all_lun_throttles(fas, slot, MAX_THROTTLE); } if (fas->f_state == STATE_FREE) { (void) fas_ustart(fas); } } exit: ASSERT(mutex_owned(FAS_MUTEX(fas))); ASSERT(fas->f_ncmds >= fas->f_ndisc); #ifdef FASDEBUG if (rval && fas_test_stop) { debug_enter("reset succeeded"); } #endif return (rval); } /* * reset delay is handled by a separate watchdog; this ensures that * regardless of fas_scsi_watchdog_tick, the reset delay will not change */ static void fas_start_watch_reset_delay(struct fas *fas) { mutex_enter(&fas_global_mutex); if ((fas_reset_watch == 0) && FAS_CAN_SCHED) { fas_reset_watch = timeout(fas_watch_reset_delay, NULL, drv_usectohz((clock_t)FAS_WATCH_RESET_DELAY_TICK * 1000)); } ASSERT((fas_reset_watch != 0) || (fas->f_flags & FAS_FLG_NOTIMEOUTS)); mutex_exit(&fas_global_mutex); } /* * set throttles to HOLD and set reset_delay for all target/luns */ static void fas_setup_reset_delay(struct fas *fas) { if (!ddi_in_panic()) { int i; fas_set_throttles(fas, 0, N_SLOTS, HOLD_THROTTLE); for (i = 0; i < NTARGETS_WIDE; i++) { fas->f_reset_delay[i] = fas->f_scsi_reset_delay; } fas_start_watch_reset_delay(fas); } else { drv_usecwait(fas->f_scsi_reset_delay * 1000); } } /* * fas_watch_reset_delay(_subr) is invoked by timeout() and checks every * fas instance for active reset delays */ /*ARGSUSED*/ static void fas_watch_reset_delay(void *arg) { struct fas *fas; struct fas *lfas; /* last not_done fas */ int not_done = 0; mutex_enter(&fas_global_mutex); fas_reset_watch = 0; mutex_exit(&fas_global_mutex); rw_enter(&fas_global_rwlock, RW_READER); for (fas = fas_head; fas != (struct fas *)NULL; fas = fas->f_next) { if (fas->f_tran == 0) { continue; } mutex_enter(FAS_MUTEX(fas)); not_done += fas_watch_reset_delay_subr(fas); lfas = fas; fas_check_waitQ_and_mutex_exit(fas); } rw_exit(&fas_global_rwlock); if (not_done) { ASSERT(lfas != NULL); fas_start_watch_reset_delay(lfas); } } static int fas_watch_reset_delay_subr(struct fas *fas) { short slot, s; int start_slot = -1; int done = 0; for (slot = 0; slot < N_SLOTS; slot += NLUNS_PER_TARGET) { /* * check if a reset delay is active; if so back to full throttle * which will unleash the cmds in the ready Q */ s = slot/NLUNS_PER_TARGET; if (fas->f_reset_delay[s] != 0) { EPRINTF2("target%d: reset delay=%d\n", s, fas->f_reset_delay[s]); fas->f_reset_delay[s] -= FAS_WATCH_RESET_DELAY_TICK; if (fas->f_reset_delay[s] <= 0) { /* * clear throttle for all luns on this target */ fas->f_reset_delay[s] = 0; fas_set_all_lun_throttles(fas, slot, MAX_THROTTLE); IPRINTF1("reset delay completed, slot=%x\n", slot); if (start_slot == -1) { start_slot = slot; } } else { done = -1; } } } /* * start a cmd if a reset delay expired */ if (start_slot != -1 && fas->f_state == STATE_FREE) { (void) fas_ustart(fas); } return (done); } /* * cleanup after a device reset. this affects all target's luns */ static void fas_reset_cleanup(struct fas *fas, int slot) { /* * reset msg has been accepted, now cleanup queues; * for all luns of this target */ int i, start, end; int target = slot/NLUNS_PER_TARGET; start = slot & ~(NLUNS_PER_TARGET-1); end = start + NLUNS_PER_TARGET; IPRINTF4("fas_reset_cleanup: slot %x, start=%x, end=%x, tcmds=%x\n", slot, start, end, fas->f_tcmds[slot]); ASSERT(!(fas->f_current_sp && (fas->f_current_sp->cmd_slot == slot) && (fas->f_state & STATE_SELECTING))); /* * if we are not in panic set up a reset delay for this target, * a zero throttle forces all new requests into the ready Q */ if (!ddi_in_panic()) { fas_set_all_lun_throttles(fas, start, HOLD_THROTTLE); fas->f_reset_delay[target] = fas->f_scsi_reset_delay; fas_start_watch_reset_delay(fas); } else { drv_usecwait(fas->f_scsi_reset_delay * 1000); } for (i = start; i < end; i++) { fas_mark_packets(fas, i, CMD_RESET, STAT_DEV_RESET); fas_flush_tagQ(fas, i); fas_flush_readyQ(fas, i); if (fas->f_arq_pkt[i]) { struct fas_cmd *sp = fas->f_arq_pkt[i]; struct arq_private_data *arq_data = (struct arq_private_data *) (sp->cmd_pkt->pkt_private); if (sp->cmd_pkt->pkt_comp) { ASSERT(arq_data->arq_save_sp == NULL); } } ASSERT(fas->f_tcmds[i] == 0); } ASSERT(fas->f_ncmds >= fas->f_ndisc); fas_force_renegotiation(fas, target); } /* * reset a currently disconnected target */ static int fas_reset_disconnected_cmd(struct fas *fas, struct scsi_address *ap) { auto struct fas_cmd local; struct fas_cmd *sp = &local; struct scsi_pkt *pkt; int rval; pkt = kmem_alloc(scsi_pkt_size(), KM_SLEEP); fas_makeproxy_cmd(sp, ap, pkt, 1, MSG_DEVICE_RESET); rval = fas_do_proxy_cmd(fas, sp, ap, scsi_mname(MSG_DEVICE_RESET)); kmem_free(pkt, scsi_pkt_size()); return (rval); } /* * reset a target with a currently connected command * Assert ATN and send MSG_DEVICE_RESET, zero throttles temporarily * to prevent new cmds from starting regardless of the outcome */ static int fas_reset_connected_cmd(struct fas *fas, struct scsi_address *ap) { int rval = FALSE; struct fas_cmd *sp = fas->f_current_sp; int flags = sp->cmd_pkt_flags; /* * only attempt to reset in data phase; during other phases * asserting ATN may just cause confusion */ if (!((fas->f_state == ACTS_DATA) || (fas->f_state == ACTS_DATA_DONE))) { return (rval); } IPRINTF2("Sending reset message to connected %d.%d\n", ap->a_target, ap->a_lun); fas->f_reset_msg_sent = 0; fas->f_omsglen = 1; fas->f_cur_msgout[0] = MSG_DEVICE_RESET; sp->cmd_pkt_flags |= FLAG_NOINTR; fas_assert_atn(fas); /* * poll for interrupts until bus free */ (void) fas_dopoll(fas, SHORT_POLL_TIMEOUT); /* * now check if the msg was taken * f_reset is set in fas_handle_msg_out_done when * msg has actually gone out (ie. msg out phase occurred) */ if (fas->f_reset_msg_sent && (sp->cmd_flags & CFLAG_COMPLETED)) { IPRINTF2("target %d.%d reset\n", ap->a_target, ap->a_lun); rval = TRUE; } else { IPRINTF2("target %d.%d did not reset\n", ap->a_target, ap->a_lun); } sp->cmd_pkt_flags = flags; fas->f_omsglen = 0; return (rval); } /* * reset the scsi bus to blow all commands away */ static int fas_reset_bus(struct fas *fas) { IPRINTF("fas_reset_bus:\n"); New_state(fas, ACTS_RESET); fas_internal_reset(fas, FAS_RESET_SCSIBUS); /* * Now that we've reset the SCSI bus, we'll take a SCSI RESET * interrupt and use that to clean up the state of things. */ return (ACTION_RETURN); } /* * fas_reset_recovery is called on the reset interrupt and cleans * up all cmds (active or waiting) */ static int fas_reset_recovery(struct fas *fas) { short slot, start_slot; int i; int rval = ACTION_SEARCH; int max_loop = 0; IPRINTF("fas_reset_recovery:\n"); fas_check_ncmds(fas); /* * renegotiate wide and sync for all targets */ fas->f_sync_known = fas->f_wide_known = 0; /* * reset dma engine */ FAS_FLUSH_DMA_HARD(fas); /* * set throttles and reset delay */ fas_setup_reset_delay(fas); /* * clear interrupts until they go away */ while (INTPENDING(fas) && (max_loop < FAS_RESET_SPIN_MAX_LOOP)) { volatile struct fasreg *fasreg = fas->f_reg; fas->f_stat = fas_reg_read(fas, &fasreg->fas_stat); fas->f_stat2 = fas_reg_read(fas, &fasreg->fas_stat2); fas->f_step = fas_reg_read(fas, &fasreg->fas_step); fas->f_intr = fas_reg_read(fas, &fasreg->fas_intr); drv_usecwait(FAS_RESET_SPIN_DELAY_USEC); max_loop++; } if (max_loop >= FAS_RESET_SPIN_MAX_LOOP) { fas_log(fas, CE_WARN, "Resetting SCSI bus failed"); } fas_reg_cmd_write(fas, CMD_FLUSH); /* * reset the chip, this shouldn't be necessary but sometimes * we get a hang in the next data in phase */ fas_internal_reset(fas, FAS_RESET_FAS); /* * reset was expected? if not, it must be external bus reset */ if (fas->f_state != ACTS_RESET) { if (fas->f_ncmds) { fas_log(fas, CE_WARN, "external SCSI bus reset"); } } if (fas->f_ncmds == 0) { rval = ACTION_RETURN; goto done; } /* * completely reset the state of the softc data. */ fas_internal_reset(fas, FAS_RESET_SOFTC); /* * Hold the state of the host adapter open */ New_state(fas, ACTS_FROZEN); /* * for right now just claim that all * commands have been destroyed by a SCSI reset * and let already set reason fields or callers * decide otherwise for specific commands. */ start_slot = fas->f_next_slot; slot = start_slot; do { fas_check_ncmds(fas); fas_mark_packets(fas, slot, CMD_RESET, STAT_BUS_RESET); fas_flush_tagQ(fas, slot); fas_flush_readyQ(fas, slot); if (fas->f_arq_pkt[slot]) { struct fas_cmd *sp = fas->f_arq_pkt[slot]; struct arq_private_data *arq_data = (struct arq_private_data *) (sp->cmd_pkt->pkt_private); if (sp->cmd_pkt->pkt_comp) { ASSERT(arq_data->arq_save_sp == NULL); } } slot = NEXTSLOT(slot, fas->f_dslot); } while (slot != start_slot); fas_check_ncmds(fas); /* * reset timeouts */ for (i = 0; i < N_SLOTS; i++) { if (fas->f_active[i]) { fas->f_active[i]->f_timebase = 0; fas->f_active[i]->f_timeout = 0; fas->f_active[i]->f_dups = 0; } } done: /* * Move the state back to free... */ New_state(fas, STATE_FREE); ASSERT(fas->f_ncmds >= fas->f_ndisc); /* * perform the reset notification callbacks that are registered. */ (void) scsi_hba_reset_notify_callback(&fas->f_mutex, &fas->f_reset_notify_listf); /* * if reset delay is still active a search is meaningless * but do it anyway */ return (rval); } /* * hba_tran ops for quiesce and unquiesce */ static int fas_scsi_quiesce(dev_info_t *dip) { struct fas *fas; scsi_hba_tran_t *tran; tran = ddi_get_driver_private(dip); if ((tran == NULL) || ((fas = TRAN2FAS(tran)) == NULL)) { return (-1); } return (fas_quiesce_bus(fas)); } static int fas_scsi_unquiesce(dev_info_t *dip) { struct fas *fas; scsi_hba_tran_t *tran; tran = ddi_get_driver_private(dip); if ((tran == NULL) || ((fas = TRAN2FAS(tran)) == NULL)) { return (-1); } return (fas_unquiesce_bus(fas)); } #ifdef FAS_TEST /* * torture test functions */ static void fas_test_reset(struct fas *fas, int slot) { struct scsi_address ap; char target = slot/NLUNS_PER_TARGET; if (fas_rtest & (1 << target)) { ap.a_hba_tran = fas->f_tran; ap.a_target = target; ap.a_lun = 0; if ((fas_rtest_type == 1) && (fas->f_state == ACTS_DATA_DONE)) { if (fas_do_scsi_reset(&ap, RESET_TARGET)) { fas_rtest = 0; } } else if ((fas_rtest_type == 2) && (fas->f_state == ACTS_DATA_DONE)) { if (fas_do_scsi_reset(&ap, RESET_ALL)) { fas_rtest = 0; } } else { if (fas_do_scsi_reset(&ap, RESET_TARGET)) { fas_rtest = 0; } } } } static void fas_test_abort(struct fas *fas, int slot) { struct fas_cmd *sp = fas->f_current_sp; struct scsi_address ap; char target = slot/NLUNS_PER_TARGET; struct scsi_pkt *pkt = NULL; if (fas_atest & (1 << target)) { ap.a_hba_tran = fas->f_tran; ap.a_target = target; ap.a_lun = 0; if ((fas_atest_disc == 0) && sp && (sp->cmd_slot == slot) && ((sp->cmd_flags & CFLAG_CMDDISC) == 0)) { pkt = sp->cmd_pkt; } else if ((fas_atest_disc == 1) && NOTAG(target)) { sp = fas->f_active[slot]->f_slot[0]; if (sp && (sp->cmd_flags & CFLAG_CMDDISC)) { pkt = sp->cmd_pkt; } } else if ((fas_atest_disc == 1) && (sp == 0) && TAGGED(target) && (fas->f_tcmds[slot] != 0)) { int tag; /* * find the oldest tag */ for (tag = NTAGS-1; tag >= 0; tag--) { if ((sp = fas->f_active[slot]->f_slot[tag]) != 0) break; } if (sp) { pkt = sp->cmd_pkt; ASSERT(sp->cmd_slot == slot); } else { return; } } else if (fas_atest_disc == 2 && (sp == 0) && (fas->f_tcmds[slot] != 0)) { pkt = NULL; } else if (fas_atest_disc == 2 && NOTAG(target)) { pkt = NULL; } else if (fas_atest_disc == 3 && fas->f_readyf[slot]) { pkt = fas->f_readyf[slot]->cmd_pkt; } else if (fas_atest_disc == 4 && fas->f_readyf[slot] && fas->f_readyf[slot]->cmd_forw) { pkt = fas->f_readyf[slot]->cmd_forw->cmd_pkt; } else if (fas_atest_disc == 5 && fas->f_readyb[slot]) { pkt = fas->f_readyb[slot]->cmd_pkt; } else if ((fas_atest_disc == 6) && sp && (sp->cmd_slot == slot) && (fas->f_state == ACTS_DATA_DONE)) { pkt = sp->cmd_pkt; } else if (fas_atest_disc == 7) { if (fas_do_scsi_abort(&ap, NULL)) { if (fas_do_scsi_abort(&ap, NULL)) { if (fas_do_scsi_reset(&ap, RESET_TARGET)) { fas_atest = 0; } } } return; } else { return; } fas_log(fas, CE_NOTE, "aborting pkt=0x%p state=%x\n", (void *)pkt, (pkt != NULL? pkt->pkt_state : 0)); if (fas_do_scsi_abort(&ap, pkt)) { fas_atest = 0; } } } #endif /* FAS_TEST */ /* * capability interface */ static int fas_commoncap(struct scsi_address *ap, char *cap, int val, int tgtonly, int doset) { struct fas *fas = ADDR2FAS(ap); int cidx; int target = ap->a_target; ushort_t tshift = (1<f_target_scsi_options[ap->a_target] |= SCSI_OPTIONS_DR; else fas->f_target_scsi_options[ap->a_target] &= ~SCSI_OPTIONS_DR; break; case SCSI_CAP_SYNCHRONOUS: if (val) { fas->f_force_async &= ~tshift; } else { fas->f_force_async |= tshift; } fas_force_renegotiation(fas, target); rval = TRUE; break; case SCSI_CAP_TAGGED_QING: { int slot = target * NLUNS_PER_TARGET | ap->a_lun; ushort_t old_notag = fas->f_notag; /* do not allow with active tgt */ if (fas->f_tcmds[slot]) { break; } slot = target * NLUNS_PER_TARGET | ap->a_lun; if (val) { if (fas->f_target_scsi_options[target] & SCSI_OPTIONS_TAG) { IPRINTF1("target %d: TQ enabled\n", target); fas->f_notag &= ntshift; } else { break; } } else { IPRINTF1("target %d: TQ disabled\n", target); fas->f_notag |= tshift; } if (val && fas_alloc_active_slots(fas, slot, KM_NOSLEEP)) { fas->f_notag = old_notag; break; } fas_set_all_lun_throttles(fas, slot, MAX_THROTTLE); fas_update_props(fas, target); rval = TRUE; break; } case SCSI_CAP_WIDE_XFER: if (val) { if (fas->f_target_scsi_options[target] & SCSI_OPTIONS_WIDE) { fas->f_nowide &= ntshift; fas->f_force_narrow &= ~tshift; } else { break; } } else { fas->f_force_narrow |= tshift; } fas_force_renegotiation(fas, target); rval = TRUE; break; case SCSI_CAP_ARQ: if (val) { if (fas_create_arq_pkt(fas, ap)) { break; } } else { if (fas_delete_arq_pkt(fas, ap)) { break; } } rval = TRUE; break; case SCSI_CAP_QFULL_RETRIES: fas->f_qfull_retries[target] = (uchar_t)val; rval = TRUE; break; case SCSI_CAP_QFULL_RETRY_INTERVAL: fas->f_qfull_retry_interval[target] = drv_usectohz(val * 1000); rval = TRUE; break; default: rval = UNDEFINED; break; } } else if (doset == 0) { int slot = target * NLUNS_PER_TARGET | ap->a_lun; switch (cidx) { case SCSI_CAP_DMA_MAX: /* very high limit because of multiple dma windows */ rval = 1<<30; break; case SCSI_CAP_MSG_OUT: rval = TRUE; break; case SCSI_CAP_DISCONNECT: if (tgtonly && (fas->f_target_scsi_options[target] & SCSI_OPTIONS_DR)) { rval = TRUE; } break; case SCSI_CAP_SYNCHRONOUS: if (tgtonly && fas->f_offset[target]) { rval = TRUE; } break; case SCSI_CAP_PARITY: rval = TRUE; break; case SCSI_CAP_INITIATOR_ID: rval = MY_ID(fas); break; case SCSI_CAP_TAGGED_QING: if (tgtonly && ((fas->f_notag & tshift) == 0)) { rval = TRUE; } break; case SCSI_CAP_WIDE_XFER: if ((tgtonly && (fas->f_nowide & tshift) == 0)) { rval = TRUE; } break; case SCSI_CAP_UNTAGGED_QING: rval = TRUE; break; case SCSI_CAP_ARQ: if (tgtonly && fas->f_arq_pkt[slot]) { rval = TRUE; } break; case SCSI_CAP_LINKED_CMDS: break; case SCSI_CAP_RESET_NOTIFICATION: rval = TRUE; break; case SCSI_CAP_QFULL_RETRIES: rval = fas->f_qfull_retries[target]; break; case SCSI_CAP_QFULL_RETRY_INTERVAL: rval = drv_hztousec( fas->f_qfull_retry_interval[target]) / 1000; break; default: rval = UNDEFINED; break; } } exit: if (val && tgtonly) { fas_update_props(fas, target); } fas_check_waitQ_and_mutex_exit(fas); if (doset) { IPRINTF6( "fas_commoncap:tgt=%x,cap=%s,tgtonly=%x,doset=%x,val=%x,rval=%x\n", target, cap, tgtonly, doset, val, rval); } return (rval); } /* * property management * fas_update_props: * create/update sync/wide/TQ/scsi-options properties for this target */ static void fas_update_props(struct fas *fas, int tgt) { char property[32]; uint_t xfer_speed = 0; uint_t xfer_rate = 0; int wide_enabled, tq_enabled; uint_t regval = fas->f_sync_period[tgt]; int offset = fas->f_offset[tgt]; wide_enabled = ((fas->f_nowide & (1<f_clock_cycle) / 1000); xfer_rate = ((wide_enabled)? 2 : 1) * xfer_speed; } (void) sprintf(property, "target%x-sync-speed", tgt); fas_update_this_prop(fas, property, xfer_rate); (void) sprintf(property, "target%x-wide", tgt); fas_update_this_prop(fas, property, wide_enabled); (void) sprintf(property, "target%x-TQ", tgt); tq_enabled = ((fas->f_notag & (1<f_dev; IPRINTF2("update prop: %s value=%x\n", property, value); ASSERT(mutex_owned(FAS_MUTEX(fas))); /* * We cannot hold any mutex at this point because the call to * ddi_prop_update_int() may block. */ mutex_exit(FAS_MUTEX(fas)); if (ddi_prop_update_int(DDI_DEV_T_NONE, dip, property, value) != DDI_PROP_SUCCESS) { IPRINTF1("cannot modify/create %s property\n", property); } mutex_enter(FAS_MUTEX(fas)); } /* * allocate active slots array, size is dependent on whether tagQ enabled */ static int fas_alloc_active_slots(struct fas *fas, int slot, int flag) { int target = slot / NLUNS_PER_TARGET; struct f_slots *old_active = fas->f_active[slot]; struct f_slots *new_active; ushort_t size; int rval = -1; if (fas->f_tcmds[slot]) { IPRINTF("cannot change size of active slots array\n"); return (rval); } size = ((NOTAG(target)) ? FAS_F_SLOT_SIZE : FAS_F_SLOTS_SIZE_TQ); EPRINTF4( "fas_alloc_active_slots: target=%x size=%x, old=0x%p, oldsize=%x\n", target, size, (void *)old_active, ((old_active == NULL) ? -1 : old_active->f_size)); new_active = kmem_zalloc(size, flag); if (new_active == NULL) { IPRINTF("new active alloc failed\n"); } else { fas->f_active[slot] = new_active; fas->f_active[slot]->f_n_slots = (NOTAG(target) ? 1 : NTAGS); fas->f_active[slot]->f_size = size; /* * reserve tag 0 for non-tagged cmds to tagged targets */ if (TAGGED(target)) { fas->f_active[slot]->f_tags = 1; } if (old_active) { kmem_free((caddr_t)old_active, old_active->f_size); } rval = 0; } return (rval); } /* * Error logging, printing, and debug print routines */ static char *fas_label = "fas"; /*PRINTFLIKE3*/ static void fas_log(struct fas *fas, int level, const char *fmt, ...) { dev_info_t *dev; va_list ap; if (fas) { dev = fas->f_dev; } else { dev = 0; } mutex_enter(&fas_log_mutex); va_start(ap, fmt); (void) vsprintf(fas_log_buf, fmt, ap); va_end(ap); if (level == CE_CONT) { scsi_log(dev, fas_label, level, "%s\n", fas_log_buf); } else { scsi_log(dev, fas_label, level, "%s", fas_log_buf); } mutex_exit(&fas_log_mutex); } /*PRINTFLIKE2*/ static void fas_printf(struct fas *fas, const char *fmt, ...) { dev_info_t *dev = 0; va_list ap; int level = CE_CONT; mutex_enter(&fas_log_mutex); va_start(ap, fmt); (void) vsprintf(fas_log_buf, fmt, ap); va_end(ap); if (fas) { dev = fas->f_dev; level = CE_NOTE; scsi_log(dev, fas_label, level, "%s", fas_log_buf); } else { scsi_log(dev, fas_label, level, "%s\n", fas_log_buf); } mutex_exit(&fas_log_mutex); } #ifdef FASDEBUG /*PRINTFLIKE2*/ void fas_dprintf(struct fas *fas, const char *fmt, ...) { dev_info_t *dev = 0; va_list ap; if (fas) { dev = fas->f_dev; } mutex_enter(&fas_log_mutex); va_start(ap, fmt); (void) vsprintf(fas_log_buf, fmt, ap); va_end(ap); scsi_log(dev, fas_label, SCSI_DEBUG, "%s", fas_log_buf); mutex_exit(&fas_log_mutex); } #endif static void fas_printstate(struct fas *fas, char *msg) { volatile struct fasreg *fasreg = fas->f_reg; volatile struct dma *dmar = fas->f_dma; uint_t csr = fas_dma_reg_read(fas, &dmar->dma_csr); uint_t count = fas_dma_reg_read(fas, &dmar->dma_count); uint_t addr = fas_dma_reg_read(fas, &dmar->dma_addr); uint_t test = fas_dma_reg_read(fas, &dmar->dma_test); uint_t fas_cnt; fas_log(fas, CE_WARN, "%s: current fas state:", msg); fas_printf(NULL, "Latched stat=0x%b intr=0x%b", fas->f_stat, FAS_STAT_BITS, fas->f_intr, FAS_INT_BITS); fas_printf(NULL, "last msgout: %s, last msgin: %s", scsi_mname(fas->f_last_msgout), scsi_mname(fas->f_last_msgin)); fas_printf(NULL, "DMA csr=0x%b", csr, dma_bits); fas_printf(NULL, "addr=%x dmacnt=%x test=%x last=%x last_cnt=%x", addr, count, test, fas->f_lastdma, fas->f_lastcount); GET_FAS_COUNT(fasreg, fas_cnt); fas_printf(NULL, "fas state:"); fas_printf(NULL, "\tcount(32)=%x cmd=%x stat=%x stat2=%x intr=%x", fas_cnt, fasreg->fas_cmd, fasreg->fas_stat, fasreg->fas_stat2, fasreg->fas_intr); fas_printf(NULL, "\tstep=%x fifoflag=%x conf=%x test=%x conf2=%x conf3=%x", fasreg->fas_step, fasreg->fas_fifo_flag, fasreg->fas_conf, fasreg->fas_test, fasreg->fas_conf2, fasreg->fas_conf3); if (fas->f_current_sp) { fas_dump_cmd(fas, fas->f_current_sp); } } /* * dump all we know about a cmd */ static void fas_dump_cmd(struct fas *fas, struct fas_cmd *sp) { int i; uchar_t *cp = (uchar_t *)sp->cmd_pkt->pkt_cdbp; auto char buf[128]; buf[0] = '\0'; fas_printf(NULL, "Cmd dump for Target %d Lun %d:", Tgt(sp), Lun(sp)); (void) sprintf(&buf[0], " cdb=["); for (i = 0; i < (int)sp->cmd_actual_cdblen; i++) { (void) sprintf(&buf[strlen(buf)], " 0x%x", *cp++); } (void) sprintf(&buf[strlen(buf)], " ]"); fas_printf(NULL, buf); fas_printf(NULL, "State=%s Last State=%s", fas_state_name(fas->f_state), fas_state_name(fas->f_laststate)); fas_printf(NULL, "pkt_state=0x%b pkt_flags=0x%x pkt_statistics=0x%x", sp->cmd_pkt->pkt_state, scsi_state_bits, sp->cmd_pkt_flags, sp->cmd_pkt->pkt_statistics); if (sp->cmd_pkt->pkt_state & STATE_GOT_STATUS) { fas_printf(NULL, "Status=0x%x\n", sp->cmd_pkt->pkt_scbp[0]); } } /*ARGSUSED*/ static void fas_short_dump_cmd(struct fas *fas, struct fas_cmd *sp) { int i; uchar_t *cp = (uchar_t *)sp->cmd_pkt->pkt_cdbp; auto char buf[128]; buf[0] = '\0'; (void) sprintf(&buf[0], "?%d.%d: cdb=[", Tgt(sp), Lun(sp)); for (i = 0; i < (int)sp->cmd_actual_cdblen; i++) { (void) sprintf(&buf[strlen(buf)], " 0x%x", *cp++); } (void) sprintf(&buf[strlen(buf)], " ]"); fas_printf(NULL, buf); } /* * state decoding for error messages */ static char * fas_state_name(ushort_t state) { if (state == STATE_FREE) { return ("FREE"); } else if (state & STATE_SELECTING) { if (state == STATE_SELECT_NORMAL) return ("SELECT"); else if (state == STATE_SELECT_N_STOP) return ("SEL&STOP"); else if (state == STATE_SELECT_N_SENDMSG) return ("SELECT_SNDMSG"); else return ("SEL_NO_ATN"); } else { static struct { char *sname; char state; } names[] = { "CMD_START", ACTS_CMD_START, "CMD_DONE", ACTS_CMD_DONE, "MSG_OUT", ACTS_MSG_OUT, "MSG_OUT_DONE", ACTS_MSG_OUT_DONE, "MSG_IN", ACTS_MSG_IN, "MSG_IN_MORE", ACTS_MSG_IN_MORE, "MSG_IN_DONE", ACTS_MSG_IN_DONE, "CLEARING", ACTS_CLEARING, "DATA", ACTS_DATA, "DATA_DONE", ACTS_DATA_DONE, "CMD_CMPLT", ACTS_C_CMPLT, "UNKNOWN", ACTS_UNKNOWN, "RESEL", ACTS_RESEL, "ENDVEC", ACTS_ENDVEC, "RESET", ACTS_RESET, "ABORTING", ACTS_ABORTING, "FROZEN", ACTS_FROZEN, 0 }; int i; for (i = 0; names[i].sname; i++) { if (names[i].state == state) return (names[i].sname); } } return (""); }