hba.c revision 0167b58cea98965c58fab4be4e690b6e456f7440
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 1394 mass storage HBA driver
*/
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <sys/byteorder.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/1394/targets/scsa1394/impl.h>
#include <sys/1394/targets/scsa1394/cmd.h>
/* 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);
/* 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 */
};
static struct modldrv scsa1394_modldrv = {
&mod_driverops, /* module type */
"1394 Mass Storage HBA Driver %I%", /* 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:
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);
}
#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:
scsa1394_cleanup(sp, SCSA1394_CLEANUP_LEVEL_MAX);
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_FAILURE);
default:
return (DDI_FAILURE);
}
}
/*ARGSUSED*/
static int
scsa1394_power(dev_info_t *dip, int comp, int level)
{
return (DDI_SUCCESS);
}
/*
*
* --- 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,
sizeof (scsa1394_cmd_t), 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 circ;
int ret;
if (scsa1394_bus_config_debug) {
flag |= NDI_DEVI_DEBUG;
}
ndi_devi_enter(dip, &circ);
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, circ);
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 circ;
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, &circ);
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, circ);
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, "target", 0);
if (ret != DDI_PROP_SUCCESS) {
(void) ndi_devi_free(cdip);
continue;
}
ret = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "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;
int circ;
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, &circ);
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, circ);
}
/*ARGSUSED*/
static void
scsa1394_reconnect(dev_info_t *dip, ddi_eventcookie_t evc, void *arg,
void *data)
{
scsa1394_state_t *sp = arg;
int circ;
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, &circ);
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, circ);
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, "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, "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 {
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, sizeof (scsa1394_cmd_t));
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)) != DDI_SUCCESS) {
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 */
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));
}
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);
}