/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2023 Oxide Computer Company */ /* * 1394 mass storage HBA driver */ #include #include #include #include #include #include #include #include #include #include #include /* DDI/DKI entry points */ static int scsa1394_attach(dev_info_t *, ddi_attach_cmd_t); static int scsa1394_detach(dev_info_t *, ddi_detach_cmd_t); static int scsa1394_power(dev_info_t *, int, int); static int scsa1394_cpr_suspend(dev_info_t *); static void scsa1394_cpr_resume(dev_info_t *); /* configuration routines */ static void scsa1394_cleanup(scsa1394_state_t *, int); static int scsa1394_attach_1394(scsa1394_state_t *); static void scsa1394_detach_1394(scsa1394_state_t *); static int scsa1394_attach_threads(scsa1394_state_t *); static void scsa1394_detach_threads(scsa1394_state_t *); static int scsa1394_attach_scsa(scsa1394_state_t *); static void scsa1394_detach_scsa(scsa1394_state_t *); static int scsa1394_create_cmd_cache(scsa1394_state_t *); static void scsa1394_destroy_cmd_cache(scsa1394_state_t *); static int scsa1394_add_events(scsa1394_state_t *); static void scsa1394_remove_events(scsa1394_state_t *); /* device configuration */ static int scsa1394_scsi_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t, void *, dev_info_t **); static int scsa1394_scsi_bus_unconfig(dev_info_t *, uint_t, ddi_bus_config_op_t, void *); static void scsa1394_create_children(scsa1394_state_t *); static void scsa1394_bus_reset(dev_info_t *, ddi_eventcookie_t, void *, void *); static void scsa1394_disconnect(dev_info_t *, ddi_eventcookie_t, void *, void *); static void scsa1394_reconnect(dev_info_t *, ddi_eventcookie_t, void *, void *); /* SCSA HBA entry points */ static int scsa1394_scsi_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static void scsa1394_scsi_tgt_free(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int scsa1394_scsi_tgt_probe(struct scsi_device *, int (*)()); static int scsa1394_probe_g0_nodata(struct scsi_device *, int (*)(), uchar_t, uint_t, uint_t); static int scsa1394_probe_tran(struct scsi_pkt *); static struct scsi_pkt *scsa1394_scsi_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t arg); static void scsa1394_scsi_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static int scsa1394_scsi_start(struct scsi_address *, struct scsi_pkt *); static int scsa1394_scsi_abort(struct scsi_address *, struct scsi_pkt *); static int scsa1394_scsi_reset(struct scsi_address *, int); static int scsa1394_scsi_getcap(struct scsi_address *, char *, int); static int scsa1394_scsi_setcap(struct scsi_address *, char *, int, int); static void scsa1394_scsi_dmafree(struct scsi_address *, struct scsi_pkt *); static void scsa1394_scsi_sync_pkt(struct scsi_address *, struct scsi_pkt *); /* pkt resource allocation routines */ static int scsa1394_cmd_cache_constructor(void *, void *, int); static void scsa1394_cmd_cache_destructor(void *, void *); static int scsa1394_cmd_ext_alloc(scsa1394_state_t *, scsa1394_cmd_t *, int); static void scsa1394_cmd_ext_free(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_cdb_dma_alloc(scsa1394_state_t *, scsa1394_cmd_t *, int, int (*)(), caddr_t); static void scsa1394_cmd_cdb_dma_free(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_buf_dma_alloc(scsa1394_state_t *, scsa1394_cmd_t *, int, int (*)(), caddr_t, struct buf *); static void scsa1394_cmd_buf_dma_free(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_dmac2seg(scsa1394_state_t *, scsa1394_cmd_t *, ddi_dma_cookie_t *, uint_t, int); static void scsa1394_cmd_seg_free(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_pt_dma_alloc(scsa1394_state_t *, scsa1394_cmd_t *, int (*)(), caddr_t, int); static void scsa1394_cmd_pt_dma_free(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_buf_addr_alloc(scsa1394_state_t *, scsa1394_cmd_t *); static void scsa1394_cmd_buf_addr_free(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_buf_dma_move(scsa1394_state_t *, scsa1394_cmd_t *); /* pkt and data transfer routines */ static void scsa1394_prepare_pkt(scsa1394_state_t *, struct scsi_pkt *); static void scsa1394_cmd_fill_cdb(scsa1394_lun_t *, scsa1394_cmd_t *); static void scsa1394_cmd_fill_cdb_rbc(scsa1394_lun_t *, scsa1394_cmd_t *); static void scsa1394_cmd_fill_cdb_other(scsa1394_lun_t *, scsa1394_cmd_t *); static void scsa1394_cmd_fill_cdb_len(scsa1394_cmd_t *, int); static void scsa1394_cmd_fill_cdb_lba(scsa1394_cmd_t *, int); static void scsa1394_cmd_fill_12byte_cdb_len(scsa1394_cmd_t *, int); static void scsa1394_cmd_fill_read_cd_cdb_len(scsa1394_cmd_t *, int); static int scsa1394_cmd_read_cd_blk_size(uchar_t); static int scsa1394_cmd_fake_mode_sense(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_fake_inquiry(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_fake_comp(scsa1394_state_t *, scsa1394_cmd_t *); static int scsa1394_cmd_setup_next_xfer(scsa1394_lun_t *, scsa1394_cmd_t *); static void scsa1394_cmd_adjust_cdb(scsa1394_lun_t *, scsa1394_cmd_t *); static void scsa1394_cmd_status_wrka(scsa1394_lun_t *, scsa1394_cmd_t *); /* other routines */ static boolean_t scsa1394_is_my_child(dev_info_t *); static void * scsa1394_kmem_realloc(void *, int, int, size_t, int); static void *scsa1394_statep; #define SCSA1394_INST2STATE(inst) (ddi_get_soft_state(scsa1394_statep, inst)) static struct cb_ops scsa1394_cb_ops = { nodev, /* open */ nodev, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ NULL, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* prop_op */ NULL, /* stream */ D_MP, /* cb_flag */ CB_REV, /* rev */ nodev, /* aread */ nodev /* awrite */ }; static struct dev_ops scsa1394_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ ddi_no_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ scsa1394_attach, /* attach */ scsa1394_detach, /* detach */ nodev, /* reset */ &scsa1394_cb_ops, /* driver operations */ NULL, /* bus operations */ scsa1394_power, /* power */ ddi_quiesce_not_supported, /* devo_quiesce */ }; static struct modldrv scsa1394_modldrv = { &mod_driverops, /* module type */ "1394 Mass Storage HBA Driver", /* name of the module */ &scsa1394_ops, /* driver ops */ }; static struct modlinkage scsa1394_modlinkage = { MODREV_1, (void *)&scsa1394_modldrv, NULL }; /* tunables */ int scsa1394_bus_config_debug = 0; int scsa1394_start_stop_fail_max = SCSA1394_START_STOP_FAIL_MAX; int scsa1394_mode_sense_fail_max = SCSA1394_MODE_SENSE_FAIL_MAX; int scsa1394_start_stop_timeout_max = SCSA1394_START_STOP_TIMEOUT_MAX; /* workarounds */ int scsa1394_wrka_rbc2direct = 1; int scsa1394_wrka_fake_rmb = 0; int scsa1394_wrka_fake_prin = 1; int scsa1394_wrka_symbios = 1; int scsa1394_symbios_page_size = 4 * 1024; /* must be <= _pagesize */ int scsa1394_symbios_size_max = 512 * 248; /* multiple of page size */ /* * * --- DDI/DKI entry points * */ int _init(void) { int ret; if (((ret = ddi_soft_state_init(&scsa1394_statep, sizeof (scsa1394_state_t), 1)) != 0)) { return (ret); } if ((ret = scsi_hba_init(&scsa1394_modlinkage)) != 0) { ddi_soft_state_fini(&scsa1394_statep); return (ret); } if ((ret = mod_install(&scsa1394_modlinkage)) != 0) { scsi_hba_fini(&scsa1394_modlinkage); ddi_soft_state_fini(&scsa1394_statep); return (ret); } return (ret); } int _fini(void) { int ret; if ((ret = mod_remove(&scsa1394_modlinkage)) == 0) { scsi_hba_fini(&scsa1394_modlinkage); ddi_soft_state_fini(&scsa1394_statep); } return (ret); } int _info(struct modinfo *modinfop) { return (mod_info(&scsa1394_modlinkage, modinfop)); } static int scsa1394_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance = ddi_get_instance(dip); scsa1394_state_t *sp; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: scsa1394_cpr_resume(dip); return (DDI_SUCCESS); default: return (DDI_FAILURE); } if (ddi_soft_state_zalloc(scsa1394_statep, instance) != 0) { return (DDI_FAILURE); } sp = SCSA1394_INST2STATE(instance); #ifndef __lock_lint sp->s_dip = dip; sp->s_instance = instance; #endif mutex_init(&sp->s_mutex, NULL, MUTEX_DRIVER, sp->s_attachinfo.iblock_cookie); cv_init(&sp->s_event_cv, NULL, CV_DRIVER, NULL); if (scsa1394_attach_1394(sp) != DDI_SUCCESS) { scsa1394_cleanup(sp, 1); return (DDI_FAILURE); } if (scsa1394_sbp2_attach(sp) != DDI_SUCCESS) { scsa1394_cleanup(sp, 2); return (DDI_FAILURE); } if (scsa1394_attach_threads(sp) != DDI_SUCCESS) { scsa1394_cleanup(sp, 3); return (DDI_FAILURE); } if (scsa1394_attach_scsa(sp) != DDI_SUCCESS) { scsa1394_cleanup(sp, 4); return (DDI_FAILURE); } if (scsa1394_create_cmd_cache(sp) != DDI_SUCCESS) { scsa1394_cleanup(sp, 5); return (DDI_FAILURE); } if (scsa1394_add_events(sp) != DDI_SUCCESS) { scsa1394_cleanup(sp, 6); return (DDI_FAILURE); } /* prevent async PM changes until we are done */ (void) pm_busy_component(dip, 0); /* Set power to full on */ (void) pm_raise_power(dip, 0, PM_LEVEL_D0); /* we are done */ (void) pm_idle_component(dip, 0); #ifndef __lock_lint sp->s_dev_state = SCSA1394_DEV_ONLINE; #endif ddi_report_dev(dip); return (DDI_SUCCESS); } static int scsa1394_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(dip); scsa1394_state_t *sp; if ((sp = SCSA1394_INST2STATE(instance)) == NULL) { return (DDI_FAILURE); } switch (cmd) { case DDI_DETACH: /* Cycle power state to off and idle where done/gone */ (void) pm_lower_power(dip, 0, PM_LEVEL_D3); scsa1394_cleanup(sp, SCSA1394_CLEANUP_LEVEL_MAX); return (DDI_SUCCESS); case DDI_SUSPEND: return (scsa1394_cpr_suspend(dip)); default: return (DDI_FAILURE); } } /*ARGSUSED*/ static int scsa1394_power(dev_info_t *dip, int comp, int level) { return (DDI_SUCCESS); } /* * scsa1394_cpr_suspend * determine if the device's state can be changed to SUSPENDED */ /* ARGSUSED */ static int scsa1394_cpr_suspend(dev_info_t *dip) { int instance = ddi_get_instance(dip); scsa1394_state_t *sp; int rval = DDI_FAILURE; sp = SCSA1394_INST2STATE(instance); ASSERT(sp != NULL); mutex_enter(&sp->s_mutex); switch (sp->s_dev_state) { case SCSA1394_DEV_ONLINE: case SCSA1394_DEV_PWRED_DOWN: case SCSA1394_DEV_DISCONNECTED: sp->s_dev_state = SCSA1394_DEV_SUSPENDED; /* Power down and make device idle */ (void) pm_lower_power(dip, 0, PM_LEVEL_D3); rval = DDI_SUCCESS; break; case SCSA1394_DEV_SUSPENDED: default: if (scsa1394_bus_config_debug) cmn_err(CE_WARN, "scsa1304_cpr_suspend: Illegal dev state: %d", sp->s_dev_state); rval = DDI_SUCCESS; break; } mutex_exit(&sp->s_mutex); return (rval); } /* * scsa2usb_cpr_resume: * restore device's state */ static void scsa1394_cpr_resume(dev_info_t *dip) { int instance = ddi_get_instance(dip); scsa1394_state_t *sp; int i; scsa1394_lun_t *lp; sp = SCSA1394_INST2STATE(instance); ASSERT(sp != NULL); if (sp->s_dev_state != SCSA1394_DEV_SUSPENDED) return; /* * Go through each lun and reset it to force a reconnect. */ for (i = 0; i < sp->s_nluns; i++) { lp = &sp->s_lun[i]; if (lp->l_ses != NULL) { /* Are we loged in? */ scsa1394_sbp2_req_bus_reset(lp); scsa1394_sbp2_req_reconnect(lp); } } /* we are down so let the power get managed */ (void) pm_idle_component(dip, 0); } /* * * --- configuration routines * */ static void scsa1394_cleanup(scsa1394_state_t *sp, int level) { ASSERT((level > 0) && (level <= SCSA1394_CLEANUP_LEVEL_MAX)); switch (level) { default: scsa1394_remove_events(sp); /* FALLTHRU */ case 6: scsa1394_detach_scsa(sp); /* FALLTHRU */ case 5: scsa1394_destroy_cmd_cache(sp); /* FALLTHRU */ case 4: scsa1394_detach_threads(sp); /* FALLTHRU */ case 3: scsa1394_sbp2_detach(sp); /* FALLTHRU */ case 2: scsa1394_detach_1394(sp); /* FALLTHRU */ case 1: cv_destroy(&sp->s_event_cv); mutex_destroy(&sp->s_mutex); ddi_soft_state_free(scsa1394_statep, sp->s_instance); } } static int scsa1394_attach_1394(scsa1394_state_t *sp) { int ret; if ((ret = t1394_attach(sp->s_dip, T1394_VERSION_V1, 0, &sp->s_attachinfo, &sp->s_t1394_hdl)) != DDI_SUCCESS) { return (ret); } /* DMA attributes for data buffers */ sp->s_buf_dma_attr = sp->s_attachinfo.dma_attr; /* DMA attributes for page tables */ sp->s_pt_dma_attr = sp->s_attachinfo.dma_attr; sp->s_pt_dma_attr.dma_attr_sgllen = 1; /* pt must be contiguous */ if ((ret = t1394_get_targetinfo(sp->s_t1394_hdl, SCSA1394_BUSGEN(sp), 0, &sp->s_targetinfo)) != DDI_SUCCESS) { (void) t1394_detach(&sp->s_t1394_hdl, 0); return (ret); } return (DDI_SUCCESS); } static void scsa1394_detach_1394(scsa1394_state_t *sp) { (void) t1394_detach(&sp->s_t1394_hdl, 0); } static int scsa1394_attach_threads(scsa1394_state_t *sp) { char name[16]; int nthr; nthr = sp->s_nluns; (void) snprintf(name, sizeof (name), "scsa1394%d", sp->s_instance); if ((sp->s_taskq = ddi_taskq_create(sp->s_dip, name, nthr, TASKQ_DEFAULTPRI, 0)) == NULL) { return (DDI_FAILURE); } if (scsa1394_sbp2_threads_init(sp) != DDI_SUCCESS) { ddi_taskq_destroy(sp->s_taskq); return (DDI_FAILURE); } return (DDI_SUCCESS); } static void scsa1394_detach_threads(scsa1394_state_t *sp) { scsa1394_sbp2_threads_fini(sp); ddi_taskq_destroy(sp->s_taskq); } static int scsa1394_attach_scsa(scsa1394_state_t *sp) { scsi_hba_tran_t *tran; int ret; sp->s_tran = tran = scsi_hba_tran_alloc(sp->s_dip, SCSI_HBA_CANSLEEP); tran->tran_hba_private = sp; tran->tran_tgt_private = NULL; tran->tran_tgt_init = scsa1394_scsi_tgt_init; tran->tran_tgt_probe = scsa1394_scsi_tgt_probe; tran->tran_tgt_free = scsa1394_scsi_tgt_free; tran->tran_start = scsa1394_scsi_start; tran->tran_abort = scsa1394_scsi_abort; tran->tran_reset = scsa1394_scsi_reset; tran->tran_getcap = scsa1394_scsi_getcap; tran->tran_setcap = scsa1394_scsi_setcap; tran->tran_init_pkt = scsa1394_scsi_init_pkt; tran->tran_destroy_pkt = scsa1394_scsi_destroy_pkt; tran->tran_dmafree = scsa1394_scsi_dmafree; tran->tran_sync_pkt = scsa1394_scsi_sync_pkt; tran->tran_reset_notify = NULL; tran->tran_get_bus_addr = NULL; tran->tran_get_name = NULL; tran->tran_bus_reset = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_get_eventcookie = NULL; tran->tran_add_eventcall = NULL; tran->tran_remove_eventcall = NULL; tran->tran_post_event = NULL; tran->tran_bus_config = scsa1394_scsi_bus_config; tran->tran_bus_unconfig = scsa1394_scsi_bus_unconfig; if ((ret = scsi_hba_attach_setup(sp->s_dip, &sp->s_attachinfo.dma_attr, tran, 0)) != DDI_SUCCESS) { scsi_hba_tran_free(tran); return (ret); } return (DDI_SUCCESS); } static void scsa1394_detach_scsa(scsa1394_state_t *sp) { int ret; ret = scsi_hba_detach(sp->s_dip); ASSERT(ret == DDI_SUCCESS); scsi_hba_tran_free(sp->s_tran); } static int scsa1394_create_cmd_cache(scsa1394_state_t *sp) { char name[64]; (void) sprintf(name, "scsa1394%d_cache", sp->s_instance); sp->s_cmd_cache = kmem_cache_create(name, SCSA1394_CMD_SIZE, sizeof (void *), scsa1394_cmd_cache_constructor, scsa1394_cmd_cache_destructor, NULL, (void *)sp, NULL, 0); return ((sp->s_cmd_cache == NULL) ? DDI_FAILURE : DDI_SUCCESS); } static void scsa1394_destroy_cmd_cache(scsa1394_state_t *sp) { kmem_cache_destroy(sp->s_cmd_cache); } static int scsa1394_add_events(scsa1394_state_t *sp) { ddi_eventcookie_t br_evc, rem_evc, ins_evc; if (ddi_get_eventcookie(sp->s_dip, DDI_DEVI_BUS_RESET_EVENT, &br_evc) != DDI_SUCCESS) { return (DDI_FAILURE); } if (ddi_add_event_handler(sp->s_dip, br_evc, scsa1394_bus_reset, sp, &sp->s_reset_cb_id) != DDI_SUCCESS) { return (DDI_FAILURE); } if (ddi_get_eventcookie(sp->s_dip, DDI_DEVI_REMOVE_EVENT, &rem_evc) != DDI_SUCCESS) { (void) ddi_remove_event_handler(sp->s_reset_cb_id); return (DDI_FAILURE); } if (ddi_add_event_handler(sp->s_dip, rem_evc, scsa1394_disconnect, sp, &sp->s_remove_cb_id) != DDI_SUCCESS) { (void) ddi_remove_event_handler(sp->s_reset_cb_id); return (DDI_FAILURE); } if (ddi_get_eventcookie(sp->s_dip, DDI_DEVI_INSERT_EVENT, &ins_evc) != DDI_SUCCESS) { (void) ddi_remove_event_handler(sp->s_remove_cb_id); (void) ddi_remove_event_handler(sp->s_reset_cb_id); return (DDI_FAILURE); } if (ddi_add_event_handler(sp->s_dip, ins_evc, scsa1394_reconnect, sp, &sp->s_insert_cb_id) != DDI_SUCCESS) { (void) ddi_remove_event_handler(sp->s_remove_cb_id); (void) ddi_remove_event_handler(sp->s_reset_cb_id); return (DDI_FAILURE); } return (DDI_SUCCESS); } static void scsa1394_remove_events(scsa1394_state_t *sp) { ddi_eventcookie_t evc; if (ddi_get_eventcookie(sp->s_dip, DDI_DEVI_INSERT_EVENT, &evc) == DDI_SUCCESS) { (void) ddi_remove_event_handler(sp->s_insert_cb_id); } if (ddi_get_eventcookie(sp->s_dip, DDI_DEVI_REMOVE_EVENT, &evc) == DDI_SUCCESS) { (void) ddi_remove_event_handler(sp->s_remove_cb_id); } if (ddi_get_eventcookie(sp->s_dip, DDI_DEVI_BUS_RESET_EVENT, &evc) == DDI_SUCCESS) { (void) ddi_remove_event_handler(sp->s_reset_cb_id); } } /* * * --- device configuration * */ static int scsa1394_scsi_bus_config(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op, void *arg, dev_info_t **child) { scsa1394_state_t *sp = SCSA1394_INST2STATE(ddi_get_instance(dip)); int ret; if (scsa1394_bus_config_debug) { flag |= NDI_DEVI_DEBUG; } ndi_devi_enter(dip); if (DEVI(dip)->devi_child == NULL) { scsa1394_create_children(sp); } ret = ndi_busop_bus_config(dip, flag, op, arg, child, 0); ndi_devi_exit(dip); return (ret); } static int scsa1394_scsi_bus_unconfig(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op, void *arg) { scsa1394_state_t *sp = SCSA1394_INST2STATE(ddi_get_instance(dip)); int ret; uint_t saved_flag = flag; if (scsa1394_bus_config_debug) { flag |= NDI_DEVI_DEBUG; } /* * First offline and if offlining successful, then remove children. */ if (op == BUS_UNCONFIG_ALL) { flag &= ~(NDI_DEVI_REMOVE | NDI_UNCONFIG); } ndi_devi_enter(dip); ret = ndi_busop_bus_unconfig(dip, flag, op, arg); /* * If previous step was successful and not part of modunload daemon, * attempt to remove children. */ if ((op == BUS_UNCONFIG_ALL) && (ret == NDI_SUCCESS) && ((flag & NDI_AUTODETACH) == 0)) { flag |= NDI_DEVI_REMOVE; ret = ndi_busop_bus_unconfig(dip, flag, op, arg); } ndi_devi_exit(dip); if ((ret != NDI_SUCCESS) && (op == BUS_UNCONFIG_ALL) && ((saved_flag & NDI_DEVI_REMOVE) != 0)) { mutex_enter(&sp->s_mutex); if (!sp->s_disconnect_warned) { cmn_err(CE_WARN, "scsa1394(%d): " "Disconnected device was busy, please reconnect.\n", sp->s_instance); sp->s_disconnect_warned = B_TRUE; } mutex_exit(&sp->s_mutex); } return (ret); } void scsa1394_dtype2name(int dtype, char **node_name, char **driver_name) { static struct { char *node_name; char *driver_name; } dtype2name[] = { { "disk", "sd" }, /* DTYPE_DIRECT 0x00 */ { "tape", "st" }, /* DTYPE_SEQUENTIAL 0x01 */ { "printer", NULL }, /* DTYPE_PRINTER 0x02 */ { "processor", NULL }, /* DTYPE_PROCESSOR 0x03 */ { "worm", NULL }, /* DTYPE_WORM 0x04 */ { "disk", "sd" }, /* DTYPE_RODIRECT 0x05 */ { "scanner", NULL }, /* DTYPE_SCANNER 0x06 */ { "disk", "sd" }, /* DTYPE_OPTICAL 0x07 */ { "changer", NULL }, /* DTYPE_CHANGER 0x08 */ { "comm", NULL }, /* DTYPE_COMM 0x09 */ { "generic", NULL }, /* DTYPE_??? 0x0A */ { "generic", NULL }, /* DTYPE_??? 0x0B */ { "array_ctrl", NULL }, /* DTYPE_ARRAY_CTRL 0x0C */ { "esi", "ses" }, /* DTYPE_ESI 0x0D */ { "disk", "sd" } /* DTYPE_RBC 0x0E */ }; if (dtype < NELEM(dtype2name)) { *node_name = dtype2name[dtype].node_name; *driver_name = dtype2name[dtype].driver_name; } else { *node_name = "generic"; *driver_name = NULL; } } static void scsa1394_create_children(scsa1394_state_t *sp) { char name[SCSA1394_COMPAT_MAX][16]; char *compatible[SCSA1394_COMPAT_MAX]; dev_info_t *cdip; int i; int dtype; char *node_name; char *driver_name; int ret; bzero(name, sizeof (name)); (void) strcpy(name[0], "sd"); for (i = 0; i < SCSA1394_COMPAT_MAX; i++) { compatible[i] = name[i]; } for (i = 0; i < sp->s_nluns; i++) { dtype = scsa1394_sbp2_get_lun_type(&sp->s_lun[i]); scsa1394_dtype2name(dtype, &node_name, &driver_name); ndi_devi_alloc_sleep(sp->s_dip, node_name, (pnode_t)DEVI_SID_NODEID, &cdip); ret = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, SCSI_ADDR_PROP_TARGET, 0); if (ret != DDI_PROP_SUCCESS) { (void) ndi_devi_free(cdip); continue; } ret = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, SCSI_ADDR_PROP_LUN, i); if (ret != DDI_PROP_SUCCESS) { ddi_prop_remove_all(cdip); (void) ndi_devi_free(cdip); continue; } /* * Some devices don't support LOG SENSE, so tell * sd driver not to send this command. */ ret = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "pm-capable", 1); if (ret != DDI_PROP_SUCCESS) { ddi_prop_remove_all(cdip); (void) ndi_devi_free(cdip); continue; } ret = ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip, "hotpluggable"); if (ret != DDI_PROP_SUCCESS) { ddi_prop_remove_all(cdip); (void) ndi_devi_free(cdip); continue; } if (driver_name) { compatible[0] = driver_name; ret = ndi_prop_update_string_array(DDI_DEV_T_NONE, cdip, "compatible", (char **)compatible, SCSA1394_COMPAT_MAX); if (ret != DDI_PROP_SUCCESS) { ddi_prop_remove_all(cdip); (void) ndi_devi_free(cdip); continue; } } /* * add property "scsa1394" to distinguish from others' children */ ret = ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip, "scsa1394"); if (ret != DDI_PROP_SUCCESS) { ddi_prop_remove_all(cdip); (void) ndi_devi_free(cdip); continue; } (void) ddi_initchild(sp->s_dip, cdip); } } /*ARGSUSED*/ static void scsa1394_bus_reset(dev_info_t *dip, ddi_eventcookie_t evc, void *arg, void *data) { scsa1394_state_t *sp = arg; if (sp != NULL) { mutex_enter(&sp->s_mutex); if (sp->s_dev_state == SCSA1394_DEV_DISCONNECTED) { mutex_exit(&sp->s_mutex); return; } sp->s_stat.stat_bus_reset_cnt++; sp->s_dev_state = SCSA1394_DEV_BUS_RESET; sp->s_attachinfo.localinfo = *(t1394_localinfo_t *)data; mutex_exit(&sp->s_mutex); scsa1394_sbp2_req(sp, 0, SCSA1394_THREQ_BUS_RESET); } } /*ARGSUSED*/ static void scsa1394_disconnect(dev_info_t *dip, ddi_eventcookie_t evc, void *arg, void *data) { scsa1394_state_t *sp = arg; dev_info_t *cdip, *cdip_next; if (sp == NULL) { return; } mutex_enter(&sp->s_mutex); sp->s_stat.stat_disconnect_cnt++; sp->s_dev_state = SCSA1394_DEV_DISCONNECTED; mutex_exit(&sp->s_mutex); scsa1394_sbp2_disconnect(sp); ndi_devi_enter(dip); for (cdip = ddi_get_child(dip); cdip != NULL; cdip = cdip_next) { cdip_next = ddi_get_next_sibling(cdip); mutex_enter(&DEVI(cdip)->devi_lock); DEVI_SET_DEVICE_REMOVED(cdip); mutex_exit(&DEVI(cdip)->devi_lock); } ndi_devi_exit(dip); } /*ARGSUSED*/ static void scsa1394_reconnect(dev_info_t *dip, ddi_eventcookie_t evc, void *arg, void *data) { scsa1394_state_t *sp = arg; dev_info_t *cdip, *cdip_next; if (sp == NULL) { return; } mutex_enter(&sp->s_mutex); sp->s_stat.stat_reconnect_cnt++; sp->s_attachinfo.localinfo = *(t1394_localinfo_t *)data; sp->s_disconnect_warned = B_FALSE; mutex_exit(&sp->s_mutex); ndi_devi_enter(dip); for (cdip = ddi_get_child(dip); cdip != NULL; cdip = cdip_next) { cdip_next = ddi_get_next_sibling(cdip); mutex_enter(&DEVI(cdip)->devi_lock); DEVI_SET_DEVICE_REINSERTED(cdip); mutex_exit(&DEVI(cdip)->devi_lock); } ndi_devi_exit(dip); scsa1394_sbp2_req(sp, 0, SCSA1394_THREQ_RECONNECT); } /* * * --- SCSA entry points * */ /*ARGSUSED*/ static int scsa1394_scsi_tgt_init(dev_info_t *dip, dev_info_t *cdip, scsi_hba_tran_t *tran, struct scsi_device *sd) { scsa1394_state_t *sp = (scsa1394_state_t *)tran->tran_hba_private; int lun; int plen = sizeof (int); int ret = DDI_FAILURE; if (ddi_prop_op(DDI_DEV_T_ANY, cdip, PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, SCSI_ADDR_PROP_LUN, (caddr_t)&lun, &plen) != DDI_PROP_SUCCESS) { return (DDI_FAILURE); } if (!scsa1394_is_my_child(cdip)) { /* * add property "scsa1394" to distinguish from others' children */ ret = ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip, "scsa1394"); if (ret != DDI_PROP_SUCCESS) { return (DDI_FAILURE); } if (scsa1394_dev_is_online(sp)) { return (scsa1394_sbp2_login(sp, lun)); } else { return (DDI_FAILURE); } } if ((lun >= sp->s_nluns) || (sp->s_lun[lun].l_cdip != NULL) || !scsa1394_dev_is_online(sp)) { return (DDI_FAILURE); } if ((ret = scsa1394_sbp2_login(sp, lun)) == DDI_SUCCESS) { sp->s_lun[lun].l_cdip = cdip; } return (ret); } /*ARGSUSED*/ static void scsa1394_scsi_tgt_free(dev_info_t *dip, dev_info_t *cdip, scsi_hba_tran_t *tran, struct scsi_device *sd) { scsa1394_state_t *sp = (scsa1394_state_t *)tran->tran_hba_private; int lun; int plen = sizeof (int); if (!scsa1394_is_my_child(cdip)) { return; } if (ddi_prop_op(DDI_DEV_T_ANY, cdip, PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, SCSI_ADDR_PROP_LUN, (caddr_t)&lun, &plen) != DDI_PROP_SUCCESS) { return; } if ((lun < sp->s_nluns) && (sp->s_lun[lun].l_cdip == cdip)) { if (scsa1394_dev_is_online(sp)) { scsa1394_sbp2_logout(sp, lun, B_TRUE); } sp->s_lun[lun].l_cdip = NULL; } } static int scsa1394_scsi_tgt_probe(struct scsi_device *sd, int (*waitfunc)()) { dev_info_t *dip = ddi_get_parent(sd->sd_dev); scsi_hba_tran_t *tran = (scsi_hba_tran_t *)ddi_get_driver_private(dip); scsa1394_state_t *sp = (scsa1394_state_t *)tran->tran_hba_private; scsa1394_lun_t *lp; if (!scsa1394_dev_is_online(sp)) { return (SCSIPROBE_FAILURE); } lp = &sp->s_lun[sd->sd_address.a_lun]; if (scsa1394_probe_g0_nodata(sd, waitfunc, SCMD_TEST_UNIT_READY, 0, 0) != SCSIPROBE_EXISTS) { lp->l_nosup_tur = B_TRUE; (void) scsa1394_sbp2_reset(lp, RESET_LUN, NULL); } if (scsa1394_probe_g0_nodata(sd, waitfunc, SCMD_START_STOP, 0, 1) != SCSIPROBE_EXISTS) { lp->l_nosup_start_stop = B_TRUE; } /* standard probe issues INQUIRY, which some devices may not support */ if (scsi_hba_probe(sd, waitfunc) != SCSIPROBE_EXISTS) { lp->l_nosup_inquiry = B_TRUE; scsa1394_sbp2_fake_inquiry(sp, &lp->l_fake_inq); bcopy(&lp->l_fake_inq, sd->sd_inq, SUN_INQSIZE); #ifndef __lock_lint lp->l_rmb_orig = 1; #endif } if (scsa1394_wrka_fake_rmb) { sd->sd_inq->inq_rmb = 1; } return (SCSIPROBE_EXISTS); } static int scsa1394_probe_g0_nodata(struct scsi_device *sd, int (*waitfunc)(), uchar_t cmd, uint_t addr, uint_t cnt) { struct scsi_pkt *pkt; int ret = SCSIPROBE_EXISTS; pkt = scsi_init_pkt(&sd->sd_address, NULL, NULL, CDB_GROUP0, sizeof (struct scsi_arq_status), 0, PKT_CONSISTENT, waitfunc, NULL); if (pkt == NULL) { return (SCSIPROBE_NOMEM); } (void) scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp, cmd, addr, cnt, 0); ((union scsi_cdb *)(pkt)->pkt_cdbp)->scc_lun = sd->sd_address.a_lun; pkt->pkt_flags = FLAG_NOINTR; if (scsa1394_probe_tran(pkt) < 0) { if (pkt->pkt_reason == CMD_INCOMPLETE) { ret = SCSIPROBE_NORESP; } else if ((pkt->pkt_reason == CMD_TRAN_ERR) && ((*(pkt->pkt_scbp) & STATUS_MASK) == STATUS_CHECK) && (pkt->pkt_state & STATE_ARQ_DONE)) { ret = SCSIPROBE_EXISTS; } else { ret = SCSIPROBE_FAILURE; } } scsi_destroy_pkt(pkt); return (ret); } static int scsa1394_probe_tran(struct scsi_pkt *pkt) { pkt->pkt_time = SCSA1394_PROBE_TIMEOUT; if (scsi_transport(pkt) != TRAN_ACCEPT) { return (-1); } else if ((pkt->pkt_reason == CMD_INCOMPLETE) && (pkt->pkt_state == 0)) { return (-1); } else if (pkt->pkt_reason != CMD_CMPLT) { return (-1); } else if (((*pkt->pkt_scbp) & STATUS_MASK) == STATUS_BUSY) { return (0); } return (0); } /*ARGSUSED*/ static int scsa1394_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt) { return (0); } static int scsa1394_scsi_reset(struct scsi_address *ap, int level) { scsa1394_state_t *sp = ADDR2STATE(ap); scsa1394_lun_t *lp; int ret; switch (level) { case RESET_ALL: case RESET_TARGET: lp = &sp->s_lun[0]; break; case RESET_LUN: lp = &sp->s_lun[ap->a_lun]; break; default: return (DDI_FAILURE); } ret = scsa1394_sbp2_reset(lp, level, NULL); return ((ret == SBP2_SUCCESS) ? 1 : 0); } /*ARGSUSED*/ static int scsa1394_scsi_getcap(struct scsi_address *ap, char *cap, int whom) { scsa1394_state_t *sp = ADDR2STATE(ap); size_t dev_bsize_cap; int ret = -1; if (!scsa1394_dev_is_online(sp)) { return (-1); } if (cap == NULL) { return (-1); } switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_DMA_MAX: ret = sp->s_attachinfo.dma_attr.dma_attr_maxxfer; break; case SCSI_CAP_SCSI_VERSION: ret = SCSI_VERSION_2; break; case SCSI_CAP_ARQ: ret = 1; break; case SCSI_CAP_UNTAGGED_QING: ret = 1; break; case SCSI_CAP_GEOMETRY: dev_bsize_cap = sp->s_totalsec; if (sp->s_secsz > DEV_BSIZE) { dev_bsize_cap *= sp->s_secsz / DEV_BSIZE; } else if (sp->s_secsz < DEV_BSIZE) { dev_bsize_cap /= DEV_BSIZE / sp->s_secsz; } if (dev_bsize_cap < 65536 * 2 * 18) { /* < ~1GB */ /* unlabeled floppy, 18k per cylinder */ ret = ((2 << 16) | 18); } else if (dev_bsize_cap < 65536 * 64 * 32) { /* < 64GB */ /* 1024k per cylinder */ ret = ((64 << 16) | 32); } else if (dev_bsize_cap < 65536 * 255 * 63) { /* < ~500GB */ /* ~8m per cylinder */ ret = ((255 << 16) | 63); } else { /* .. 8TB */ /* 64m per cylinder */ ret = ((512 << 16) | 256); } break; default: break; } return (ret); } /*ARGSUSED*/ static int scsa1394_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom) { scsa1394_state_t *sp = ADDR2STATE(ap); int ret = -1; if (!scsa1394_dev_is_online(sp)) { return (-1); } switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: ret = 1; break; case SCSI_CAP_DMA_MAX: case SCSI_CAP_SCSI_VERSION: case SCSI_CAP_UNTAGGED_QING: /* supported but not settable */ ret = 0; break; case SCSI_CAP_SECTOR_SIZE: if (value) { sp->s_secsz = value; } break; case SCSI_CAP_TOTAL_SECTORS: if (value) { sp->s_totalsec = value; } break; default: break; } return (ret); } /*ARGSUSED*/ static void scsa1394_scsi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { scsa1394_cmd_t *cmd = PKT2CMD(pkt); if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_VALID) { (void) ddi_dma_sync(cmd->sc_buf_dma_hdl, 0, 0, (cmd->sc_flags & SCSA1394_CMD_READ) ? DDI_DMA_SYNC_FORCPU : DDI_DMA_SYNC_FORDEV); } } /* * * --- pkt resource allocation routines * */ static struct scsi_pkt * scsa1394_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) { scsa1394_state_t *sp = ADDR2STATE(ap); scsa1394_lun_t *lp; scsa1394_cmd_t *cmd; boolean_t is_new; /* new cmd is being allocated */ int kf = (callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP; if (ap->a_lun >= sp->s_nluns) { return (NULL); } lp = &sp->s_lun[ap->a_lun]; /* * allocate cmd space */ if (pkt == NULL) { is_new = B_TRUE; if ((cmd = kmem_cache_alloc(sp->s_cmd_cache, kf)) == NULL) { return (NULL); } /* initialize cmd */ pkt = &cmd->sc_scsi_pkt; pkt->pkt_ha_private = cmd; pkt->pkt_address = *ap; pkt->pkt_private = cmd->sc_priv; pkt->pkt_scbp = (uchar_t *)&cmd->sc_scb; pkt->pkt_cdbp = (uchar_t *)&cmd->sc_pkt_cdb; pkt->pkt_resid = 0; cmd->sc_lun = lp; cmd->sc_pkt = pkt; cmd->sc_cdb_len = cmdlen; cmd->sc_scb_len = statuslen; cmd->sc_priv_len = tgtlen; /* need external space? */ if ((cmdlen > sizeof (cmd->sc_pkt_cdb)) || (statuslen > sizeof (cmd->sc_scb)) || (tgtlen > sizeof (cmd->sc_priv))) { if (scsa1394_cmd_ext_alloc(sp, cmd, kf) != DDI_SUCCESS) { kmem_cache_free(sp->s_cmd_cache, cmd); lp->l_stat.stat_err_pkt_kmem_alloc++; return (NULL); } } /* allocate DMA resources for CDB */ if (scsa1394_cmd_cdb_dma_alloc(sp, cmd, flags, callback, arg) != DDI_SUCCESS) { scsa1394_scsi_destroy_pkt(ap, pkt); return (NULL); } } else { is_new = B_FALSE; cmd = PKT2CMD(pkt); } cmd->sc_flags &= ~SCSA1394_CMD_RDWR; /* allocate/move DMA resources for data buffer */ if ((bp != NULL) && (bp->b_bcount > 0)) { if ((cmd->sc_flags & SCSA1394_CMD_DMA_BUF_VALID) == 0) { if (scsa1394_cmd_buf_dma_alloc(sp, cmd, flags, callback, arg, bp) != DDI_SUCCESS) { if (is_new) { scsa1394_scsi_destroy_pkt(ap, pkt); } return (NULL); } } else { if (scsa1394_cmd_buf_dma_move(sp, cmd) != DDI_SUCCESS) { return (NULL); } } ASSERT(cmd->sc_win_len > 0); pkt->pkt_resid = bp->b_bcount - cmd->sc_win_len; } /* * kernel virtual address may be required for certain workarounds * and in case of B_PHYS or B_PAGEIO, bp_mapin() will get it for us */ if ((bp != NULL) && ((bp->b_flags & (B_PAGEIO | B_PHYS)) != 0) && (bp->b_bcount < SCSA1394_MAPIN_SIZE_MAX) && ((cmd->sc_flags & SCSA1394_CMD_DMA_BUF_MAPIN) == 0)) { bp_mapin(bp); cmd->sc_flags |= SCSA1394_CMD_DMA_BUF_MAPIN; } return (pkt); } static void scsa1394_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { scsa1394_state_t *sp = ADDR2STATE(ap); scsa1394_cmd_t *cmd = PKT2CMD(pkt); if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_VALID) { scsa1394_cmd_buf_dma_free(sp, cmd); } if (cmd->sc_flags & SCSA1394_CMD_DMA_CDB_VALID) { scsa1394_cmd_cdb_dma_free(sp, cmd); } if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_MAPIN) { bp_mapout(cmd->sc_bp); cmd->sc_flags &= ~SCSA1394_CMD_DMA_BUF_MAPIN; } if (cmd->sc_flags & SCSA1394_CMD_EXT) { scsa1394_cmd_ext_free(sp, cmd); } kmem_cache_free(sp->s_cmd_cache, cmd); } static void scsa1394_scsi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { scsa1394_state_t *sp = ADDR2STATE(ap); scsa1394_cmd_t *cmd = PKT2CMD(pkt); if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_VALID) { scsa1394_cmd_buf_dma_free(sp, cmd); } if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_MAPIN) { bp_mapout(cmd->sc_bp); cmd->sc_flags &= ~SCSA1394_CMD_DMA_BUF_MAPIN; } } /*ARGSUSED*/ static int scsa1394_cmd_cache_constructor(void *buf, void *cdrarg, int kf) { scsa1394_cmd_t *cmd = buf; bzero(buf, SCSA1394_CMD_SIZE); cmd->sc_task.ts_drv_priv = cmd; return (0); } /*ARGSUSED*/ static void scsa1394_cmd_cache_destructor(void *buf, void *cdrarg) { } /* * allocate and deallocate external cmd space (ie. not part of scsa1394_cmd_t) * for non-standard length cdb, pkt_private, status areas */ static int scsa1394_cmd_ext_alloc(scsa1394_state_t *sp, scsa1394_cmd_t *cmd, int kf) { struct scsi_pkt *pkt = cmd->sc_pkt; void *buf; if (cmd->sc_cdb_len > sizeof (cmd->sc_pkt_cdb)) { if ((buf = kmem_zalloc(cmd->sc_cdb_len, kf)) == NULL) { return (DDI_FAILURE); } pkt->pkt_cdbp = buf; cmd->sc_flags |= SCSA1394_CMD_CDB_EXT; } if (cmd->sc_scb_len > sizeof (cmd->sc_scb)) { if ((buf = kmem_zalloc(cmd->sc_scb_len, kf)) == NULL) { scsa1394_cmd_ext_free(sp, cmd); return (DDI_FAILURE); } pkt->pkt_scbp = buf; cmd->sc_flags |= SCSA1394_CMD_SCB_EXT; } if (cmd->sc_priv_len > sizeof (cmd->sc_priv)) { if ((buf = kmem_zalloc(cmd->sc_priv_len, kf)) == NULL) { scsa1394_cmd_ext_free(sp, cmd); return (DDI_FAILURE); } pkt->pkt_private = buf; cmd->sc_flags |= SCSA1394_CMD_PRIV_EXT; } return (DDI_SUCCESS); } /*ARGSUSED*/ static void scsa1394_cmd_ext_free(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { struct scsi_pkt *pkt = cmd->sc_pkt; if (cmd->sc_flags & SCSA1394_CMD_CDB_EXT) { kmem_free(pkt->pkt_cdbp, cmd->sc_cdb_len); } if (cmd->sc_flags & SCSA1394_CMD_SCB_EXT) { kmem_free(pkt->pkt_scbp, cmd->sc_scb_len); } if (cmd->sc_flags & SCSA1394_CMD_PRIV_EXT) { kmem_free(pkt->pkt_private, cmd->sc_priv_len); } cmd->sc_flags &= ~SCSA1394_CMD_EXT; } /*ARGSUSED*/ static int scsa1394_cmd_cdb_dma_alloc(scsa1394_state_t *sp, scsa1394_cmd_t *cmd, int flags, int (*callback)(), caddr_t arg) { if (sbp2_task_orb_alloc(cmd->sc_lun->l_lun, &cmd->sc_task, sizeof (scsa1394_cmd_orb_t)) != SBP2_SUCCESS) { return (DDI_FAILURE); } cmd->sc_flags |= SCSA1394_CMD_DMA_CDB_VALID; return (DDI_SUCCESS); } /*ARGSUSED*/ static void scsa1394_cmd_cdb_dma_free(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { sbp2_task_orb_free(cmd->sc_lun->l_lun, &cmd->sc_task); cmd->sc_flags &= ~SCSA1394_CMD_DMA_CDB_VALID; } /* * buffer resources */ static int scsa1394_cmd_buf_dma_alloc(scsa1394_state_t *sp, scsa1394_cmd_t *cmd, int flags, int (*callback)(), caddr_t arg, struct buf *bp) { scsa1394_lun_t *lp = cmd->sc_lun; int kf = (callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP; int dma_flags; ddi_dma_cookie_t dmac; uint_t ccount; int error; int ret; cmd->sc_bp = bp; if ((ddi_dma_alloc_handle(sp->s_dip, &sp->s_buf_dma_attr, callback, NULL, &cmd->sc_buf_dma_hdl)) != DDI_SUCCESS) { bioerror(bp, 0); return (DDI_FAILURE); } cmd->sc_flags &= ~SCSA1394_CMD_RDWR; if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; cmd->sc_flags |= SCSA1394_CMD_READ; } else { dma_flags = DDI_DMA_WRITE; cmd->sc_flags |= SCSA1394_CMD_WRITE; } if (flags & PKT_CONSISTENT) { dma_flags |= DDI_DMA_CONSISTENT; } if (flags & PKT_DMA_PARTIAL) { dma_flags |= DDI_DMA_PARTIAL; } ret = ddi_dma_buf_bind_handle(cmd->sc_buf_dma_hdl, bp, dma_flags, callback, arg, &dmac, &ccount); switch (ret) { case DDI_DMA_MAPPED: cmd->sc_nwin = 1; cmd->sc_curwin = 0; cmd->sc_win_offset = 0; cmd->sc_win_len = bp->b_bcount; break; case DDI_DMA_PARTIAL_MAP: /* retrieve number of windows and first window cookie */ cmd->sc_curwin = 0; if ((ddi_dma_numwin(cmd->sc_buf_dma_hdl, &cmd->sc_nwin) != DDI_SUCCESS) || (ddi_dma_getwin(cmd->sc_buf_dma_hdl, cmd->sc_curwin, &cmd->sc_win_offset, &cmd->sc_win_len, &dmac, &ccount) != DDI_SUCCESS)) { (void) ddi_dma_unbind_handle(cmd->sc_buf_dma_hdl); ddi_dma_free_handle(&cmd->sc_buf_dma_hdl); return (DDI_FAILURE); } lp->l_stat.stat_cmd_buf_dma_partial++; break; case DDI_DMA_NORESOURCES: error = 0; goto map_error; case DDI_DMA_BADATTR: case DDI_DMA_NOMAPPING: error = EFAULT; goto map_error; default: error = EINVAL; map_error: bioerror(bp, error); lp->l_stat.stat_err_cmd_buf_dbind++; ddi_dma_free_handle(&cmd->sc_buf_dma_hdl); return (DDI_FAILURE); } cmd->sc_flags |= SCSA1394_CMD_DMA_BUF_BIND_VALID; /* * setup page table if needed */ if ((ccount == 1) && (dmac.dmac_size <= SBP2_PT_SEGSIZE_MAX) && (!sp->s_symbios || (dmac.dmac_size <= scsa1394_symbios_page_size))) { cmd->sc_buf_nsegs = 1; cmd->sc_buf_seg_mem.ss_len = dmac.dmac_size; cmd->sc_buf_seg_mem.ss_daddr = dmac.dmac_address; cmd->sc_buf_seg = &cmd->sc_buf_seg_mem; } else { /* break window into segments */ if (scsa1394_cmd_dmac2seg(sp, cmd, &dmac, ccount, kf) != DDI_SUCCESS) { scsa1394_cmd_buf_dma_free(sp, cmd); bioerror(bp, 0); return (DDI_FAILURE); } /* allocate DMA resources for page table */ if (scsa1394_cmd_pt_dma_alloc(sp, cmd, callback, arg, cmd->sc_buf_nsegs) != DDI_SUCCESS) { scsa1394_cmd_buf_dma_free(sp, cmd); bioerror(bp, 0); return (DDI_FAILURE); } } /* allocate 1394 addresses for segments */ if (scsa1394_cmd_buf_addr_alloc(sp, cmd) != DDI_SUCCESS) { scsa1394_cmd_buf_dma_free(sp, cmd); bioerror(bp, 0); return (DDI_FAILURE); } return (DDI_SUCCESS); } static void scsa1394_cmd_buf_dma_free(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { scsa1394_cmd_buf_addr_free(sp, cmd); if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_PT_VALID) { scsa1394_cmd_pt_dma_free(sp, cmd); } scsa1394_cmd_seg_free(sp, cmd); if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_BIND_VALID) { (void) ddi_dma_unbind_handle(cmd->sc_buf_dma_hdl); ddi_dma_free_handle(&cmd->sc_buf_dma_hdl); } cmd->sc_flags &= ~(SCSA1394_CMD_DMA_BUF_VALID | SCSA1394_CMD_RDWR); } /* * Break a set DMA cookies into segments suitable for SBP-2 page table. * This routine can reuse/reallocate segment array from previous calls. */ static int scsa1394_cmd_dmac2seg(scsa1394_state_t *sp, scsa1394_cmd_t *cmd, ddi_dma_cookie_t *dmac, uint_t ccount, int kf) { scsa1394_lun_t *lp = cmd->sc_lun; int i; int nsegs; size_t segsize_max; size_t dmac_resid; uint32_t dmac_addr; scsa1394_cmd_seg_t *seg; if (!sp->s_symbios) { /* * Number of segments is unknown at this point. Start with * a reasonable estimate and grow it later if needed. */ nsegs = max(ccount, cmd->sc_win_len / SBP2_PT_SEGSIZE_MAX) * 2; segsize_max = SBP2_PT_SEGSIZE_MAX; } else { /* * For Symbios workaround we know exactly the number of segments * Additional segment may be needed if buffer is not aligned. */ nsegs = howmany(cmd->sc_win_len, scsa1394_symbios_page_size) + 1; segsize_max = scsa1394_symbios_page_size; } if (nsegs > cmd->sc_buf_nsegs_alloc) { if ((cmd->sc_buf_seg = scsa1394_kmem_realloc(cmd->sc_buf_seg, cmd->sc_buf_nsegs_alloc, nsegs, sizeof (scsa1394_cmd_seg_t), kf)) == NULL) { cmd->sc_buf_nsegs_alloc = 0; return (DDI_FAILURE); } cmd->sc_buf_nsegs_alloc = nsegs; } /* each cookie maps into one or more segments */ cmd->sc_buf_nsegs = 0; i = ccount; for (;;) { dmac_resid = dmac->dmac_size; dmac_addr = dmac->dmac_address; while (dmac_resid > 0) { /* grow array if needed */ if (cmd->sc_buf_nsegs >= cmd->sc_buf_nsegs_alloc) { if ((cmd->sc_buf_seg = scsa1394_kmem_realloc( cmd->sc_buf_seg, cmd->sc_buf_nsegs_alloc, cmd->sc_buf_nsegs_alloc + ccount, sizeof (scsa1394_cmd_seg_t), kf)) == NULL) { return (DDI_FAILURE); } cmd->sc_buf_nsegs_alloc += ccount; } seg = &cmd->sc_buf_seg[cmd->sc_buf_nsegs]; seg->ss_len = min(dmac_resid, segsize_max); seg->ss_daddr = (uint64_t)dmac_addr; dmac_addr += seg->ss_len; dmac_resid -= seg->ss_len; cmd->sc_buf_nsegs++; } ASSERT(dmac_resid == 0); /* grab next cookie */ if (--i <= 0) { break; } ddi_dma_nextcookie(cmd->sc_buf_dma_hdl, dmac); } if (cmd->sc_buf_nsegs > lp->l_stat.stat_cmd_buf_max_nsegs) { lp->l_stat.stat_cmd_buf_max_nsegs = cmd->sc_buf_nsegs; } return (DDI_SUCCESS); } /*ARGSUSED*/ static void scsa1394_cmd_seg_free(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { if (cmd->sc_buf_nsegs_alloc > 0) { kmem_free(cmd->sc_buf_seg, cmd->sc_buf_nsegs_alloc * sizeof (scsa1394_cmd_seg_t)); } cmd->sc_buf_seg = NULL; cmd->sc_buf_nsegs = 0; cmd->sc_buf_nsegs_alloc = 0; } static int scsa1394_cmd_pt_dma_alloc(scsa1394_state_t *sp, scsa1394_cmd_t *cmd, int (*callback)(), caddr_t arg, int cnt) { scsa1394_lun_t *lp = cmd->sc_lun; size_t len, rlen; uint_t ccount; t1394_alloc_addr_t aa; int result; /* allocate DMA memory for page table */ if ((ddi_dma_alloc_handle(sp->s_dip, &sp->s_pt_dma_attr, callback, NULL, &cmd->sc_pt_dma_hdl)) != DDI_SUCCESS) { lp->l_stat.stat_err_cmd_pt_dmem_alloc++; return (DDI_FAILURE); } cmd->sc_pt_ent_alloc = cnt; len = cmd->sc_pt_ent_alloc * SBP2_PT_ENT_SIZE; if (ddi_dma_mem_alloc(cmd->sc_pt_dma_hdl, len, &sp->s_attachinfo.acc_attr, DDI_DMA_CONSISTENT, callback, arg, &cmd->sc_pt_kaddr, &rlen, &cmd->sc_pt_acc_hdl) != DDI_SUCCESS) { ddi_dma_free_handle(&cmd->sc_pt_dma_hdl); lp->l_stat.stat_err_cmd_pt_dmem_alloc++; return (DDI_FAILURE); } if (ddi_dma_addr_bind_handle(cmd->sc_pt_dma_hdl, NULL, cmd->sc_pt_kaddr, len, DDI_DMA_READ | DDI_DMA_CONSISTENT, callback, arg, &cmd->sc_pt_dmac, &ccount) != DDI_DMA_MAPPED) { ddi_dma_mem_free(&cmd->sc_pt_acc_hdl); ddi_dma_free_handle(&cmd->sc_pt_dma_hdl); lp->l_stat.stat_err_cmd_pt_dmem_alloc++; return (DDI_FAILURE); } ASSERT(ccount == 1); /* because dma_attr_sgllen is 1 */ /* allocate 1394 address for page table */ aa.aa_type = T1394_ADDR_FIXED; aa.aa_length = len; aa.aa_address = cmd->sc_pt_dmac.dmac_address; aa.aa_evts.recv_read_request = NULL; aa.aa_evts.recv_write_request = NULL; aa.aa_evts.recv_lock_request = NULL; aa.aa_arg = NULL; aa.aa_kmem_bufp = NULL; aa.aa_enable = T1394_ADDR_RDENBL; if (t1394_alloc_addr(sp->s_t1394_hdl, &aa, 0, &result) != DDI_SUCCESS) { (void) ddi_dma_unbind_handle(cmd->sc_pt_dma_hdl); ddi_dma_mem_free(&cmd->sc_pt_acc_hdl); ddi_dma_free_handle(&cmd->sc_pt_dma_hdl); lp->l_stat.stat_err_cmd_pt_addr_alloc++; return (DDI_FAILURE); } ASSERT(aa.aa_address != 0); cmd->sc_pt_baddr = aa.aa_address; cmd->sc_pt_addr_hdl = aa.aa_hdl; cmd->sc_flags |= SCSA1394_CMD_DMA_BUF_PT_VALID; return (DDI_SUCCESS); } static void scsa1394_cmd_pt_dma_free(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { (void) ddi_dma_unbind_handle(cmd->sc_pt_dma_hdl); ddi_dma_mem_free(&cmd->sc_pt_acc_hdl); ddi_dma_free_handle(&cmd->sc_pt_dma_hdl); (void) t1394_free_addr(sp->s_t1394_hdl, &cmd->sc_pt_addr_hdl, 0); cmd->sc_flags &= ~SCSA1394_CMD_DMA_BUF_PT_VALID; } /* * allocate 1394 addresses for all buffer segments */ static int scsa1394_cmd_buf_addr_alloc(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { scsa1394_lun_t *lp = cmd->sc_lun; t1394_alloc_addr_t aa; scsa1394_cmd_seg_t *seg; int result; int i; aa.aa_type = T1394_ADDR_FIXED; aa.aa_evts.recv_read_request = NULL; aa.aa_evts.recv_write_request = NULL; aa.aa_evts.recv_lock_request = NULL; aa.aa_arg = NULL; aa.aa_kmem_bufp = NULL; if (cmd->sc_flags & SCSA1394_CMD_READ) { aa.aa_enable = T1394_ADDR_RDENBL; } else { aa.aa_enable = T1394_ADDR_WRENBL; } for (i = 0; i < cmd->sc_buf_nsegs; i++) { seg = &cmd->sc_buf_seg[i]; /* segment bus address */ aa.aa_length = seg->ss_len; aa.aa_address = seg->ss_daddr; if (t1394_alloc_addr(sp->s_t1394_hdl, &aa, 0, &result) != DDI_SUCCESS) { lp->l_stat.stat_err_cmd_buf_addr_alloc++; return (DDI_FAILURE); } ASSERT(aa.aa_address != 0); seg->ss_baddr = aa.aa_address; seg->ss_addr_hdl = aa.aa_hdl; } cmd->sc_flags |= SCSA1394_CMD_DMA_BUF_ADDR_VALID; return (DDI_SUCCESS); } static void scsa1394_cmd_buf_addr_free(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { int i; for (i = 0; i < cmd->sc_buf_nsegs; i++) { if (cmd->sc_buf_seg[i].ss_addr_hdl) { (void) t1394_free_addr(sp->s_t1394_hdl, &cmd->sc_buf_seg[i].ss_addr_hdl, 0); } } cmd->sc_flags &= ~SCSA1394_CMD_DMA_BUF_ADDR_VALID; } /* * move to next DMA window */ static int scsa1394_cmd_buf_dma_move(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { /* scsa1394_lun_t *lp = cmd->sc_lun; */ ddi_dma_cookie_t dmac; uint_t ccount; /* for small pkts, leave things where they are (says WDD) */ if ((cmd->sc_curwin == cmd->sc_nwin) && (cmd->sc_nwin == 1)) { return (DDI_SUCCESS); } if (++cmd->sc_curwin >= cmd->sc_nwin) { return (DDI_FAILURE); } if (ddi_dma_getwin(cmd->sc_buf_dma_hdl, cmd->sc_curwin, &cmd->sc_win_offset, &cmd->sc_win_len, &dmac, &ccount) != DDI_SUCCESS) { return (DDI_FAILURE); } scsa1394_cmd_buf_addr_free(sp, cmd); /* * setup page table if needed */ if ((ccount == 1) && (dmac.dmac_size <= SBP2_PT_SEGSIZE_MAX) && (!sp->s_symbios || (dmac.dmac_size <= scsa1394_symbios_page_size))) { /* but first, free old resources */ if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_PT_VALID) { scsa1394_cmd_pt_dma_free(sp, cmd); } scsa1394_cmd_seg_free(sp, cmd); cmd->sc_buf_nsegs = 1; cmd->sc_buf_seg_mem.ss_len = dmac.dmac_size; cmd->sc_buf_seg_mem.ss_daddr = dmac.dmac_address; cmd->sc_buf_seg = &cmd->sc_buf_seg_mem; } else { /* break window into segments */ if (scsa1394_cmd_dmac2seg(sp, cmd, &dmac, ccount, KM_NOSLEEP) != DDI_SUCCESS) { return (DDI_FAILURE); } /* allocate DMA resources */ if (scsa1394_cmd_pt_dma_alloc(sp, cmd, NULL_FUNC, NULL, cmd->sc_buf_nsegs) != DDI_SUCCESS) { return (DDI_FAILURE); } } /* allocate 1394 addresses for segments */ if (scsa1394_cmd_buf_addr_alloc(sp, cmd) != DDI_SUCCESS) { return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * * --- pkt and data transfer routines * */ static int scsa1394_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt) { scsa1394_state_t *sp = ADDR2STATE(ap); scsa1394_cmd_t *cmd = PKT2CMD(pkt); scsa1394_lun_t *lp = cmd->sc_lun; int ret; /* * since we don't support polled I/O, just accept the packet * so the rest of the file systems get synced properly */ if (ddi_in_panic()) { scsa1394_prepare_pkt(sp, pkt); return (TRAN_ACCEPT); } /* polling not supported yet */ if (pkt->pkt_flags & FLAG_NOINTR) { return (TRAN_BADPKT); } mutex_enter(&sp->s_mutex); if (sp->s_dev_state != SCSA1394_DEV_ONLINE) { /* * If device is temporarily gone due to bus reset, * return busy to prevent prevent scary console messages. * If permanently gone, leave it to scsa1394_cmd_fake_comp(). */ if (sp->s_dev_state == SCSA1394_DEV_BUS_RESET) { mutex_exit(&sp->s_mutex); return (TRAN_BUSY); } } mutex_exit(&sp->s_mutex); if ((ap->a_lun >= sp->s_nluns) || (ap->a_lun != pkt->pkt_address.a_lun)) { return (TRAN_BADPKT); } scsa1394_prepare_pkt(sp, pkt); /* some commands may require fake completion */ if ((ret = scsa1394_cmd_fake_comp(sp, cmd)) == DDI_SUCCESS) { return (TRAN_ACCEPT); } scsa1394_cmd_fill_cdb(lp, cmd); if (cmd->sc_flags & SCSA1394_CMD_DMA_BUF_PT_VALID) { scsa1394_sbp2_seg2pt(lp, cmd); } scsa1394_sbp2_cmd2orb(lp, cmd); /* convert into ORB */ if ((ret = scsa1394_sbp2_start(lp, cmd)) != TRAN_BUSY) { scsa1394_sbp2_nudge(lp); } return (ret); } /*ARGSUSED*/ static void scsa1394_prepare_pkt(scsa1394_state_t *sp, struct scsi_pkt *pkt) { scsa1394_cmd_t *cmd = PKT2CMD(pkt); pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state = 0; pkt->pkt_statistics = 0; *(pkt->pkt_scbp) = STATUS_GOOD; if (cmd) { cmd->sc_timeout = pkt->pkt_time; /* workarounds */ switch (pkt->pkt_cdbp[0]) { /* * sd does START_STOP_UNIT during attach with a 200 sec timeout. * at this time devi_lock is held, prtconf will be stuck. * reduce timeout for the time being. */ case SCMD_START_STOP: cmd->sc_timeout = min(cmd->sc_timeout, scsa1394_start_stop_timeout_max); break; default: break; } } } static void scsa1394_cmd_fill_cdb(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { cmd->sc_cdb_actual_len = cmd->sc_cdb_len; mutex_enter(&lp->l_mutex); switch (lp->l_dtype_orig) { case DTYPE_DIRECT: case DTYPE_RODIRECT: case DTYPE_OPTICAL: case SCSA1394_DTYPE_RBC: scsa1394_cmd_fill_cdb_rbc(lp, cmd); break; default: scsa1394_cmd_fill_cdb_other(lp, cmd); break; } mutex_exit(&lp->l_mutex); } static void scsa1394_cmd_fill_cdb_rbc(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { scsa1394_state_t *sp = lp->l_sp; struct scsi_pkt *pkt = CMD2PKT(cmd); int lba, opcode; struct buf *bp = cmd->sc_bp; size_t len; size_t blk_size; int sz; opcode = pkt->pkt_cdbp[0]; blk_size = lp->l_lba_size; switch (opcode) { case SCMD_READ: /* RBC only supports 10-byte read/write */ lba = SCSA1394_LBA_6BYTE(pkt); len = SCSA1394_LEN_6BYTE(pkt); opcode = SCMD_READ_G1; cmd->sc_cdb_actual_len = CDB_GROUP1; break; case SCMD_WRITE: lba = SCSA1394_LBA_6BYTE(pkt); len = SCSA1394_LEN_6BYTE(pkt); opcode = SCMD_WRITE_G1; cmd->sc_cdb_actual_len = CDB_GROUP1; break; case SCMD_READ_G1: case SCMD_READ_LONG: lba = SCSA1394_LBA_10BYTE(pkt); len = SCSA1394_LEN_10BYTE(pkt); break; case SCMD_WRITE_G1: case SCMD_WRITE_LONG: lba = SCSA1394_LBA_10BYTE(pkt); len = SCSA1394_LEN_10BYTE(pkt); if ((lp->l_dtype_orig == DTYPE_RODIRECT) && (bp != NULL) && (len != 0)) { sz = SCSA1394_CDRW_BLKSZ(bp->b_bcount, len); if (SCSA1394_VALID_CDRW_BLKSZ(sz)) { blk_size = sz; } } break; case SCMD_READ_CD: lba = SCSA1394_LBA_10BYTE(pkt); len = SCSA1394_LEN_READ_CD(pkt); blk_size = scsa1394_cmd_read_cd_blk_size(pkt->pkt_cdbp[1] >> 2); break; case SCMD_READ_G5: lba = SCSA1394_LBA_12BYTE(pkt); len = SCSA1394_LEN_12BYTE(pkt); break; case SCMD_WRITE_G5: lba = SCSA1394_LBA_12BYTE(pkt); len = SCSA1394_LEN_12BYTE(pkt); break; default: /* no special mapping for other commands */ scsa1394_cmd_fill_cdb_other(lp, cmd); return; } cmd->sc_blk_size = blk_size; /* limit xfer length for Symbios workaround */ if (sp->s_symbios && (len * blk_size > scsa1394_symbios_size_max)) { cmd->sc_flags |= SCSA1394_CMD_SYMBIOS_BREAKUP; cmd->sc_total_blks = cmd->sc_resid_blks = len; len = scsa1394_symbios_size_max / blk_size; } cmd->sc_xfer_blks = len; cmd->sc_xfer_bytes = len * blk_size; /* finalize new CDB */ switch (pkt->pkt_cdbp[0]) { case SCMD_READ: case SCMD_WRITE: /* * We rewrite READ/WRITE G0 commands as READ/WRITE G1. * Build new cdb from scatch. * The lba and length fields is updated below. */ bzero(cmd->sc_cdb, cmd->sc_cdb_actual_len); break; default: /* * Copy the non lba/len fields. * The lba and length fields is updated below. */ bcopy(pkt->pkt_cdbp, cmd->sc_cdb, cmd->sc_cdb_actual_len); break; } cmd->sc_cdb[0] = (uchar_t)opcode; scsa1394_cmd_fill_cdb_lba(cmd, lba); switch (opcode) { case SCMD_READ_CD: scsa1394_cmd_fill_read_cd_cdb_len(cmd, len); break; case SCMD_WRITE_G5: case SCMD_READ_G5: scsa1394_cmd_fill_12byte_cdb_len(cmd, len); break; default: scsa1394_cmd_fill_cdb_len(cmd, len); break; } } /*ARGSUSED*/ static void scsa1394_cmd_fill_cdb_other(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { struct scsi_pkt *pkt = CMD2PKT(cmd); cmd->sc_xfer_bytes = cmd->sc_win_len; cmd->sc_xfer_blks = cmd->sc_xfer_bytes / lp->l_lba_size; cmd->sc_total_blks = cmd->sc_xfer_blks; cmd->sc_lba = 0; bcopy(pkt->pkt_cdbp, cmd->sc_cdb, cmd->sc_cdb_len); } /* * fill up parts of CDB */ static void scsa1394_cmd_fill_cdb_len(scsa1394_cmd_t *cmd, int len) { cmd->sc_cdb[7] = len >> 8; cmd->sc_cdb[8] = (uchar_t)len; } static void scsa1394_cmd_fill_cdb_lba(scsa1394_cmd_t *cmd, int lba) { cmd->sc_cdb[2] = lba >> 24; cmd->sc_cdb[3] = lba >> 16; cmd->sc_cdb[4] = lba >> 8; cmd->sc_cdb[5] = (uchar_t)lba; cmd->sc_lba = lba; } static void scsa1394_cmd_fill_12byte_cdb_len(scsa1394_cmd_t *cmd, int len) { cmd->sc_cdb[6] = len >> 24; cmd->sc_cdb[7] = len >> 16; cmd->sc_cdb[8] = len >> 8; cmd->sc_cdb[9] = (uchar_t)len; } static void scsa1394_cmd_fill_read_cd_cdb_len(scsa1394_cmd_t *cmd, int len) { cmd->sc_cdb[6] = len >> 16; cmd->sc_cdb[7] = len >> 8; cmd->sc_cdb[8] = (uchar_t)len; } /* * For SCMD_READ_CD, figure out the block size based on expected sector type. * See MMC SCSI Specs section 6.1.15 */ static int scsa1394_cmd_read_cd_blk_size(uchar_t expected_sector_type) { int blk_size; switch (expected_sector_type) { case READ_CD_EST_CDDA: blk_size = CDROM_BLK_2352; break; case READ_CD_EST_MODE2: blk_size = CDROM_BLK_2336; break; case READ_CD_EST_MODE2FORM2: blk_size = CDROM_BLK_2324; break; case READ_CD_EST_MODE2FORM1: case READ_CD_EST_ALLTYPE: case READ_CD_EST_MODE1: default: blk_size = CDROM_BLK_2048; } return (blk_size); } /*ARGSUSED*/ static int scsa1394_cmd_fake_mode_sense(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { struct scsi_pkt *pkt = CMD2PKT(cmd); struct scsi_arq_status *arqp = (struct scsi_arq_status *)pkt->pkt_scbp; struct scsi_extended_sense *esp = &arqp->sts_sensedata; *(pkt->pkt_scbp) = STATUS_CHECK; *(uint8_t *)&arqp->sts_rqpkt_status = STATUS_GOOD; arqp->sts_rqpkt_reason = CMD_CMPLT; arqp->sts_rqpkt_resid = 0; arqp->sts_rqpkt_state |= STATE_XFERRED_DATA; arqp->sts_rqpkt_statistics = 0; bzero(esp, sizeof (struct scsi_extended_sense)); esp->es_class = CLASS_EXTENDED_SENSE; esp->es_key = KEY_ILLEGAL_REQUEST; pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS); if (pkt->pkt_comp) { (*pkt->pkt_comp)(pkt); } return (DDI_SUCCESS); } /*ARGSUSED*/ static int scsa1394_cmd_fake_inquiry(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { scsa1394_lun_t *lp = cmd->sc_lun; struct scsi_pkt *pkt = CMD2PKT(cmd); struct scsi_inquiry *inq; /* copy fabricated inquiry data */ inq = (struct scsi_inquiry *)cmd->sc_bp->b_un.b_addr; bcopy(&lp->l_fake_inq, inq, sizeof (struct scsi_inquiry)); pkt->pkt_resid -= sizeof (struct scsi_inquiry); pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS); if (pkt->pkt_comp) { (*pkt->pkt_comp)(pkt); } return (DDI_SUCCESS); } /* * If command allows fake completion (without actually being transported), * call completion callback and return DDI_SUCCESS. * Otherwise return DDI_FAILURE. */ static int scsa1394_cmd_fake_comp(scsa1394_state_t *sp, scsa1394_cmd_t *cmd) { struct scsi_pkt *pkt = CMD2PKT(cmd); scsa1394_lun_t *lp = cmd->sc_lun; int ret = DDI_SUCCESS; /* * agreement with sd in case of device hot removal * is to fake completion with CMD_DEV_GONE */ mutex_enter(&sp->s_mutex); if (sp->s_dev_state != SCSA1394_DEV_ONLINE) { mutex_exit(&sp->s_mutex); pkt->pkt_reason = CMD_DEV_GONE; if (pkt->pkt_comp) { (*pkt->pkt_comp)(pkt); } return (DDI_SUCCESS); } mutex_exit(&sp->s_mutex); mutex_enter(&lp->l_mutex); switch (pkt->pkt_cdbp[0]) { /* * RBC support for PRIN/PROUT is optional */ case SCMD_PRIN: case SCMD_PROUT: if (!scsa1394_wrka_fake_prin) { ret = DDI_FAILURE; } break; /* * Some fixed disks don't like doorlock cmd. And they don't need it. */ case SCMD_DOORLOCK: if (lp->l_rmb_orig != 0) { ret = DDI_FAILURE; } break; case SCMD_TEST_UNIT_READY: if (!lp->l_nosup_tur) { ret = DDI_FAILURE; } break; case SCMD_START_STOP: if (!lp->l_nosup_start_stop) { ret = DDI_FAILURE; } break; case SCMD_INQUIRY: if (!lp->l_nosup_inquiry) { ret = DDI_FAILURE; } else { mutex_exit(&lp->l_mutex); return (scsa1394_cmd_fake_inquiry(sp, cmd)); } break; case SCMD_MODE_SENSE: if (!lp->l_mode_sense_fake) { ret = DDI_FAILURE; } else { mutex_exit(&lp->l_mutex); return (scsa1394_cmd_fake_mode_sense(sp, cmd)); } break; default: ret = DDI_FAILURE; } mutex_exit(&lp->l_mutex); if (ret != DDI_SUCCESS) { return (ret); } ASSERT(*(pkt->pkt_scbp) == STATUS_GOOD); ASSERT(pkt->pkt_reason == CMD_CMPLT); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS); if (pkt->pkt_comp) { (*pkt->pkt_comp)(pkt); } return (DDI_SUCCESS); } /* * Returns DDI_SUCCESS if next xfer setup successfully, DDI_FAILURE otherwise. */ static int scsa1394_cmd_setup_next_xfer(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { struct scsi_pkt *pkt = CMD2PKT(cmd); ASSERT(cmd->sc_flags & SCSA1394_CMD_SYMBIOS_BREAKUP); cmd->sc_resid_blks -= cmd->sc_xfer_blks; if (cmd->sc_resid_blks <= 0) { pkt->pkt_resid = 0; return (DDI_FAILURE); } scsa1394_cmd_adjust_cdb(lp, cmd); scsa1394_sbp2_seg2pt(lp, cmd); scsa1394_sbp2_cmd2orb(lp, cmd); if (scsa1394_sbp2_start(lp, cmd) != TRAN_ACCEPT) { pkt->pkt_resid = cmd->sc_resid_blks * cmd->sc_blk_size; return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * new lba = current lba + previous xfer len */ /*ARGSUSED*/ static void scsa1394_cmd_adjust_cdb(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { int len; ASSERT(cmd->sc_flags & SCSA1394_CMD_SYMBIOS_BREAKUP); cmd->sc_lba += cmd->sc_xfer_blks; len = cmd->sc_resid_blks; /* limit xfer length for Symbios workaround */ if (len * cmd->sc_blk_size > scsa1394_symbios_size_max) { len = scsa1394_symbios_size_max / cmd->sc_blk_size; } switch (cmd->sc_cdb[0]) { case SCMD_READ_CD: scsa1394_cmd_fill_read_cd_cdb_len(cmd, len); break; case SCMD_WRITE_G5: case SCMD_READ_G5: scsa1394_cmd_fill_12byte_cdb_len(cmd, len); break; case SCMD_WRITE_G1: case SCMD_WRITE_LONG: default: scsa1394_cmd_fill_cdb_len(cmd, len); } scsa1394_cmd_fill_cdb_lba(cmd, cmd->sc_lba); cmd->sc_xfer_blks = len; cmd->sc_xfer_bytes = len * cmd->sc_blk_size; } void scsa1394_cmd_status_proc(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { struct scsi_pkt *pkt = CMD2PKT(cmd); /* next iteration of partial xfer? */ if ((pkt->pkt_reason == CMD_CMPLT) && (cmd->sc_flags & SCSA1394_CMD_SYMBIOS_BREAKUP)) { if (scsa1394_cmd_setup_next_xfer(lp, cmd) == DDI_SUCCESS) { return; } } cmd->sc_flags &= ~SCSA1394_CMD_SYMBIOS_BREAKUP; /* apply workarounds */ if (pkt->pkt_reason == CMD_CMPLT) { scsa1394_cmd_status_wrka(lp, cmd); } mutex_enter(&lp->l_mutex); /* mode sense workaround */ if (pkt->pkt_cdbp[0] == SCMD_MODE_SENSE) { if (pkt->pkt_reason == CMD_CMPLT) { lp->l_mode_sense_fail_cnt = 0; } else if (++lp->l_mode_sense_fail_cnt >= scsa1394_mode_sense_fail_max) { lp->l_mode_sense_fake = B_TRUE; } } else { lp->l_mode_sense_fail_cnt = 0; } mutex_exit(&lp->l_mutex); if (pkt->pkt_comp) { (*pkt->pkt_comp)(pkt); } } static void scsa1394_cmd_status_wrka(scsa1394_lun_t *lp, scsa1394_cmd_t *cmd) { struct scsi_pkt *pkt = CMD2PKT(cmd); mutex_enter(&lp->l_mutex); switch (pkt->pkt_cdbp[0]) { case SCMD_INQUIRY: { struct scsi_inquiry *inq; inq = (struct scsi_inquiry *)cmd->sc_bp->b_un.b_addr; /* change dtype RBC to DIRECT, sd doesn't support RBC */ lp->l_dtype_orig = inq->inq_dtype; if ((inq->inq_dtype == SCSA1394_DTYPE_RBC) && scsa1394_wrka_rbc2direct) { inq->inq_dtype = DTYPE_DIRECT; } /* force RMB to 1 */ lp->l_rmb_orig = inq->inq_rmb; if (scsa1394_wrka_fake_rmb) { inq->inq_rmb = 1; } break; } case SCMD_READ_CAPACITY: { uint32_t *capacity_buf; capacity_buf = (uint32_t *)cmd->sc_bp->b_un.b_addr; if (lp->l_dtype_orig != DTYPE_RODIRECT) { lp->l_lba_size = min(BE_32(capacity_buf[1]), DEV_BSIZE); if (lp->l_lba_size == 0) { cmn_err(CE_WARN, "zero LBA size reported, " "possibly broken device"); lp->l_lba_size = DEV_BSIZE; } } else { lp->l_lba_size = 2048; } } default: break; } mutex_exit(&lp->l_mutex); } /* * --- thread management * * dispatch a thread */ int scsa1394_thr_dispatch(scsa1394_thread_t *thr) { scsa1394_lun_t *lp = thr->thr_lun; scsa1394_state_t *sp = lp->l_sp; int ret; ASSERT(mutex_owned(&lp->l_mutex)); ASSERT(thr->thr_state == SCSA1394_THR_INIT); thr->thr_state = SCSA1394_THR_RUN; ret = ddi_taskq_dispatch(sp->s_taskq, thr->thr_func, thr->thr_arg, KM_SLEEP); return (ret); } /* * cancel thread */ void scsa1394_thr_cancel(scsa1394_thread_t *thr) { scsa1394_lun_t *lp = thr->thr_lun; ASSERT(mutex_owned(&lp->l_mutex)); thr->thr_req |= SCSA1394_THREQ_EXIT; cv_signal(&thr->thr_cv); /* wait until the thread actually exits */ do { if (cv_wait_sig(&thr->thr_cv, &lp->l_mutex) == 0) { break; } } while (thr->thr_state != SCSA1394_THR_EXIT); } /* * wake thread */ void scsa1394_thr_wake(scsa1394_thread_t *thr, int req) { scsa1394_lun_t *lp = thr->thr_lun; ASSERT(mutex_owned(&lp->l_mutex)); thr->thr_req |= req; cv_signal(&thr->thr_cv); } void scsa1394_thr_clear_req(scsa1394_thread_t *thr, int mask) { scsa1394_lun_t *lp = thr->thr_lun; mutex_enter(&lp->l_mutex); thr->thr_req &= ~mask; mutex_exit(&lp->l_mutex); } /* * * --- other routines * */ static boolean_t scsa1394_is_my_child(dev_info_t *dip) { return ((dip != NULL) && (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "scsa1394") == 1)); } boolean_t scsa1394_dev_is_online(scsa1394_state_t *sp) { boolean_t ret; mutex_enter(&sp->s_mutex); ret = (sp->s_dev_state == SCSA1394_DEV_ONLINE); mutex_exit(&sp->s_mutex); return (ret); } static void * scsa1394_kmem_realloc(void *old_buf, int old_size, int new_size, size_t elsize, int kf) { void *new_buf; new_buf = kmem_zalloc(new_size * elsize, kf); if (old_size > 0) { if (new_buf != NULL) { bcopy(old_buf, new_buf, old_size * elsize); } kmem_free(old_buf, old_size * elsize); } return (new_buf); }