sata.c revision 8315c5be243dbb5d2daa15cc2475e22de71d16d9
/*
* 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.
*/
/*
* SATA Framework
* Generic SATA Host Adapter Implementation
*/
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/thread.h>
#include <sys/kstat.h>
#include <sys/note.h>
#include <sys/sysevent.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sysevent/dr.h>
#include <sys/taskq.h>
#include <sys/disp.h>
#include <sys/sata/impl/sata.h>
#include <sys/sata/sata_hba.h>
#include <sys/sata/sata_defs.h>
#include <sys/sata/sata_cfgadm.h>
/* Debug flags - defined in sata.h */
int sata_debug_flags = 0;
int sata_msg = 0;
/*
* Flags enabling selected SATA HBA framework functionality
*/
#define SATA_ENABLE_QUEUING 1
#define SATA_ENABLE_NCQ 2
#define SATA_ENABLE_PROCESS_EVENTS 4
int sata_func_enable =
SATA_ENABLE_PROCESS_EVENTS | SATA_ENABLE_QUEUING | SATA_ENABLE_NCQ;
/*
* Global variable setting default maximum queue depth (NCQ or TCQ)
* Note:minimum queue depth is 1
*/
int sata_max_queue_depth = SATA_MAX_QUEUE_DEPTH; /* max NCQ/TCQ queue depth */
/*
* Currently used default NCQ/TCQ queue depth. It is set-up during the driver
* initialization, using value from sata_max_queue_depth
* It is adjusted to minimum supported by the controller and by the device,
* if queueing is enabled.
*/
static int sata_current_max_qdepth;
/*
* Global variable determining the default behavior after device hotpluggin.
* If non-zero, the hotplugged device is onlined (if possible) without explicit
* IOCTL request (AP_CONFIGURE).
* If zero, hotplugged device is identified, but not onlined.
* Enabling (AP_CONNECT) device port with an attached device does not result
* in device onlining regardless of the flag setting
*/
int sata_auto_online = 0;
#ifdef SATA_DEBUG
#define SATA_LOG_D(args) sata_log args
uint64_t mbuf_count = 0;
uint64_t mbuffail_count = 0;
sata_atapi_cmd_t sata_atapi_trace[64];
uint32_t sata_atapi_trace_index = 0;
int sata_atapi_trace_save = 1;
static void sata_save_atapi_trace(sata_pkt_txlate_t *, int);
#define SATAATAPITRACE(spx, count) if (sata_atapi_trace_save) \
sata_save_atapi_trace(spx, count);
#else
#define SATA_LOG_D(arg)
#define SATAATAPITRACE(spx, count)
#endif
#if 0
static void
sata_test_atapi_packet_command(sata_hba_inst_t *, int);
#endif
#ifdef SATA_INJECT_FAULTS
#define SATA_INJECT_PKT_FAULT 1
uint32_t sata_inject_fault = 0;
uint32_t sata_inject_fault_count = 0;
uint32_t sata_inject_fault_pause_count = 0;
uint32_t sata_fault_type = 0;
uint32_t sata_fault_cmd = 0;
dev_info_t *sata_fault_ctrl = NULL;
sata_device_t sata_fault_device;
static void sata_inject_pkt_fault(sata_pkt_t *, int *, int);
#endif
#define LEGACY_HWID_LEN 64 /* Model (40) + Serial (20) + pad */
static char sata_rev_tag[] = {"1.42"};
/*
* SATA cb_ops functions
*/
static int sata_hba_open(dev_t *, int, int, cred_t *);
static int sata_hba_close(dev_t, int, int, cred_t *);
static int sata_hba_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
/*
* SCSA required entry points
*/
static int sata_scsi_tgt_init(dev_info_t *, dev_info_t *,
scsi_hba_tran_t *, struct scsi_device *);
static int sata_scsi_tgt_probe(struct scsi_device *,
int (*callback)(void));
static void sata_scsi_tgt_free(dev_info_t *, dev_info_t *,
scsi_hba_tran_t *, struct scsi_device *);
static int sata_scsi_start(struct scsi_address *, struct scsi_pkt *);
static int sata_scsi_abort(struct scsi_address *, struct scsi_pkt *);
static int sata_scsi_reset(struct scsi_address *, int);
static int sata_scsi_getcap(struct scsi_address *, char *, int);
static int sata_scsi_setcap(struct scsi_address *, char *, int, int);
static struct scsi_pkt *sata_scsi_init_pkt(struct scsi_address *,
struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(caddr_t),
caddr_t);
static void sata_scsi_destroy_pkt(struct scsi_address *, struct scsi_pkt *);
static void sata_scsi_dmafree(struct scsi_address *, struct scsi_pkt *);
static void sata_scsi_sync_pkt(struct scsi_address *, struct scsi_pkt *);
/*
* SATA HBA interface functions are defined in sata_hba.h header file
*/
/* Event processing functions */
static void sata_event_daemon(void *);
static void sata_event_thread_control(int);
static void sata_process_controller_events(sata_hba_inst_t *sata_hba_inst);
static void sata_process_device_reset(sata_hba_inst_t *, sata_address_t *);
static void sata_process_port_failed_event(sata_hba_inst_t *,
sata_address_t *);
static void sata_process_port_link_events(sata_hba_inst_t *,
sata_address_t *);
static void sata_process_device_detached(sata_hba_inst_t *, sata_address_t *);
static void sata_process_device_attached(sata_hba_inst_t *, sata_address_t *);
static void sata_process_port_pwr_change(sata_hba_inst_t *, sata_address_t *);
static void sata_process_cntrl_pwr_level_change(sata_hba_inst_t *);
static void sata_process_target_node_cleanup(sata_hba_inst_t *,
sata_address_t *);
static void sata_process_device_autoonline(sata_hba_inst_t *,
sata_address_t *saddr);
/*
* Local translation functions
*/
static int sata_txlt_inquiry(sata_pkt_txlate_t *);
static int sata_txlt_test_unit_ready(sata_pkt_txlate_t *);
static int sata_txlt_start_stop_unit(sata_pkt_txlate_t *);
static int sata_txlt_read_capacity(sata_pkt_txlate_t *);
static int sata_txlt_request_sense(sata_pkt_txlate_t *);
static int sata_txlt_read(sata_pkt_txlate_t *);
static int sata_txlt_write(sata_pkt_txlate_t *);
static int sata_txlt_log_sense(sata_pkt_txlate_t *);
static int sata_txlt_log_select(sata_pkt_txlate_t *);
static int sata_txlt_mode_sense(sata_pkt_txlate_t *);
static int sata_txlt_mode_select(sata_pkt_txlate_t *);
static int sata_txlt_synchronize_cache(sata_pkt_txlate_t *);
static int sata_txlt_write_buffer(sata_pkt_txlate_t *);
static int sata_txlt_nodata_cmd_immediate(sata_pkt_txlate_t *);
static int sata_hba_start(sata_pkt_txlate_t *, int *);
static int sata_txlt_invalid_command(sata_pkt_txlate_t *);
static int sata_txlt_lba_out_of_range(sata_pkt_txlate_t *);
static void sata_txlt_rw_completion(sata_pkt_t *);
static void sata_txlt_nodata_cmd_completion(sata_pkt_t *);
static void sata_txlt_download_mcode_cmd_completion(sata_pkt_t *);
static int sata_emul_rw_completion(sata_pkt_txlate_t *);
static struct scsi_extended_sense *sata_immediate_error_response(
sata_pkt_txlate_t *, int);
static struct scsi_extended_sense *sata_arq_sense(sata_pkt_txlate_t *);
static int sata_txlt_atapi(sata_pkt_txlate_t *);
static void sata_txlt_atapi_completion(sata_pkt_t *);
/*
* Local functions for ioctl
*/
static int32_t sata_get_port_num(sata_hba_inst_t *, struct devctl_iocdata *);
static void sata_cfgadm_state(sata_hba_inst_t *, int32_t,
devctl_ap_state_t *);
static dev_info_t *sata_get_target_dip(dev_info_t *, int32_t);
static dev_info_t *sata_get_scsi_target_dip(dev_info_t *, sata_address_t *);
static dev_info_t *sata_devt_to_devinfo(dev_t);
static int sata_ioctl_connect(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_disconnect(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_configure(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_unconfigure(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_activate(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_deactivate(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_reset_port(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_reset_device(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_reset_all(sata_hba_inst_t *);
static int sata_ioctl_port_self_test(sata_hba_inst_t *, sata_device_t *);
static int sata_ioctl_get_device_path(sata_hba_inst_t *, sata_device_t *,
sata_ioctl_data_t *, int mode);
static int sata_ioctl_get_ap_type(sata_hba_inst_t *, sata_device_t *,
sata_ioctl_data_t *, int mode);
static int sata_ioctl_get_model_info(sata_hba_inst_t *, sata_device_t *,
sata_ioctl_data_t *, int mode);
static int sata_ioctl_get_revfirmware_info(sata_hba_inst_t *, sata_device_t *,
sata_ioctl_data_t *, int mode);
static int sata_ioctl_get_serialnumber_info(sata_hba_inst_t *,
sata_device_t *, sata_ioctl_data_t *, int mode);
/*
* Local functions
*/
static void sata_remove_hba_instance(dev_info_t *);
static int sata_validate_sata_hba_tran(dev_info_t *, sata_hba_tran_t *);
static void sata_probe_ports(sata_hba_inst_t *);
static int sata_reprobe_port(sata_hba_inst_t *, sata_device_t *, int);
static int sata_add_device(dev_info_t *, sata_hba_inst_t *, int cport,
int pmport);
static dev_info_t *sata_create_target_node(dev_info_t *, sata_hba_inst_t *,
sata_address_t *);
static int sata_validate_scsi_address(sata_hba_inst_t *,
struct scsi_address *, sata_device_t *);
static int sata_validate_sata_address(sata_hba_inst_t *, int, int, int);
static sata_pkt_t *sata_pkt_alloc(sata_pkt_txlate_t *, int (*)(caddr_t));
static void sata_pkt_free(sata_pkt_txlate_t *);
static int sata_dma_buf_setup(sata_pkt_txlate_t *, int, int (*)(caddr_t),
caddr_t, ddi_dma_attr_t *);
static void sata_common_free_dma_rsrcs(sata_pkt_txlate_t *);
static int sata_probe_device(sata_hba_inst_t *, sata_device_t *);
static sata_drive_info_t *sata_get_device_info(sata_hba_inst_t *,
sata_device_t *);
static int sata_identify_device(sata_hba_inst_t *, sata_drive_info_t *);
static void sata_reidentify_device(sata_pkt_txlate_t *);
static struct buf *sata_alloc_local_buffer(sata_pkt_txlate_t *, int);
static void sata_free_local_buffer(sata_pkt_txlate_t *);
static uint64_t sata_check_capacity(sata_drive_info_t *);
void sata_adjust_dma_attr(sata_drive_info_t *, ddi_dma_attr_t *,
ddi_dma_attr_t *);
static int sata_fetch_device_identify_data(sata_hba_inst_t *,
sata_drive_info_t *);
static void sata_update_port_info(sata_hba_inst_t *, sata_device_t *);
static void sata_update_port_scr(sata_port_scr_t *, sata_device_t *);
static int sata_set_dma_mode(sata_hba_inst_t *, sata_drive_info_t *);
static int sata_set_cache_mode(sata_hba_inst_t *, sata_drive_info_t *, int);
static int sata_set_rmsn(sata_hba_inst_t *, sata_drive_info_t *, int);
static int sata_set_drive_features(sata_hba_inst_t *,
sata_drive_info_t *, int flag);
static void sata_init_write_cache_mode(sata_drive_info_t *sdinfo);
static int sata_initialize_device(sata_hba_inst_t *, sata_drive_info_t *);
static void sata_identdev_to_inquiry(sata_hba_inst_t *, sata_drive_info_t *,
uint8_t *);
static int sata_get_atapi_inquiry_data(sata_hba_inst_t *, sata_address_t *,
struct scsi_inquiry *);
static int sata_build_msense_page_1(sata_drive_info_t *, int, uint8_t *);
static int sata_build_msense_page_8(sata_drive_info_t *, int, uint8_t *);
static int sata_build_msense_page_1a(sata_drive_info_t *, int, uint8_t *);
static int sata_build_msense_page_1c(sata_drive_info_t *, int, uint8_t *);
static int sata_mode_select_page_8(sata_pkt_txlate_t *,
struct mode_cache_scsi3 *, int, int *, int *, int *);
static int sata_mode_select_page_1c(sata_pkt_txlate_t *,
struct mode_info_excpt_page *, int, int *, int *, int *);
static int sata_build_msense_page_30(sata_drive_info_t *, int, uint8_t *);
static int sata_mode_select_page_30(sata_pkt_txlate_t *,
struct mode_acoustic_management *, int, int *, int *, int *);
static int sata_build_lsense_page_0(sata_drive_info_t *, uint8_t *);
static int sata_build_lsense_page_10(sata_drive_info_t *, uint8_t *,
sata_hba_inst_t *);
static int sata_build_lsense_page_2f(sata_drive_info_t *, uint8_t *,
sata_hba_inst_t *);
static int sata_build_lsense_page_30(sata_drive_info_t *, uint8_t *,
sata_hba_inst_t *);
static void sata_save_drive_settings(sata_drive_info_t *);
static void sata_show_drive_info(sata_hba_inst_t *, sata_drive_info_t *);
static void sata_log(sata_hba_inst_t *, uint_t, char *fmt, ...);
static int sata_fetch_smart_return_status(sata_hba_inst_t *,
sata_drive_info_t *);
static int sata_fetch_smart_data(sata_hba_inst_t *, sata_drive_info_t *,
struct smart_data *);
static int sata_smart_selftest_log(sata_hba_inst_t *,
sata_drive_info_t *,
struct smart_selftest_log *);
static int sata_ext_smart_selftest_read_log(sata_hba_inst_t *,
sata_drive_info_t *, struct smart_ext_selftest_log *, uint16_t);
static int sata_smart_read_log(sata_hba_inst_t *, sata_drive_info_t *,
uint8_t *, uint8_t, uint8_t);
static int sata_read_log_ext_directory(sata_hba_inst_t *, sata_drive_info_t *,
struct read_log_ext_directory *);
static void sata_gen_sysevent(sata_hba_inst_t *, sata_address_t *, int);
static void sata_xlate_errors(sata_pkt_txlate_t *);
static void sata_decode_device_error(sata_pkt_txlate_t *,
struct scsi_extended_sense *);
static void sata_set_device_removed(dev_info_t *);
static boolean_t sata_check_device_removed(dev_info_t *);
static void sata_set_target_node_cleanup(sata_hba_inst_t *, sata_address_t *);
static int sata_ncq_err_ret_cmd_setup(sata_pkt_txlate_t *,
sata_drive_info_t *);
static int sata_atapi_err_ret_cmd_setup(sata_pkt_txlate_t *,
sata_drive_info_t *);
static void sata_atapi_packet_cmd_setup(sata_cmd_t *, sata_drive_info_t *);
static void sata_fixed_sense_data_preset(struct scsi_extended_sense *);
static void sata_target_devid_register(dev_info_t *, sata_drive_info_t *);
static int sata_check_modser(char *, int);
/*
* SATA Framework will ignore SATA HBA driver cb_ops structure and
* register following one with SCSA framework.
* Open & close are provided, so scsi framework will not use its own
*/
static struct cb_ops sata_cb_ops = {
sata_hba_open, /* open */
sata_hba_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
sata_hba_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* chpoll */
ddi_prop_op, /* cb_prop_op */
0, /* streamtab */
D_NEW | D_MP, /* cb_flag */
CB_REV, /* rev */
nodev, /* aread */
nodev /* awrite */
};
extern struct mod_ops mod_miscops;
extern uchar_t scsi_cdb_size[];
static struct modlmisc modlmisc = {
&mod_miscops, /* Type of module */
"SATA Module" /* module name */
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modlmisc,
NULL
};
/*
* Default sata pkt timeout. Used when a target driver scsi_pkt time is zero,
* i.e. when scsi_pkt has not timeout specified.
*/
static int sata_default_pkt_time = 60; /* 60 seconds */
/*
* Intermediate buffer device access attributes - they are required,
* but not necessarily used.
*/
static ddi_device_acc_attr_t sata_acc_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
/*
* Mutexes protecting structures in multithreaded operations.
* Because events are relatively rare, a single global mutex protecting
* data structures should be sufficient. To increase performance, add
* separate mutex per each sata port and use global mutex only to protect
* common data structures.
*/
static kmutex_t sata_mutex; /* protects sata_hba_list */
static kmutex_t sata_log_mutex; /* protects log */
static char sata_log_buf[256];
/* Default write cache setting for SATA hard disks */
int sata_write_cache = 1; /* enabled */
/* Default write cache setting for SATA ATAPI CD/DVD */
int sata_atapicdvd_write_cache = 1; /* enabled */
/* Default write cache setting for SATA ATAPI tape */
int sata_atapitape_write_cache = 1; /* enabled */
/* Default write cache setting for SATA ATAPI disk */
int sata_atapidisk_write_cache = 1; /* enabled */
/*
* Linked list of HBA instances
*/
static sata_hba_inst_t *sata_hba_list = NULL;
static sata_hba_inst_t *sata_hba_list_tail = NULL;
/*
* Pointer to per-instance SATA HBA soft structure is stored in sata_hba_tran
* structure and in sata soft state.
*/
/*
* Event daemon related variables
*/
static kmutex_t sata_event_mutex;
static kcondvar_t sata_event_cv;
static kthread_t *sata_event_thread = NULL;
static int sata_event_thread_terminate = 0;
static int sata_event_pending = 0;
static int sata_event_thread_active = 0;
extern pri_t minclsyspri;
/*
* NCQ error recovery command
*/
static const sata_cmd_t sata_rle_cmd = {
SATA_CMD_REV,
NULL,
{
SATA_DIR_READ
},
ATA_ADDR_LBA48,
0,
0,
0,
0,
0,
1,
READ_LOG_EXT_NCQ_ERROR_RECOVERY,
0,
0,
0,
SATAC_READ_LOG_EXT,
0,
0,
0,
};
/*
* ATAPI error recovery CDB
*/
static const uint8_t sata_rqsense_cdb[SATA_ATAPI_RQSENSE_CDB_LEN] = {
SCMD_REQUEST_SENSE,
0, /* Only fixed RQ format is supported */
0,
0,
SATA_ATAPI_MIN_RQSENSE_LEN, /* Less data may be returned */
0
};
/* Warlock directives */
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_hba_tran))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_device))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", dev_ops))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_extended_sense))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_arq_status))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", ddi_dma_attr))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", ddi_dma_cookie_t))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", devctl_ap_state))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", dev_info::devi_state))
_NOTE(MUTEX_PROTECTS_DATA(sata_mutex, sata_hba_list))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_hba_list))
_NOTE(MUTEX_PROTECTS_DATA(sata_mutex, sata_hba_inst::satahba_next))
_NOTE(MUTEX_PROTECTS_DATA(sata_mutex, sata_hba_inst::satahba_prev))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", \
sata_hba_inst::satahba_scsi_tran))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", sata_hba_inst::satahba_tran))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", sata_hba_inst::satahba_dip))
_NOTE(SCHEME_PROTECTS_DATA("Scheme", sata_hba_inst::satahba_attached))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_hba_inst::satahba_dev_port))
_NOTE(MUTEX_PROTECTS_DATA(sata_hba_inst::satahba_mutex,
sata_hba_inst::satahba_event_flags))
_NOTE(MUTEX_PROTECTS_DATA(sata_cport_info::cport_mutex, \
sata_cport_info::cport_devp))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_cport_info::cport_devp))
_NOTE(SCHEME_PROTECTS_DATA("Scheme", sata_cport_info::cport_addr))
_NOTE(MUTEX_PROTECTS_DATA(sata_cport_info::cport_mutex, \
sata_cport_info::cport_dev_type))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_cport_info::cport_dev_type))
_NOTE(MUTEX_PROTECTS_DATA(sata_cport_info::cport_mutex, \
sata_cport_info::cport_state))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_cport_info::cport_state))
_NOTE(MUTEX_PROTECTS_DATA(sata_cport_info::cport_mutex, \
sata_pmport_info::pmport_state))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmport_info::pmport_state))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmport_info::pmport_dev_type))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmport_info::pmport_sata_drive))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmult_info::pmult_dev_port))
_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmult_info::pmult_num_dev_ports))
#ifdef SATA_DEBUG
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", mbuf_count))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", mbuffail_count))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", sata_atapi_trace))
_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", sata_atapi_trace_index))
#endif
/* End of warlock directives */
/* ************** loadable module configuration functions ************** */
int
_init()
{
int rval;
mutex_init(&sata_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&sata_event_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&sata_log_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&sata_event_cv, NULL, CV_DRIVER, NULL);
if ((rval = mod_install(&modlinkage)) != 0) {
#ifdef SATA_DEBUG
cmn_err(CE_WARN, "sata: _init: mod_install failed\n");
#endif
mutex_destroy(&sata_log_mutex);
cv_destroy(&sata_event_cv);
mutex_destroy(&sata_event_mutex);
mutex_destroy(&sata_mutex);
}
return (rval);
}
int
_fini()
{
int rval;
if ((rval = mod_remove(&modlinkage)) != 0)
return (rval);
mutex_destroy(&sata_log_mutex);
cv_destroy(&sata_event_cv);
mutex_destroy(&sata_event_mutex);
mutex_destroy(&sata_mutex);
return (rval);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/* ********************* SATA HBA entry points ********************* */
/*
* Called by SATA HBA from _init().
* Registers HBA driver instance/sata framework pair with scsi framework, by
* calling scsi_hba_init().
*
* SATA HBA driver cb_ops are ignored - SATA HBA framework cb_ops are used
* instead. SATA HBA framework cb_ops pointer overwrites SATA HBA driver
* cb_ops pointer in SATA HBA driver dev_ops structure.
* SATA HBA framework cb_ops supplies cb_open cb_close and cb_ioctl vectors.
*
* Return status of the scsi_hba_init() is returned to a calling SATA HBA
* driver.
*/
int
sata_hba_init(struct modlinkage *modlp)
{
int rval;
struct dev_ops *hba_ops;
SATADBG1(SATA_DBG_HBA_IF, NULL,
"sata_hba_init: name %s \n",
((struct modldrv *)(modlp->ml_linkage[0]))->drv_linkinfo);
/*
* Fill-up cb_ops and dev_ops when necessary
*/
hba_ops = ((struct modldrv *)(modlp->ml_linkage[0]))->drv_dev_ops;
/*
* Provide pointer to SATA dev_ops
*/
hba_ops->devo_cb_ops = &sata_cb_ops;
/*
* Register SATA HBA with SCSI framework
*/
if ((rval = scsi_hba_init(modlp)) != 0) {
SATADBG1(SATA_DBG_HBA_IF, NULL,
"sata_hba_init: scsi hba init failed\n", NULL);
return (rval);
}
return (0);
}
/* HBA attach stages */
#define HBA_ATTACH_STAGE_SATA_HBA_INST 1
#define HBA_ATTACH_STAGE_SCSI_ATTACHED 2
#define HBA_ATTACH_STAGE_SETUP 4
#define HBA_ATTACH_STAGE_LINKED 8
/*
*
* Called from SATA HBA driver's attach routine to attach an instance of
* the HBA.
*
* For DDI_ATTACH command:
* sata_hba_inst structure is allocated here and initialized with pointers to
* SATA framework implementation of required scsi tran functions.
* The scsi_tran's tran_hba_private field is used by SATA Framework to point
* to the soft structure (sata_hba_inst) allocated by SATA framework for
* SATA HBA instance related data.
* The scsi_tran's tran_hba_private field is used by SATA framework to
* store a pointer to per-HBA-instance of sata_hba_inst structure.
* The sata_hba_inst structure is cross-linked to scsi tran structure.
* Among other info, a pointer to sata_hba_tran structure is stored in
* sata_hba_inst. The sata_hba_inst structures for different HBA instances are
* linked together into the list, pointed to by sata_hba_list.
* On the first HBA instance attach the sata event thread is initialized.
* Attachment points are created for all SATA ports of the HBA being attached.
* All HBA instance's SATA ports are probed and type of plugged devices is
* determined. For each device of a supported type, a target node is created.
*
* DDI_SUCCESS is returned when attachment process is successful,
* DDI_FAILURE is returned otherwise.
*
* For DDI_RESUME command:
* Not implemented at this time (postponed until phase 2 of the development).
*/
int
sata_hba_attach(dev_info_t *dip, sata_hba_tran_t *sata_tran,
ddi_attach_cmd_t cmd)
{
sata_hba_inst_t *sata_hba_inst;
scsi_hba_tran_t *scsi_tran = NULL;
int hba_attach_state = 0;
char taskq_name[MAXPATHLEN];
SATADBG3(SATA_DBG_HBA_IF, NULL,
"sata_hba_attach: node %s (%s%d)\n",
ddi_node_name(dip), ddi_driver_name(dip),
ddi_get_instance(dip));
if (cmd == DDI_RESUME) {
/*
* Postponed until phase 2 of the development
*/
return (DDI_FAILURE);
}
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
/* cmd == DDI_ATTACH */
if (sata_validate_sata_hba_tran(dip, sata_tran) != SATA_SUCCESS) {
SATA_LOG_D((NULL, CE_WARN,
"sata_hba_attach: invalid sata_hba_tran"));
return (DDI_FAILURE);
}
/*
* Allocate and initialize SCSI tran structure.
* SATA copy of tran_bus_config is provided to create port nodes.
*/
scsi_tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP);
if (scsi_tran == NULL)
return (DDI_FAILURE);
/*
* Allocate soft structure for SATA HBA instance.
* There is a separate softstate for each HBA instance.
*/
sata_hba_inst = kmem_zalloc(sizeof (struct sata_hba_inst), KM_SLEEP);
ASSERT(sata_hba_inst != NULL); /* this should not fail */
mutex_init(&sata_hba_inst->satahba_mutex, NULL, MUTEX_DRIVER, NULL);
hba_attach_state |= HBA_ATTACH_STAGE_SATA_HBA_INST;
/*
* scsi_trans's tran_hba_private is used by SATA Framework to point to
* soft structure allocated by SATA framework for
* SATA HBA instance related data.
*/
scsi_tran->tran_hba_private = sata_hba_inst;
scsi_tran->tran_tgt_private = NULL;
scsi_tran->tran_tgt_init = sata_scsi_tgt_init;
scsi_tran->tran_tgt_probe = sata_scsi_tgt_probe;
scsi_tran->tran_tgt_free = sata_scsi_tgt_free;
scsi_tran->tran_start = sata_scsi_start;
scsi_tran->tran_reset = sata_scsi_reset;
scsi_tran->tran_abort = sata_scsi_abort;
scsi_tran->tran_getcap = sata_scsi_getcap;
scsi_tran->tran_setcap = sata_scsi_setcap;
scsi_tran->tran_init_pkt = sata_scsi_init_pkt;
scsi_tran->tran_destroy_pkt = sata_scsi_destroy_pkt;
scsi_tran->tran_dmafree = sata_scsi_dmafree;
scsi_tran->tran_sync_pkt = sata_scsi_sync_pkt;
scsi_tran->tran_reset_notify = NULL;
scsi_tran->tran_get_bus_addr = NULL;
scsi_tran->tran_quiesce = NULL;
scsi_tran->tran_unquiesce = NULL;
scsi_tran->tran_bus_reset = NULL;
if (scsi_hba_attach_setup(dip, sata_tran->sata_tran_hba_dma_attr,
scsi_tran, 0) != DDI_SUCCESS) {
#ifdef SATA_DEBUG
cmn_err(CE_WARN, "?SATA: %s%d hba scsi attach failed",
ddi_driver_name(dip), ddi_get_instance(dip));
#endif
goto fail;
}
hba_attach_state |= HBA_ATTACH_STAGE_SCSI_ATTACHED;
if (!ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sata")) {
if (ddi_prop_update_int(DDI_DEV_T_NONE, dip,
"sata", 1) != DDI_PROP_SUCCESS) {
SATA_LOG_D((NULL, CE_WARN, "sata_hba_attach: "
"failed to create hba sata prop"));
goto fail;
}
}
/*
* Save pointers in hba instance soft state.
*/
sata_hba_inst->satahba_scsi_tran = scsi_tran;
sata_hba_inst->satahba_tran = sata_tran;
sata_hba_inst->satahba_dip = dip;
/*
* Create a task queue to handle emulated commands completion
* Use node name, dash, instance number as the queue name.
*/
taskq_name[0] = '\0';
(void) strlcat(taskq_name, DEVI(dip)->devi_node_name,
sizeof (taskq_name));
(void) snprintf(taskq_name + strlen(taskq_name),
sizeof (taskq_name) - strlen(taskq_name),
"-%d", DEVI(dip)->devi_instance);
sata_hba_inst->satahba_taskq = taskq_create(taskq_name, 1,
minclsyspri, 1, sata_tran->sata_tran_hba_num_cports * 4,
TASKQ_DYNAMIC);
hba_attach_state |= HBA_ATTACH_STAGE_SETUP;
/*
* Create events thread if not created yet.
*/
sata_event_thread_control(1);
/*
* Link this hba instance into the list.
*/
mutex_enter(&sata_mutex);
if (sata_hba_list == NULL) {
/*
* The first instance of HBA is attached.
* Set current/active default maximum NCQ/TCQ queue depth for
* all SATA devices. It is done here and now, to eliminate the
* possibility of the dynamic, programatic modification of the
* queue depth via global (and public) sata_max_queue_depth
* variable (this would require special handling in HBA drivers)
*/
sata_current_max_qdepth = sata_max_queue_depth;
if (sata_current_max_qdepth > 32)
sata_current_max_qdepth = 32;
else if (sata_current_max_qdepth < 1)
sata_current_max_qdepth = 1;
}
sata_hba_inst->satahba_next = NULL;
sata_hba_inst->satahba_prev = sata_hba_list_tail;
if (sata_hba_list == NULL) {
sata_hba_list = sata_hba_inst;
}
if (sata_hba_list_tail != NULL) {
sata_hba_list_tail->satahba_next = sata_hba_inst;
}
sata_hba_list_tail = sata_hba_inst;
mutex_exit(&sata_mutex);
hba_attach_state |= HBA_ATTACH_STAGE_LINKED;
/*
* Create SATA HBA devctl minor node for sata_hba_open, close, ioctl
* SATA HBA driver should not use its own open/close entry points.
*
* Make sure that instance number doesn't overflow
* when forming minor numbers.
*/
ASSERT(ddi_get_instance(dip) <= (L_MAXMIN >> INST_MINOR_SHIFT));
if (ddi_create_minor_node(dip, "devctl", S_IFCHR,
INST2DEVCTL(ddi_get_instance(dip)),
DDI_NT_SATA_NEXUS, 0) != DDI_SUCCESS) {
#ifdef SATA_DEBUG
cmn_err(CE_WARN, "sata_hba_attach: "
"cannot create devctl minor node");
#endif
goto fail;
}
/*
* Set-up kstats here, if necessary.
* (postponed until future phase of the development).
*/
/*
* Indicate that HBA is attached. This will enable events processing
* for this HBA.
*/
sata_hba_inst->satahba_attached = 1;
/*
* Probe controller ports. This operation will describe a current
* controller/port/multipliers/device configuration and will create
* attachment points.
* We may end-up with just a controller with no devices attached.
* For the ports with a supported device attached, device target nodes
* are created and devices are initialized.
*/
sata_probe_ports(sata_hba_inst);
return (DDI_SUCCESS);
fail:
if (hba_attach_state & HBA_ATTACH_STAGE_LINKED) {
(void) sata_remove_hba_instance(dip);
if (sata_hba_list == NULL)
sata_event_thread_control(0);
}
if (hba_attach_state & HBA_ATTACH_STAGE_SETUP) {
(void) ddi_prop_remove(DDI_DEV_T_ANY, dip, "sata");
taskq_destroy(sata_hba_inst->satahba_taskq);
}
if (hba_attach_state & HBA_ATTACH_STAGE_SCSI_ATTACHED)
(void) scsi_hba_detach(dip);
if (hba_attach_state & HBA_ATTACH_STAGE_SATA_HBA_INST) {
mutex_destroy(&sata_hba_inst->satahba_mutex);
kmem_free((void *)sata_hba_inst,
sizeof (struct sata_hba_inst));
scsi_hba_tran_free(scsi_tran);
}
sata_log(NULL, CE_WARN, "?SATA: %s%d hba attach failed",
ddi_driver_name(dip), ddi_get_instance(dip));
return (DDI_FAILURE);
}
/*
* Called by SATA HBA from to detach an instance of the driver.
*
* For DDI_DETACH command:
* Free local structures allocated for SATA HBA instance during
* sata_hba_attach processing.
*
* Returns DDI_SUCCESS when HBA was detached, DDI_FAILURE otherwise.
*
* For DDI_SUSPEND command:
* Not implemented at this time (postponed until phase 2 of the development)
* Returnd DDI_SUCCESS.
*
* When the last HBA instance is detached, the event daemon is terminated.
*
* NOTE: cport support only, no port multiplier support.
*/
int
sata_hba_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
dev_info_t *tdip;
sata_hba_inst_t *sata_hba_inst;
scsi_hba_tran_t *scsi_hba_tran;
sata_cport_info_t *cportinfo;
sata_drive_info_t *sdinfo;
int ncport;
SATADBG3(SATA_DBG_HBA_IF, NULL, "sata_hba_detach: node %s (%s%d)\n",
ddi_node_name(dip), ddi_driver_name(dip), ddi_get_instance(dip));
switch (cmd) {
case DDI_DETACH:
if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
return (DDI_FAILURE);
sata_hba_inst = scsi_hba_tran->tran_hba_private;
if (sata_hba_inst == NULL)
return (DDI_FAILURE);
if (scsi_hba_detach(dip) == DDI_FAILURE) {
sata_hba_inst->satahba_attached = 1;
return (DDI_FAILURE);
}
/*
* Free all target nodes - at this point
* devices should be at least offlined
* otherwise scsi_hba_detach() should not be called.
*/
for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst);
ncport++) {
cportinfo = SATA_CPORT_INFO(sata_hba_inst, ncport);
if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
if (sdinfo != NULL) {
tdip = sata_get_target_dip(dip,
ncport);
if (tdip != NULL) {
if (ndi_devi_offline(tdip,
NDI_DEVI_REMOVE) !=
NDI_SUCCESS) {
SATA_LOG_D((
sata_hba_inst,
CE_WARN,
"sata_hba_detach: "
"Target node not "
"removed !"));
return (DDI_FAILURE);
}
}
}
}
}
/*
* Disable sata event daemon processing for this HBA
*/
sata_hba_inst->satahba_attached = 0;
/*
* Remove event daemon thread, if it is last HBA instance.
*/
mutex_enter(&sata_mutex);
if (sata_hba_list->satahba_next == NULL) {
mutex_exit(&sata_mutex);
sata_event_thread_control(0);
mutex_enter(&sata_mutex);
}
mutex_exit(&sata_mutex);
/* Remove this HBA instance from the HBA list */
sata_remove_hba_instance(dip);
/*
* At this point there should be no target nodes attached.
* Detach and destroy device and port info structures.
*/
for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst);
ncport++) {
cportinfo = SATA_CPORT_INFO(sata_hba_inst, ncport);
if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
sdinfo =
cportinfo->cport_devp.cport_sata_drive;
if (sdinfo != NULL) {
/* Release device structure */
kmem_free(sdinfo,
sizeof (sata_drive_info_t));
}
/* Release cport info */
mutex_destroy(&cportinfo->cport_mutex);
kmem_free(cportinfo,
sizeof (sata_cport_info_t));
}
}
scsi_hba_tran_free(sata_hba_inst->satahba_scsi_tran);
(void) ddi_prop_remove(DDI_DEV_T_ANY, dip, "sata");
taskq_destroy(sata_hba_inst->satahba_taskq);
mutex_destroy(&sata_hba_inst->satahba_mutex);
kmem_free((void *)sata_hba_inst,
sizeof (struct sata_hba_inst));
return (DDI_SUCCESS);
case DDI_SUSPEND:
/*
* Postponed until phase 2
*/
return (DDI_FAILURE);
default:
return (DDI_FAILURE);
}
}
/*
* Called by an HBA drive from _fini() routine.
* Unregisters SATA HBA instance/SATA framework pair from the scsi framework.
*/
void
sata_hba_fini(struct modlinkage *modlp)
{
SATADBG1(SATA_DBG_HBA_IF, NULL,
"sata_hba_fini: name %s\n",
((struct modldrv *)(modlp->ml_linkage[0]))->drv_linkinfo);
scsi_hba_fini(modlp);
}
/*
* Default open and close routine for sata_hba framework.
*
*/
/*
* Open devctl node.
*
* Returns:
* 0 if node was open successfully, error code otherwise.
*
*
*/
static int
sata_hba_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(credp))
#endif
int rv = 0;
dev_info_t *dip;
scsi_hba_tran_t *scsi_hba_tran;
sata_hba_inst_t *sata_hba_inst;
SATADBG1(SATA_DBG_IOCTL_IF, NULL, "sata_hba_open: entered", NULL);
if (otyp != OTYP_CHR)
return (EINVAL);
dip = sata_devt_to_devinfo(*devp);
if (dip == NULL)
return (ENXIO);
if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
return (ENXIO);
sata_hba_inst = scsi_hba_tran->tran_hba_private;
if (sata_hba_inst == NULL || sata_hba_inst->satahba_attached == 0)
return (ENXIO);
mutex_enter(&sata_mutex);
if (flags & FEXCL) {
if (sata_hba_inst->satahba_open_flag != 0) {
rv = EBUSY;
} else {
sata_hba_inst->satahba_open_flag =
SATA_DEVCTL_EXOPENED;
}
} else {
if (sata_hba_inst->satahba_open_flag == SATA_DEVCTL_EXOPENED) {
rv = EBUSY;
} else {
sata_hba_inst->satahba_open_flag =
SATA_DEVCTL_SOPENED;
}
}
mutex_exit(&sata_mutex);
return (rv);
}
/*
* Close devctl node.
* Returns:
* 0 if node was closed successfully, error code otherwise.
*
*/
static int
sata_hba_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(credp))
_NOTE(ARGUNUSED(flag))
#endif
dev_info_t *dip;
scsi_hba_tran_t *scsi_hba_tran;
sata_hba_inst_t *sata_hba_inst;
SATADBG1(SATA_DBG_IOCTL_IF, NULL, "sata_hba_close: entered", NULL);
if (otyp != OTYP_CHR)
return (EINVAL);
dip = sata_devt_to_devinfo(dev);
if (dip == NULL)
return (ENXIO);
if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
return (ENXIO);
sata_hba_inst = scsi_hba_tran->tran_hba_private;
if (sata_hba_inst == NULL || sata_hba_inst->satahba_attached == 0)
return (ENXIO);
mutex_enter(&sata_mutex);
sata_hba_inst->satahba_open_flag = 0;
mutex_exit(&sata_mutex);
return (0);
}
/*
* Standard IOCTL commands for SATA hotplugging.
* Implemented DEVCTL_AP commands:
* DEVCTL_AP_CONNECT
* DEVCTL_AP_DISCONNECT
* DEVCTL_AP_CONFIGURE
* DEVCTL_UNCONFIGURE
* DEVCTL_AP_CONTROL
*
* Commands passed to default ndi ioctl handler:
* DEVCTL_DEVICE_GETSTATE
* DEVCTL_DEVICE_ONLINE
* DEVCTL_DEVICE_OFFLINE
* DEVCTL_DEVICE_REMOVE
* DEVCTL_DEVICE_INSERT
* DEVCTL_BUS_GETSTATE
*
* All other cmds are passed to HBA if it provide ioctl handler, or failed
* if not.
*
* Returns:
* 0 if successful,
* error code if operation failed.
*
* NOTE: Port Multiplier is not supported.
*
*/
static int
sata_hba_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rvalp)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(credp))
_NOTE(ARGUNUSED(rvalp))
#endif
int rv = 0;
int32_t comp_port = -1;
dev_info_t *dip;
devctl_ap_state_t ap_state;
struct devctl_iocdata *dcp = NULL;
scsi_hba_tran_t *scsi_hba_tran;
sata_hba_inst_t *sata_hba_inst;
sata_device_t sata_device;
sata_cport_info_t *cportinfo;
int cport, pmport, qual;
int rval = SATA_SUCCESS;
dip = sata_devt_to_devinfo(dev);
if (dip == NULL)
return (ENXIO);
if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
return (ENXIO);
sata_hba_inst = scsi_hba_tran->tran_hba_private;
if (sata_hba_inst == NULL)
return (ENXIO);
if (sata_hba_inst->satahba_tran == NULL)
return (ENXIO);
switch (cmd) {
case DEVCTL_DEVICE_GETSTATE:
case DEVCTL_DEVICE_ONLINE:
case DEVCTL_DEVICE_OFFLINE:
case DEVCTL_DEVICE_REMOVE:
case DEVCTL_BUS_GETSTATE:
/*
* There may be more cases that we want to pass to default
* handler rather than fail them.
*/
return (ndi_devctl_ioctl(dip, cmd, arg, mode, 0));
}
/* read devctl ioctl data */
if (cmd != DEVCTL_AP_CONTROL) {
if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
return (EFAULT);
if ((comp_port = sata_get_port_num(sata_hba_inst, dcp)) ==
-1) {
if (dcp)
ndi_dc_freehdl(dcp);
return (EINVAL);
}
cport = SCSI_TO_SATA_CPORT(comp_port);
pmport = SCSI_TO_SATA_PMPORT(comp_port);
/* Only cport is considered now, i.e. SATA_ADDR_CPORT */
qual = SATA_ADDR_CPORT;
if (sata_validate_sata_address(sata_hba_inst, cport, pmport,
qual) != 0) {
ndi_dc_freehdl(dcp);
return (EINVAL);
}
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
if (cportinfo->cport_event_flags & SATA_EVNT_LOCK_PORT_BUSY) {
/*
* Cannot process ioctl request now. Come back later.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
ndi_dc_freehdl(dcp);
return (EBUSY);
}
/* Block event processing for this port */
cportinfo->cport_event_flags |= SATA_APCTL_LOCK_PORT_BUSY;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
sata_device.satadev_addr.cport = cport;
sata_device.satadev_addr.pmport = pmport;
sata_device.satadev_addr.qual = qual;
sata_device.satadev_rev = SATA_DEVICE_REV;
}
switch (cmd) {
case DEVCTL_AP_DISCONNECT:
/*
* Normally, cfgadm sata plugin will try to offline
* (unconfigure) device before this request. Nevertheless,
* if a device is still configured, we need to
* attempt to offline and unconfigure device first, and we will
* deactivate the port regardless of the unconfigure
* operation results.
*
*/
rv = sata_ioctl_disconnect(sata_hba_inst, &sata_device);
break;
case DEVCTL_AP_UNCONFIGURE:
/*
* The unconfigure operation uses generic nexus operation to
* offline a device. It leaves a target device node attached.
* and obviously sata_drive_info attached as well, because
* from the hardware point of view nothing has changed.
*/
rv = sata_ioctl_unconfigure(sata_hba_inst, &sata_device);
break;
case DEVCTL_AP_CONNECT:
{
/*
* The sata cfgadm pluging will invoke this operation only if
* port was found in the disconnect state (failed state
* is also treated as the disconnected state).
* If port activation is successful and a device is found
* attached to the port, the initialization sequence is
* executed to probe the port and attach
* a device structure to a port structure. The device is not
* set in configured state (system-wise) by this operation.
*/
rv = sata_ioctl_connect(sata_hba_inst, &sata_device);
break;
}
case DEVCTL_AP_CONFIGURE:
{
/*
* A port may be in an active or shutdown state.
* If port is in a failed state, operation is aborted.
* If a port is in a shutdown state, sata_tran_port_activate()
* is invoked prior to any other operation.
*
* Onlining the device involves creating a new target node.
* If there is an old target node present (belonging to
* previously removed device), the operation is aborted - the
* old node has to be released and removed before configure
* operation is attempted.
*/
rv = sata_ioctl_configure(sata_hba_inst, &sata_device);
break;
}
case DEVCTL_AP_GETSTATE:
sata_cfgadm_state(sata_hba_inst, comp_port, &ap_state);
ap_state.ap_last_change = (time_t)-1;
ap_state.ap_error_code = 0;
ap_state.ap_in_transition = 0;
/* Copy the return AP-state information to the user space */
if (ndi_dc_return_ap_state(&ap_state, dcp) != NDI_SUCCESS) {
rv = EFAULT;
}
break;
case DEVCTL_AP_CONTROL:
{
/*
* Generic devctl for hardware specific functionality
*/
sata_ioctl_data_t ioc;
ASSERT(dcp == NULL);
/* Copy in user ioctl data first */
#ifdef _MULTI_DATAMODEL
if (ddi_model_convert_from(mode & FMODELS) ==
DDI_MODEL_ILP32) {
sata_ioctl_data_32_t ioc32;
if (ddi_copyin((void *)arg, (void *)&ioc32,
sizeof (ioc32), mode) != 0) {
rv = EFAULT;
break;
}
ioc.cmd = (uint_t)ioc32.cmd;
ioc.port = (uint_t)ioc32.port;
ioc.get_size = (uint_t)ioc32.get_size;
ioc.buf = (caddr_t)(uintptr_t)ioc32.buf;
ioc.bufsiz = (uint_t)ioc32.bufsiz;
ioc.misc_arg = (uint_t)ioc32.misc_arg;
} else
#endif /* _MULTI_DATAMODEL */
if (ddi_copyin((void *)arg, (void *)&ioc, sizeof (ioc),
mode) != 0) {
return (EFAULT);
}
SATADBG2(SATA_DBG_IOCTL_IF, sata_hba_inst,
"sata_hba_ioctl: DEVCTL_AP_CONTROL "
"cmd 0x%x, port 0x%x", ioc.cmd, ioc.port);
/*
* To avoid BE/LE and 32/64 issues, a get_size always returns
* a 32-bit number.
*/
if (ioc.get_size != 0 && ioc.bufsiz != (sizeof (uint32_t))) {
return (EINVAL);
}
/* validate address */
cport = SCSI_TO_SATA_CPORT(ioc.port);
pmport = SCSI_TO_SATA_PMPORT(ioc.port);
qual = SCSI_TO_SATA_ADDR_QUAL(ioc.port);
/* Override address qualifier - handle cport only for now */
qual = SATA_ADDR_CPORT;
if (sata_validate_sata_address(sata_hba_inst, cport,
pmport, qual) != 0)
return (EINVAL);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
/* Is the port locked by event processing daemon ? */
if (cportinfo->cport_event_flags & SATA_EVNT_LOCK_PORT_BUSY) {
/*
* Cannot process ioctl request now. Come back later
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
return (EBUSY);
}
/* Block event processing for this port */
cportinfo->cport_event_flags |= SATA_APCTL_LOCK_PORT_BUSY;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
sata_device.satadev_addr.cport = cport;
sata_device.satadev_addr.pmport = pmport;
sata_device.satadev_addr.qual = qual;
sata_device.satadev_rev = SATA_DEVICE_REV;
switch (ioc.cmd) {
case SATA_CFGA_RESET_PORT:
/*
* There is no protection for configured device.
*/
rv = sata_ioctl_reset_port(sata_hba_inst, &sata_device);
break;
case SATA_CFGA_RESET_DEVICE:
/*
* There is no protection for configured device.
*/
rv = sata_ioctl_reset_device(sata_hba_inst,
&sata_device);
break;
case SATA_CFGA_RESET_ALL:
/*
* There is no protection for configured devices.
*/
rv = sata_ioctl_reset_all(sata_hba_inst);
/*
* We return here, because common return is for
* a single port operation - we have already unlocked
* all ports and no dc handle was allocated.
*/
return (rv);
case SATA_CFGA_PORT_DEACTIVATE:
/*
* Arbitrarily unconfigure attached device, if any.
* Even if the unconfigure fails, proceed with the
* port deactivation.
*/
rv = sata_ioctl_deactivate(sata_hba_inst, &sata_device);
break;
case SATA_CFGA_PORT_ACTIVATE:
rv = sata_ioctl_activate(sata_hba_inst, &sata_device);
break;
case SATA_CFGA_PORT_SELF_TEST:
rv = sata_ioctl_port_self_test(sata_hba_inst,
&sata_device);
break;
case SATA_CFGA_GET_DEVICE_PATH:
if (qual == SATA_ADDR_CPORT)
sata_device.satadev_addr.qual =
SATA_ADDR_DCPORT;
else
sata_device.satadev_addr.qual =
SATA_ADDR_DPMPORT;
rv = sata_ioctl_get_device_path(sata_hba_inst,
&sata_device, &ioc, mode);
break;
case SATA_CFGA_GET_AP_TYPE:
rv = sata_ioctl_get_ap_type(sata_hba_inst,
&sata_device, &ioc, mode);
break;
case SATA_CFGA_GET_MODEL_INFO:
rv = sata_ioctl_get_model_info(sata_hba_inst,
&sata_device, &ioc, mode);
break;
case SATA_CFGA_GET_REVFIRMWARE_INFO:
rv = sata_ioctl_get_revfirmware_info(sata_hba_inst,
&sata_device, &ioc, mode);
break;
case SATA_CFGA_GET_SERIALNUMBER_INFO:
rv = sata_ioctl_get_serialnumber_info(sata_hba_inst,
&sata_device, &ioc, mode);
break;
default:
rv = EINVAL;
break;
} /* End of DEVCTL_AP_CONTROL cmd switch */
break;
}
default:
{
/*
* If we got here, we got an IOCTL that SATA HBA Framework
* does not recognize. Pass ioctl to HBA driver, in case
* it could process it.
*/
sata_hba_tran_t *sata_tran = sata_hba_inst->satahba_tran;
dev_info_t *mydip = SATA_DIP(sata_hba_inst);
SATADBG1(SATA_DBG_IOCTL_IF, sata_hba_inst,
"IOCTL 0x%2x not supported in SATA framework, "
"passthrough to HBA", cmd);
if (sata_tran->sata_tran_ioctl == NULL) {
rv = EINVAL;
break;
}
rval = (*sata_tran->sata_tran_ioctl)(mydip, cmd, arg);
if (rval != 0) {
SATADBG1(SATA_DBG_IOCTL_IF, sata_hba_inst,
"IOCTL 0x%2x failed in HBA", cmd);
rv = rval;
}
break;
}
} /* End of main IOCTL switch */
if (dcp) {
ndi_dc_freehdl(dcp);
}
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
cportinfo->cport_event_flags &= ~SATA_APCTL_LOCK_PORT_BUSY;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
return (rv);
}
/*
* Create error retrieval sata packet
*
* A sata packet is allocated and set-up to contain specified error retrieval
* command and appropriate dma-able data buffer.
* No association with any scsi packet is made and no callback routine is
* specified.
*
* Returns a pointer to sata packet upon successfull packet creation.
* Returns NULL, if packet cannot be created.
*/
sata_pkt_t *
sata_get_error_retrieval_pkt(dev_info_t *dip, sata_device_t *sata_device,
int pkt_type)
{
sata_hba_inst_t *sata_hba_inst;
sata_pkt_txlate_t *spx;
sata_pkt_t *spkt;
sata_drive_info_t *sdinfo;
mutex_enter(&sata_mutex);
for (sata_hba_inst = sata_hba_list; sata_hba_inst != NULL;
sata_hba_inst = sata_hba_inst->satahba_next) {
if (SATA_DIP(sata_hba_inst) == dip)
break;
}
mutex_exit(&sata_mutex);
ASSERT(sata_hba_inst != NULL);
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
if (sdinfo == NULL) {
sata_log(sata_hba_inst, CE_WARN,
"sata: error recovery request for non-attached device at "
"cport %d", sata_device->satadev_addr.cport);
return (NULL);
}
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, NULL);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (NULL);
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sata_device->satadev_addr;
switch (pkt_type) {
case SATA_ERR_RETR_PKT_TYPE_NCQ:
if (sata_ncq_err_ret_cmd_setup(spx, sdinfo) == SATA_SUCCESS)
return (spkt);
break;
case SATA_ERR_RETR_PKT_TYPE_ATAPI:
if (sata_atapi_err_ret_cmd_setup(spx, sdinfo) == SATA_SUCCESS)
return (spkt);
break;
default:
break;
}
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (NULL);
}
/*
* Free error retrieval sata packet
*
* Free sata packet and any associated resources allocated previously by
* sata_get_error_retrieval_pkt().
*
* Void return.
*/
void
sata_free_error_retrieval_pkt(sata_pkt_t *sata_pkt)
{
sata_pkt_txlate_t *spx =
(sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
ASSERT(sata_pkt != NULL);
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
}
/*
* sata_name_child is for composing the name of the node
* the format of the name is "target,0".
*/
static int
sata_name_child(dev_info_t *dip, char *name, int namelen)
{
int target;
target = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "target", -1);
if (target == -1)
return (DDI_FAILURE);
(void) snprintf(name, namelen, "%x,0", target);
return (DDI_SUCCESS);
}
/* ****************** SCSA required entry points *********************** */
/*
* Implementation of scsi tran_tgt_init.
* sata_scsi_tgt_init() initializes scsi_device structure
*
* If successful, DDI_SUCCESS is returned.
* DDI_FAILURE is returned if addressed device does not exist
*/
static int
sata_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(hba_dip))
_NOTE(ARGUNUSED(tgt_dip))
#endif
sata_device_t sata_device;
sata_drive_info_t *sdinfo;
struct sata_id *sid;
sata_hba_inst_t *sata_hba_inst;
char model[SATA_ID_MODEL_LEN + 1];
char fw[SATA_ID_FW_LEN + 1];
char *vid, *pid;
int i;
/*
* Fail tran_tgt_init for .conf stub node
*/
if (ndi_dev_is_persistent_node(tgt_dip) == 0) {
(void) ndi_merge_node(tgt_dip, sata_name_child);
ddi_set_name_addr(tgt_dip, NULL);
return (DDI_FAILURE);
}
sata_hba_inst = (sata_hba_inst_t *)(hba_tran->tran_hba_private);
/* Validate scsi device address */
if (sata_validate_scsi_address(sata_hba_inst, &sd->sd_address,
&sata_device) != 0)
return (DDI_FAILURE);
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
/* sata_device now contains a valid sata address */
sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
if (sdinfo == NULL) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return (DDI_FAILURE);
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
/*
* Check if we need to create a legacy devid (i.e cmdk style) for
* the target disks.
*
* HBA devinfo node will have the property "use-cmdk-devid-format"
* if we need to create cmdk-style devid for all the disk devices
* attached to this controller. This property may have been set
* from HBA driver's .conf file or by the HBA driver in its
* attach(9F) function.
*/
if ((sdinfo->satadrv_type == SATA_DTYPE_ATADISK) &&
(ddi_getprop(DDI_DEV_T_ANY, hba_dip, DDI_PROP_DONTPASS,
"use-cmdk-devid-format", 0) == 1)) {
/* register a legacy devid for this target node */
sata_target_devid_register(tgt_dip, sdinfo);
}
/*
* 'Identify Device Data' does not always fit in standard SCSI
* INQUIRY data, so establish INQUIRY_* properties with full-form
* of information.
*/
sid = &sdinfo->satadrv_id;
#ifdef _LITTLE_ENDIAN
swab(sid->ai_model, model, SATA_ID_MODEL_LEN);
swab(sid->ai_fw, fw, SATA_ID_FW_LEN);
#else /* _LITTLE_ENDIAN */
bcopy(sid->ai_model, model, SATA_ID_MODEL_LEN);
bcopy(sid->ai_fw, fw, SATA_ID_FW_LEN);
#endif /* _LITTLE_ENDIAN */
model[SATA_ID_MODEL_LEN] = 0;
fw[SATA_ID_FW_LEN] = 0;
/* split model into into vid/pid */
for (i = 0, pid = model; i < SATA_ID_MODEL_LEN; i++, pid++)
if ((*pid == ' ') || (*pid == '\t'))
break;
if (i < SATA_ID_MODEL_LEN) {
vid = model;
*pid++ = 0; /* terminate vid, establish pid */
} else {
vid = NULL; /* vid will stay "ATA " */
pid = model; /* model is all pid */
}
if (vid)
(void) scsi_hba_prop_update_inqstring(sd, INQUIRY_VENDOR_ID,
vid, strlen(vid));
if (pid)
(void) scsi_hba_prop_update_inqstring(sd, INQUIRY_PRODUCT_ID,
pid, strlen(pid));
(void) scsi_hba_prop_update_inqstring(sd, INQUIRY_REVISION_ID,
fw, strlen(fw));
return (DDI_SUCCESS);
}
/*
* Implementation of scsi tran_tgt_probe.
* Probe target, by calling default scsi routine scsi_hba_probe()
*/
static int
sata_scsi_tgt_probe(struct scsi_device *sd, int (*callback)(void))
{
sata_hba_inst_t *sata_hba_inst =
(sata_hba_inst_t *)(sd->sd_address.a_hba_tran->tran_hba_private);
int rval;
rval = scsi_hba_probe(sd, callback);
if (rval == SCSIPROBE_EXISTS) {
/*
* Set property "pm-capable" on the target device node, so that
* the target driver will not try to fetch scsi cycle counters
* before enabling device power-management.
*/
if ((ddi_prop_update_int(DDI_DEV_T_NONE, sd->sd_dev,
"pm-capable", 1)) != DDI_PROP_SUCCESS) {
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d: "
"will not be power-managed ",
SCSI_TO_SATA_CPORT(sd->sd_address.a_target));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"failure updating pm-capable property"));
}
}
return (rval);
}
/*
* Implementation of scsi tran_tgt_free.
* Release all resources allocated for scsi_device
*/
static void
sata_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip,
scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(hba_dip))
#endif
sata_device_t sata_device;
sata_drive_info_t *sdinfo;
sata_hba_inst_t *sata_hba_inst;
ddi_devid_t devid;
sata_hba_inst = (sata_hba_inst_t *)(hba_tran->tran_hba_private);
/* Validate scsi device address */
if (sata_validate_scsi_address(sata_hba_inst, &sd->sd_address,
&sata_device) != 0)
return;
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
/* sata_device now should contain a valid sata address */
sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
if (sdinfo == NULL) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return;
}
/*
* We did not allocate any resources in sata_scsi_tgt_init()
* other than few properties.
* Free them.
*/
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
(void) ndi_prop_remove(DDI_DEV_T_NONE, tgt_dip, "pm-capable");
/*
* If devid was previously created but not freed up from
* sd(7D) driver (i.e during detach(9F)) then do it here.
*/
if ((sdinfo->satadrv_type == SATA_DTYPE_ATADISK) &&
(ddi_getprop(DDI_DEV_T_ANY, hba_dip, DDI_PROP_DONTPASS,
"use-cmdk-devid-format", 0) == 1) &&
(ddi_devid_get(tgt_dip, &devid) == DDI_SUCCESS)) {
ddi_devid_unregister(tgt_dip);
ddi_devid_free(devid);
}
}
/*
* Implementation of scsi tran_init_pkt
* Upon successful return, scsi pkt buffer has DMA resources allocated.
*
* It seems that we should always allocate pkt, even if the address is
* for non-existing device - just use some default for dma_attr.
* The reason is that there is no way to communicate this to a caller here.
* Subsequent call to sata_scsi_start may fail appropriately.
* Simply returning NULL does not seem to discourage a target driver...
*
* Returns a pointer to initialized scsi_pkt, or NULL otherwise.
*/
static struct scsi_pkt *
sata_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), caddr_t arg)
{
sata_hba_inst_t *sata_hba_inst =
(sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
dev_info_t *dip = SATA_DIP(sata_hba_inst);
sata_device_t sata_device;
sata_drive_info_t *sdinfo;
sata_pkt_txlate_t *spx;
ddi_dma_attr_t cur_dma_attr;
int rval;
boolean_t new_pkt = TRUE;
ASSERT(ap->a_hba_tran->tran_hba_dip == dip);
/*
* We need to translate the address, even if it could be
* a bogus one, for a non-existing device
*/
sata_device.satadev_addr.qual = SCSI_TO_SATA_ADDR_QUAL(ap->a_target);
sata_device.satadev_addr.cport = SCSI_TO_SATA_CPORT(ap->a_target);
sata_device.satadev_addr.pmport = SCSI_TO_SATA_PMPORT(ap->a_target);
sata_device.satadev_rev = SATA_DEVICE_REV;
if (pkt == NULL) {
/*
* Have to allocate a brand new scsi packet.
* We need to operate with auto request sense enabled.
*/
pkt = scsi_hba_pkt_alloc(dip, ap, cmdlen,
MAX(statuslen, sizeof (struct scsi_arq_status)),
tgtlen, sizeof (sata_pkt_txlate_t), callback, arg);
if (pkt == NULL)
return (NULL);
/* Fill scsi packet structure */
pkt->pkt_comp = (void (*)())NULL;
pkt->pkt_time = 0;
pkt->pkt_resid = 0;
pkt->pkt_statistics = 0;
pkt->pkt_reason = 0;
/*
* pkt_hba_private will point to sata pkt txlate structure
*/
spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
bzero(spx, sizeof (sata_pkt_txlate_t));
spx->txlt_scsi_pkt = pkt;
spx->txlt_sata_hba_inst = sata_hba_inst;
/* Allocate sata_pkt */
spx->txlt_sata_pkt = sata_pkt_alloc(spx, callback);
if (spx->txlt_sata_pkt == NULL) {
/* Could not allocate sata pkt */
scsi_hba_pkt_free(ap, pkt);
return (NULL);
}
/* Set sata address */
spx->txlt_sata_pkt->satapkt_device.satadev_addr =
sata_device.satadev_addr;
spx->txlt_sata_pkt->satapkt_device.satadev_rev =
sata_device.satadev_rev;
if ((bp == NULL) || (bp->b_bcount == 0))
return (pkt);
spx->txlt_total_residue = bp->b_bcount;
} else {
new_pkt = FALSE;
/*
* Packet was preallocated/initialized by previous call
*/
spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
if ((bp == NULL) || (bp->b_bcount == 0)) {
return (pkt);
}
/* Pkt is available already: spx->txlt_scsi_pkt == pkt; */
}
spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = bp;
/*
* We use an adjusted version of the dma_attr, to account
* for device addressing limitations.
* sata_adjust_dma_attr() will handle sdinfo == NULL which may
* happen when a device is not yet configured.
*/
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
/* NULL sdinfo may be passsed to sata_adjust_dma_attr() */
sata_adjust_dma_attr(sdinfo,
SATA_DMA_ATTR(spx->txlt_sata_hba_inst), &cur_dma_attr);
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
/*
* Allocate necessary DMA resources for the packet's data buffer
* NOTE:
* In case of read/write commands, DMA resource allocation here is
* based on the premise that the transfer length specified in
* the read/write scsi cdb will match exactly DMA resources -
* returning correct packet residue is crucial.
*/
if ((rval = sata_dma_buf_setup(spx, flags, callback, arg,
&cur_dma_attr)) != DDI_SUCCESS) {
/*
* If a DMA allocation request fails with
* DDI_DMA_NOMAPPING, indicate the error by calling
* bioerror(9F) with bp and an error code of EFAULT.
* If a DMA allocation request fails with
* DDI_DMA_TOOBIG, indicate the error by calling
* bioerror(9F) with bp and an error code of EINVAL.
* For DDI_DMA_NORESOURCES, we may have some of them allocated.
* Request may be repeated later - there is no real error.
*/
switch (rval) {
case DDI_DMA_NORESOURCES:
bioerror(bp, 0);
break;
case DDI_DMA_NOMAPPING:
case DDI_DMA_BADATTR:
bioerror(bp, EFAULT);
break;
case DDI_DMA_TOOBIG:
default:
bioerror(bp, EINVAL);
break;
}
if (new_pkt == TRUE) {
/*
* Since this is a new packet, we can clean-up
* everything
*/
sata_scsi_destroy_pkt(ap, pkt);
} else {
/*
* This is a re-used packet. It will be target driver's
* responsibility to eventually destroy it (which
* will free allocated resources).
* Here, we just "complete" the request, leaving
* allocated resources intact, so the request may
* be retried.
*/
spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = NULL;
sata_pkt_free(spx);
}
return (NULL);
}
/* Set number of bytes that are not yet accounted for */
pkt->pkt_resid = spx->txlt_total_residue;
ASSERT(pkt->pkt_resid >= 0);
return (pkt);
}
/*
* Implementation of scsi tran_start.
* Translate scsi cmd into sata operation and return status.
* ATAPI CDBs are passed to ATAPI devices - the device determines what commands
* are supported.
* For SATA hard disks, supported scsi commands:
* SCMD_INQUIRY
* SCMD_TEST_UNIT_READY
* SCMD_START_STOP
* SCMD_READ_CAPACITY
* SCMD_REQUEST_SENSE
* SCMD_LOG_SENSE_G1
* SCMD_LOG_SELECT_G1
* SCMD_MODE_SENSE (specific pages)
* SCMD_MODE_SENSE_G1 (specific pages)
* SCMD_MODE_SELECT (specific pages)
* SCMD_MODE_SELECT_G1 (specific pages)
* SCMD_SYNCHRONIZE_CACHE
* SCMD_SYNCHRONIZE_CACHE_G1
* SCMD_READ
* SCMD_READ_G1
* SCMD_READ_G4
* SCMD_READ_G5
* SCMD_WRITE
* SCMD_WRITE_BUFFER
* SCMD_WRITE_G1
* SCMD_WRITE_G4
* SCMD_WRITE_G5
* SCMD_SEEK (noop)
* SCMD_SDIAG
*
* All other commands are rejected as unsupported.
*
* Returns:
* TRAN_ACCEPT if command was executed successfully or accepted by HBA driver
* for execution. TRAN_ACCEPT may be returned also if device was removed but
* a callback could be scheduled.
* TRAN_BADPKT if cmd was directed to invalid address.
* TRAN_FATAL_ERROR is command was rejected due to hardware error, including
* some unspecified error. TRAN_FATAL_ERROR may be also returned if a device
* was removed and there was no callback specified in scsi pkt.
* TRAN_BUSY if command could not be executed becasue HBA driver or SATA
* framework was busy performing some other operation(s).
*
*/
static int
sata_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt)
{
sata_hba_inst_t *sata_hba_inst =
(sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
sata_pkt_txlate_t *spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
sata_drive_info_t *sdinfo;
struct buf *bp;
int cport;
int rval;
SATADBG1(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_scsi_start: cmd 0x%02x\n", pkt->pkt_cdbp[0]);
ASSERT(spx != NULL &&
spx->txlt_scsi_pkt == pkt && spx->txlt_sata_pkt != NULL);
cport = SCSI_TO_SATA_CPORT(ap->a_target);
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
sdinfo = sata_get_device_info(sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
if (sdinfo == NULL ||
SATA_CPORT_INFO(sata_hba_inst, cport)->cport_tgtnode_clean ==
B_FALSE ||
(sdinfo->satadrv_state & SATA_DSTATE_FAILED) != 0) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
pkt->pkt_reason = CMD_DEV_GONE;
/*
* The sd target driver is checking CMD_DEV_GONE pkt_reason
* only in callback function (for normal requests) and
* in the dump code path.
* So, if the callback is available, we need to do
* the callback rather than returning TRAN_FATAL_ERROR here.
*/
if (pkt->pkt_comp != NULL) {
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)pkt->pkt_comp,
(void *)pkt, TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/* No callback available */
return (TRAN_FATAL_ERROR);
}
if (sdinfo->satadrv_type & SATA_DTYPE_ATAPI) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
rval = sata_txlt_atapi(spx);
SATADBG1(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_scsi_start atapi: rval %d\n", rval);
return (rval);
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
/* ATA Disk commands processing starts here */
bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
switch (pkt->pkt_cdbp[0]) {
case SCMD_INQUIRY:
/* Mapped to identify device */
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_inquiry(spx);
break;
case SCMD_TEST_UNIT_READY:
/*
* SAT "SATA to ATA Translation" doc specifies translation
* to ATA CHECK POWER MODE.
*/
rval = sata_txlt_test_unit_ready(spx);
break;
case SCMD_START_STOP:
/* Mapping depends on the command */
rval = sata_txlt_start_stop_unit(spx);
break;
case SCMD_READ_CAPACITY:
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_read_capacity(spx);
break;
case SCMD_REQUEST_SENSE:
/*
* Always No Sense, since we force ARQ
*/
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_request_sense(spx);
break;
case SCMD_LOG_SENSE_G1:
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_log_sense(spx);
break;
case SCMD_LOG_SELECT_G1:
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_log_select(spx);
break;
case SCMD_MODE_SENSE:
case SCMD_MODE_SENSE_G1:
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_mode_sense(spx);
break;
case SCMD_MODE_SELECT:
case SCMD_MODE_SELECT_G1:
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_mode_select(spx);
break;
case SCMD_SYNCHRONIZE_CACHE:
case SCMD_SYNCHRONIZE_CACHE_G1:
rval = sata_txlt_synchronize_cache(spx);
break;
case SCMD_READ:
case SCMD_READ_G1:
case SCMD_READ_G4:
case SCMD_READ_G5:
rval = sata_txlt_read(spx);
break;
case SCMD_WRITE_BUFFER:
if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
bp_mapin(bp);
rval = sata_txlt_write_buffer(spx);
break;
case SCMD_WRITE:
case SCMD_WRITE_G1:
case SCMD_WRITE_G4:
case SCMD_WRITE_G5:
rval = sata_txlt_write(spx);
break;
case SCMD_SEEK:
rval = sata_txlt_nodata_cmd_immediate(spx);
break;
/* Other cases will be filed later */
/* postponed until phase 2 of the development */
default:
rval = sata_txlt_invalid_command(spx);
break;
}
SATADBG1(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_scsi_start: rval %d\n", rval);
return (rval);
}
/*
* Implementation of scsi tran_abort.
* Abort specific pkt or all packets.
*
* Returns 1 if one or more packets were aborted, returns 0 otherwise
*
* May be called from an interrupt level.
*/
static int
sata_scsi_abort(struct scsi_address *ap, struct scsi_pkt *scsi_pkt)
{
sata_hba_inst_t *sata_hba_inst =
(sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
sata_device_t sata_device;
sata_pkt_t *sata_pkt;
SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_scsi_abort: %s at target: 0x%x\n",
scsi_pkt == NULL ? "all packets" : "one pkt", ap->a_target);
/* Validate address */
if (sata_validate_scsi_address(sata_hba_inst, ap, &sata_device) != 0)
/* Invalid address */
return (0);
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
if (sata_get_device_info(sata_hba_inst, &sata_device) == NULL) {
/* invalid address */
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return (0);
}
if (scsi_pkt == NULL) {
/*
* Abort all packets.
* Although we do not have specific packet, we still need
* dummy packet structure to pass device address to HBA.
* Allocate one, without sleeping. Fail if pkt cannot be
* allocated.
*/
sata_pkt = kmem_zalloc(sizeof (sata_pkt_t), KM_NOSLEEP);
if (sata_pkt == NULL) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_pkt_abort: "
"could not allocate sata_pkt"));
return (0);
}
sata_pkt->satapkt_rev = SATA_PKT_REV;
sata_pkt->satapkt_device = sata_device;
sata_pkt->satapkt_device.satadev_rev = SATA_DEVICE_REV;
} else {
if (scsi_pkt->pkt_ha_private == NULL) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return (0); /* Bad scsi pkt */
}
/* extract pointer to sata pkt */
sata_pkt = ((sata_pkt_txlate_t *)scsi_pkt->pkt_ha_private)->
txlt_sata_pkt;
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
/* Send abort request to HBA */
if ((*SATA_ABORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_pkt,
scsi_pkt == NULL ? SATA_ABORT_ALL_PACKETS : SATA_ABORT_PACKET) ==
SATA_SUCCESS) {
if (scsi_pkt == NULL)
kmem_free(sata_pkt, sizeof (sata_pkt_t));
/* Success */
return (1);
}
/* Else, something did not go right */
if (scsi_pkt == NULL)
kmem_free(sata_pkt, sizeof (sata_pkt_t));
/* Failure */
return (0);
}
/*
* Implementation of scsi tran_reset.
* RESET_ALL request is translated into port reset.
* RESET_TARGET requests is translated into a device reset,
* RESET_LUN request is accepted only for LUN 0 and translated into
* device reset.
* The target reset should cause all HBA active and queued packets to
* be terminated and returned with pkt reason SATA_PKT_RESET prior to
* the return. HBA should report reset event for the device.
*
* Returns 1 upon success, 0 upon failure.
*/
static int
sata_scsi_reset(struct scsi_address *ap, int level)
{
sata_hba_inst_t *sata_hba_inst =
(sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
sata_device_t sata_device;
int val;
SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_scsi_reset: level %d target: 0x%x\n",
level, ap->a_target);
/* Validate address */
val = sata_validate_scsi_address(sata_hba_inst, ap, &sata_device);
if (val == -1)
/* Invalid address */
return (0);
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
if (sata_get_device_info(sata_hba_inst, &sata_device) == NULL) {
/* invalid address */
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return (0);
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
if (level == RESET_ALL) {
/* port reset - cport only */
sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device) == SATA_SUCCESS)
return (1);
else
return (0);
} else if (val == 0 &&
(level == RESET_TARGET || level == RESET_LUN)) {
/* reset device (device attached) */
if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device) == SATA_SUCCESS)
return (1);
else
return (0);
}
return (0);
}
/*
* Implementation of scsi tran_getcap (get transport/device capabilities).
* Supported capabilities for SATA hard disks:
* auto-rqsense (always supported)
* tagged-qing (supported if HBA supports it)
* untagged-qing (could be supported if disk supports it, but because
* caching behavior allowing untagged queuing actually
* results in reduced performance. sd tries to throttle
* back to only 3 outstanding commands, which may
* work for real SCSI disks, but with read ahead
* caching, having more than 1 outstanding command
* results in cache thrashing.)
* sector_size
* dma_max
* interconnect-type (INTERCONNECT_SATA)
*
* Supported capabilities for ATAPI CD/DVD devices:
* auto-rqsense (always supported)
* sector_size
* dma_max
* max-cdb-length
* interconnect-type (INTERCONNECT_SATA)
*
* Supported capabilities for ATAPI TAPE devices:
* auto-rqsense (always supported)
* dma_max
* max-cdb-length
*
* Supported capabilities for SATA ATAPI hard disks:
* auto-rqsense (always supported)
* interconnect-type (INTERCONNECT_SATA)
* max-cdb-length
*
* Request for other capabilities is rejected as unsupported.
*
* Returns supported capability value, or -1 if capability is unsuppported or
* the address is invalid - no device.
*/
static int
sata_scsi_getcap(struct scsi_address *ap, char *cap, int whom)
{
sata_hba_inst_t *sata_hba_inst =
(sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
sata_device_t sata_device;
sata_drive_info_t *sdinfo;
ddi_dma_attr_t adj_dma_attr;
int rval;
SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_scsi_getcap: target: 0x%x, cap: %s\n",
ap->a_target, cap);
/*
* We want to process the capabilities on per port granularity.
* So, we are specifically restricting ourselves to whom != 0
* to exclude the controller wide handling.
*/
if (cap == NULL || whom == 0)
return (-1);
if (sata_validate_scsi_address(sata_hba_inst, ap, &sata_device) != 0) {
/* Invalid address */
return (-1);
}
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
if ((sdinfo = sata_get_device_info(sata_hba_inst, &sata_device)) ==
NULL) {
/* invalid address */
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return (-1);
}
switch (scsi_hba_lookup_capstr(cap)) {
case SCSI_CAP_ARQ:
rval = 1; /* ARQ supported, turned on */
break;
case SCSI_CAP_SECTOR_SIZE:
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK)
rval = SATA_DISK_SECTOR_SIZE; /* fixed size */
else if (sdinfo->satadrv_type == SATA_DTYPE_ATAPICD)
rval = SATA_ATAPI_SECTOR_SIZE;
else rval = -1;
break;
/*
* untagged queuing cause a performance inversion because of
* the way sd operates. Because of this reason we do not
* use it when available.
*/
case SCSI_CAP_UNTAGGED_QING:
if (sdinfo->satadrv_features_enabled &
SATA_DEV_F_E_UNTAGGED_QING)
rval = 1; /* Untagged queuing available */
else
rval = -1; /* Untagged queuing not available */
break;
case SCSI_CAP_TAGGED_QING:
if ((sdinfo->satadrv_features_enabled &
SATA_DEV_F_E_TAGGED_QING) &&
(sdinfo->satadrv_max_queue_depth > 1))
rval = 1; /* Tagged queuing available */
else
rval = -1; /* Tagged queuing not available */
break;
case SCSI_CAP_DMA_MAX:
sata_adjust_dma_attr(sdinfo, SATA_DMA_ATTR(sata_hba_inst),
&adj_dma_attr);
rval = (int)adj_dma_attr.dma_attr_maxxfer;
/* We rely on the fact that dma_attr_maxxfer < 0x80000000 */
break;
case SCSI_CAP_INTERCONNECT_TYPE:
rval = INTERCONNECT_SATA; /* SATA interconnect type */
break;
case SCSI_CAP_CDB_LEN:
if (sdinfo->satadrv_type & SATA_DTYPE_ATAPI)
rval = sdinfo->satadrv_atapi_cdb_len;
else
rval = -1;
break;
default:
rval = -1;
break;
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return (rval);
}
/*
* Implementation of scsi tran_setcap
*
* Only SCSI_CAP_UNTAGGED_QING and SCSI_CAP_TAGGED_QING are changeable.
*
*/
static int
sata_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom)
{
sata_hba_inst_t *sata_hba_inst =
(sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
sata_device_t sata_device;
sata_drive_info_t *sdinfo;
int rval;
SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_scsi_setcap: target: 0x%x, cap: %s\n", ap->a_target, cap);
/*
* We want to process the capabilities on per port granularity.
* So, we are specifically restricting ourselves to whom != 0
* to exclude the controller wide handling.
*/
if (cap == NULL || whom == 0) {
return (-1);
}
if (sata_validate_scsi_address(sata_hba_inst, ap, &sata_device) != 0) {
/* Invalid address */
return (-1);
}
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
if ((sdinfo = sata_get_device_info(sata_hba_inst,
&sata_device)) == NULL) {
/* invalid address */
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
return (-1);
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device.satadev_addr.cport)));
switch (scsi_hba_lookup_capstr(cap)) {
case SCSI_CAP_ARQ:
case SCSI_CAP_SECTOR_SIZE:
case SCSI_CAP_DMA_MAX:
case SCSI_CAP_INTERCONNECT_TYPE:
rval = 0;
break;
case SCSI_CAP_UNTAGGED_QING:
if (SATA_QDEPTH(sata_hba_inst) > 1) {
rval = 1;
if (value == 1) {
sdinfo->satadrv_features_enabled |=
SATA_DEV_F_E_UNTAGGED_QING;
} else if (value == 0) {
sdinfo->satadrv_features_enabled &=
~SATA_DEV_F_E_UNTAGGED_QING;
} else {
rval = -1;
}
} else {
rval = 0;
}
break;
case SCSI_CAP_TAGGED_QING:
/* This can TCQ or NCQ */
if (sata_func_enable & SATA_ENABLE_QUEUING &&
((sdinfo->satadrv_features_support & SATA_DEV_F_TCQ &&
SATA_FEATURES(sata_hba_inst) & SATA_CTLF_QCMD) ||
(sata_func_enable & SATA_ENABLE_NCQ &&
sdinfo->satadrv_features_support & SATA_DEV_F_NCQ &&
SATA_FEATURES(sata_hba_inst) & SATA_CTLF_NCQ)) &&
(sdinfo->satadrv_max_queue_depth > 1)) {
rval = 1;
if (value == 1) {
sdinfo->satadrv_features_enabled |=
SATA_DEV_F_E_TAGGED_QING;
} else if (value == 0) {
sdinfo->satadrv_features_enabled &=
~SATA_DEV_F_E_TAGGED_QING;
} else {
rval = -1;
}
} else {
rval = 0;
}
break;
default:
rval = -1;
break;
}
return (rval);
}
/*
* Implementations of scsi tran_destroy_pkt.
* Free resources allocated by sata_scsi_init_pkt()
*/
static void
sata_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt)
{
sata_pkt_txlate_t *spx;
spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
sata_common_free_dma_rsrcs(spx);
spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = NULL;
sata_pkt_free(spx);
scsi_hba_pkt_free(ap, pkt);
}
/*
* Implementation of scsi tran_dmafree.
* Free DMA resources allocated by sata_scsi_init_pkt()
*/
static void
sata_scsi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(ap))
#endif
sata_pkt_txlate_t *spx;
ASSERT(pkt != NULL);
spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
sata_common_free_dma_rsrcs(spx);
}
/*
* Implementation of scsi tran_sync_pkt.
*
* The assumption below is that pkt is unique - there is no need to check ap
*
* Synchronize DMA buffer and, if the intermediate buffer is used, copy data
* into/from the real buffer.
*/
static void
sata_scsi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(ap))
#endif
int rval;
sata_pkt_txlate_t *spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
struct buf *bp;
int direction;
ASSERT(spx != NULL);
if (spx->txlt_buf_dma_handle != NULL) {
direction = spx->txlt_sata_pkt->
satapkt_cmd.satacmd_flags.sata_data_direction;
if (spx->txlt_sata_pkt != NULL &&
direction != SATA_DIR_NODATA_XFER) {
if (spx->txlt_tmp_buf != NULL) {
/* Intermediate DMA buffer used */
bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
if (direction & SATA_DIR_WRITE) {
bcopy(bp->b_un.b_addr,
spx->txlt_tmp_buf, bp->b_bcount);
}
}
/* Sync the buffer for device or for CPU */
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
(direction & SATA_DIR_WRITE) ?
DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU);
ASSERT(rval == DDI_SUCCESS);
if (spx->txlt_tmp_buf != NULL &&
!(direction & SATA_DIR_WRITE)) {
/* Intermediate DMA buffer used for read */
bcopy(spx->txlt_tmp_buf,
bp->b_un.b_addr, bp->b_bcount);
}
}
}
}
/* ******************* SATA - SCSI Translation functions **************** */
/*
* SCSI to SATA pkt and command translation and SATA to SCSI status/error
* translation.
*/
/*
* Checks if a device exists and can be access and translates common
* scsi_pkt data to sata_pkt data.
*
* Returns TRAN_ACCEPT and scsi pkt_reason CMD_CMPLT if device exists and
* sata_pkt was set-up.
* Returns TRAN_ACCEPT and scsi pkt_reason CMD_DEV_GONE if device does not
* exist and pkt_comp callback was scheduled.
* Returns other TRAN_XXXXX values when error occured and command should be
* rejected with the returned TRAN_XXXXX value.
*
* This function should be called with port mutex held.
*/
static int
sata_txlt_generic_pkt_info(sata_pkt_txlate_t *spx, int *reason)
{
sata_drive_info_t *sdinfo;
sata_device_t sata_device;
const struct sata_cmd_flags sata_initial_cmd_flags = {
SATA_DIR_NODATA_XFER,
/* all other values to 0/FALSE */
};
/*
* Pkt_reason has to be set if the pkt_comp callback is invoked,
* and that implies TRAN_ACCEPT return value. Any other returned value
* indicates that the scsi packet was not accepted (the reason will not
* be checked by the scsi target driver).
* To make debugging easier, we set pkt_reason to know value here.
* It may be changed later when different completion reason is
* determined.
*/
spx->txlt_scsi_pkt->pkt_reason = CMD_TRAN_ERR;
*reason = CMD_TRAN_ERR;
/* Validate address */
switch (sata_validate_scsi_address(spx->txlt_sata_hba_inst,
&spx->txlt_scsi_pkt->pkt_address, &sata_device)) {
case -1:
/* Invalid address or invalid device type */
return (TRAN_BADPKT);
case 1:
/* valid address but no device - it has disappeared ? */
spx->txlt_scsi_pkt->pkt_reason = CMD_DEV_GONE;
*reason = CMD_DEV_GONE;
/*
* The sd target driver is checking CMD_DEV_GONE pkt_reason
* only in callback function (for normal requests) and
* in the dump code path.
* So, if the callback is available, we need to do
* the callback rather than returning TRAN_FATAL_ERROR here.
*/
if (spx->txlt_scsi_pkt->pkt_comp != NULL) {
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)spx->txlt_scsi_pkt->pkt_comp,
(void *)spx->txlt_scsi_pkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
return (TRAN_FATAL_ERROR);
default:
/* all OK; pkt reason will be overwritten later */
break;
}
/*
* If in an interrupt context, reject packet if it is to be
* executed in polling mode
*/
if (servicing_interrupt() &&
(spx->txlt_scsi_pkt->pkt_flags & FLAG_NOINTR) != 0) {
SATADBG1(SATA_DBG_INTR_CTX, spx->txlt_sata_hba_inst,
"sata_scsi_start: rejecting synchronous command because "
"of interrupt context\n", NULL);
return (TRAN_BUSY);
}
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
/*
* If device is in reset condition, reject the packet with
* TRAN_BUSY, unless:
* 1. system is panicking (dumping)
* In such case only one thread is running and there is no way to
* process reset.
* 2. cfgadm operation is is progress (internal APCTL lock is set)
* Some cfgadm operations involve drive commands, so reset condition
* needs to be ignored for IOCTL operations.
*/
if ((sdinfo->satadrv_event_flags &
(SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET)) != 0) {
if (!ddi_in_panic() &&
((SATA_CPORT_EVENT_FLAGS(spx->txlt_sata_hba_inst,
sata_device.satadev_addr.cport) &
SATA_APCTL_LOCK_PORT_BUSY) == 0)) {
spx->txlt_scsi_pkt->pkt_reason = CMD_INCOMPLETE;
*reason = CMD_INCOMPLETE;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_scsi_start: rejecting command because "
"of device reset state\n", NULL);
return (TRAN_BUSY);
}
}
/*
* Fix the dev_type in the sata_pkt->satapkt_device. It was not set by
* sata_scsi_pkt_init() because pkt init had to work also with
* non-existing devices.
* Now we know that the packet was set-up for a real device, so its
* type is known.
*/
spx->txlt_sata_pkt->satapkt_device.satadev_type = sdinfo->satadrv_type;
spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags = sata_initial_cmd_flags;
if ((SATA_CPORT_INFO(spx->txlt_sata_hba_inst,
sata_device.satadev_addr.cport)->cport_event_flags &
SATA_APCTL_LOCK_PORT_BUSY) != 0) {
spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags.
sata_ignore_dev_reset = B_TRUE;
}
/*
* At this point the generic translation routine determined that the
* scsi packet should be accepted. Packet completion reason may be
* changed later when a different completion reason is determined.
*/
spx->txlt_scsi_pkt->pkt_reason = CMD_CMPLT;
*reason = CMD_CMPLT;
if ((spx->txlt_scsi_pkt->pkt_flags & FLAG_NOINTR) != 0) {
/* Synchronous execution */
spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_SYNCH |
SATA_OPMODE_POLLING;
spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags.
sata_ignore_dev_reset = ddi_in_panic();
} else {
/* Asynchronous execution */
spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_ASYNCH |
SATA_OPMODE_INTERRUPTS;
}
/* Convert queuing information */
if (spx->txlt_scsi_pkt->pkt_flags & FLAG_STAG)
spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags.sata_queue_stag =
B_TRUE;
else if (spx->txlt_scsi_pkt->pkt_flags &
(FLAG_OTAG | FLAG_HTAG | FLAG_HEAD))
spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags.sata_queue_otag =
B_TRUE;
/* Always limit pkt time */
if (spx->txlt_scsi_pkt->pkt_time == 0)
spx->txlt_sata_pkt->satapkt_time = sata_default_pkt_time;
else
/* Pass on scsi_pkt time */
spx->txlt_sata_pkt->satapkt_time =
spx->txlt_scsi_pkt->pkt_time;
return (TRAN_ACCEPT);
}
/*
* Translate ATA Identify Device data to SCSI Inquiry data.
* This function may be called only for ATA devices.
* This function should not be called for ATAPI devices - they
* respond directly to SCSI Inquiry command.
*
* SATA Identify Device data has to be valid in sata_rive_info.
* Buffer has to accomodate the inquiry length (36 bytes).
*
* This function should be called with a port mutex held.
*/
static void
sata_identdev_to_inquiry(sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo, uint8_t *buf)
{
struct scsi_inquiry *inq = (struct scsi_inquiry *)buf;
struct sata_id *sid = &sdinfo->satadrv_id;
/* Start with a nice clean slate */
bzero((void *)inq, sizeof (struct scsi_inquiry));
/*
* Rely on the dev_type for setting paripheral qualifier.
* Assume that DTYPE_RODIRECT applies to CD/DVD R/W devices.
* It could be that DTYPE_OPTICAL could also qualify in the future.
* ATAPI Inquiry may provide more data to the target driver.
*/
inq->inq_dtype = sdinfo->satadrv_type == SATA_DTYPE_ATADISK ?
DTYPE_DIRECT : DTYPE_RODIRECT; /* DTYPE_UNKNOWN; */
inq->inq_rmb = sid->ai_config & SATA_REM_MEDIA ? 1 : 0;
inq->inq_qual = 0; /* Device type qualifier (obsolete in SCSI3? */
inq->inq_iso = 0; /* ISO version */
inq->inq_ecma = 0; /* ECMA version */
inq->inq_ansi = 3; /* ANSI version - SCSI 3 */
inq->inq_aenc = 0; /* Async event notification cap. */
inq->inq_trmiop = 0; /* Supports TERMINATE I/O PROC msg - NO */
inq->inq_normaca = 0; /* setting NACA bit supported - NO */
inq->inq_rdf = RDF_SCSI2; /* Response data format- SPC-3 */
inq->inq_len = 31; /* Additional length */
inq->inq_dualp = 0; /* dual port device - NO */
inq->inq_reladdr = 0; /* Supports relative addressing - NO */
inq->inq_sync = 0; /* Supports synchronous data xfers - NO */
inq->inq_linked = 0; /* Supports linked commands - NO */
/*
* Queuing support - controller has to
* support some sort of command queuing.
*/
if (SATA_QDEPTH(sata_hba_inst) > 1)
inq->inq_cmdque = 1; /* Supports command queueing - YES */
else
inq->inq_cmdque = 0; /* Supports command queueing - NO */
inq->inq_sftre = 0; /* Supports Soft Reset option - NO ??? */
inq->inq_wbus32 = 0; /* Supports 32 bit wide data xfers - NO */
inq->inq_wbus16 = 0; /* Supports 16 bit wide data xfers - NO */
#ifdef _LITTLE_ENDIAN
/* Swap text fields to match SCSI format */
bcopy("ATA ", inq->inq_vid, 8); /* Vendor ID */
swab(sid->ai_model, inq->inq_pid, 16); /* Product ID */
if (strncmp(&sid->ai_fw[4], " ", 4) == 0)
swab(sid->ai_fw, inq->inq_revision, 4); /* Revision level */
else
swab(&sid->ai_fw[4], inq->inq_revision, 4); /* Rev. level */
#else /* _LITTLE_ENDIAN */
bcopy("ATA ", inq->inq_vid, 8); /* Vendor ID */
bcopy(sid->ai_model, inq->inq_pid, 16); /* Product ID */
if (strncmp(&sid->ai_fw[4], " ", 4) == 0)
bcopy(sid->ai_fw, inq->inq_revision, 4); /* Revision level */
else
bcopy(&sid->ai_fw[4], inq->inq_revision, 4); /* Rev. level */
#endif /* _LITTLE_ENDIAN */
}
/*
* Scsi response set up for invalid command (command not supported)
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_invalid_command(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense *sense;
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_COMMAND_CODE;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)spx->txlt_scsi_pkt->pkt_comp,
(void *)spx->txlt_scsi_pkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* Scsi response setup for
* emulated non-data command that requires no action/return data
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_nodata_cmd_immediate(sata_pkt_txlate_t *spx)
{
int rval;
int reason;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
spx->txlt_scsi_pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
spx->txlt_scsi_pkt->pkt_reason = CMD_CMPLT;
*(spx->txlt_scsi_pkt->pkt_scbp) = STATUS_GOOD;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n",
spx->txlt_scsi_pkt->pkt_reason);
if ((spx->txlt_scsi_pkt->pkt_flags & FLAG_NOINTR) == 0 &&
spx->txlt_scsi_pkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)spx->txlt_scsi_pkt->pkt_comp,
(void *)spx->txlt_scsi_pkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Inquiry / Identify Device
* Use cached Identify Device data for now, rather than issuing actual
* Device Identify cmd request. If device is detached and re-attached,
* asynchromous event processing should fetch and refresh Identify Device
* data.
* Two VPD pages are supported now:
* Vital Product Data page
* Unit Serial Number page
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*/
#define EVPD 1 /* Extended Vital Product Data flag */
#define CMDDT 2 /* Command Support Data - Obsolete */
#define INQUIRY_SUP_VPD_PAGE 0 /* Supported VDP Pages Page COde */
#define INQUIRY_USN_PAGE 0x80 /* Unit Serial Number Page Code */
#define INQUIRY_DEV_IDENTIFICATION_PAGE 0x83 /* Not needed yet */
static int
sata_txlt_inquiry(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
sata_drive_info_t *sdinfo;
struct scsi_extended_sense *sense;
int count;
uint8_t *p;
int i, j;
uint8_t page_buf[0xff]; /* Max length */
int rval, reason;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
ASSERT(sdinfo != NULL);
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
/* Reject not supported request */
if (scsipkt->pkt_cdbp[1] & CMDDT) { /* No support for this bit */
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
/* Valid Inquiry request */
*scsipkt->pkt_scbp = STATUS_GOOD;
if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
/*
* Because it is fully emulated command storing data
* programatically in the specified buffer, release
* preallocated DMA resources before storing data in the buffer,
* so no unwanted DMA sync would take place.
*/
sata_scsi_dmafree(NULL, scsipkt);
if (!(scsipkt->pkt_cdbp[1] & EVPD)) {
/* Standard Inquiry Data request */
struct scsi_inquiry inq;
unsigned int bufsize;
sata_identdev_to_inquiry(spx->txlt_sata_hba_inst,
sdinfo, (uint8_t *)&inq);
/* Copy no more than requested */
count = MIN(bp->b_bcount,
sizeof (struct scsi_inquiry));
bufsize = scsipkt->pkt_cdbp[4];
bufsize |= scsipkt->pkt_cdbp[3] << 8;
count = MIN(count, bufsize);
bcopy(&inq, bp->b_un.b_addr, count);
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_resid = scsipkt->pkt_cdbp[4] > count ?
bufsize - count : 0;
} else {
/*
* peripheral_qualifier = 0;
*
* We are dealing only with HD and will be
* dealing with CD/DVD devices soon
*/
uint8_t peripheral_device_type =
sdinfo->satadrv_type == SATA_DTYPE_ATADISK ?
DTYPE_DIRECT : DTYPE_RODIRECT;
switch ((uint_t)scsipkt->pkt_cdbp[2]) {
case INQUIRY_SUP_VPD_PAGE:
/*
* Request for suported Vital Product Data
* pages - assuming only 2 page codes
* supported.
*/
page_buf[0] = peripheral_device_type;
page_buf[1] = INQUIRY_SUP_VPD_PAGE;
page_buf[2] = 0;
page_buf[3] = 2; /* page length */
page_buf[4] = INQUIRY_SUP_VPD_PAGE;
page_buf[5] = INQUIRY_USN_PAGE;
/* Copy no more than requested */
count = MIN(bp->b_bcount, 6);
bcopy(page_buf, bp->b_un.b_addr, count);
break;
case INQUIRY_USN_PAGE:
/*
* Request for Unit Serial Number page.
* Set-up the page.
*/
page_buf[0] = peripheral_device_type;
page_buf[1] = INQUIRY_USN_PAGE;
page_buf[2] = 0;
/* remaining page length */
page_buf[3] = SATA_ID_SERIAL_LEN;
/*
* Copy serial number from Identify Device data
* words into the inquiry page and swap bytes
* when necessary.
*/
p = (uint8_t *)(sdinfo->satadrv_id.ai_drvser);
#ifdef _LITTLE_ENDIAN
swab(p, &page_buf[4], SATA_ID_SERIAL_LEN);
#else
bcopy(p, &page_buf[4], SATA_ID_SERIAL_LEN);
#endif
/*
* Least significant character of the serial
* number shall appear as the last byte,
* according to SBC-3 spec.
* Count trailing spaces to determine the
* necessary shift length.
*/
p = &page_buf[SATA_ID_SERIAL_LEN + 4 - 1];
for (j = 0; j < SATA_ID_SERIAL_LEN; j++) {
if (*(p - j) != '\0' &&
*(p - j) != '\040')
break;
}
/*
* Shift SN string right, so that the last
* non-blank character would appear in last
* byte of SN field in the page.
* 'j' is the shift length.
*/
for (i = 0;
i < (SATA_ID_SERIAL_LEN - j) && j != 0;
i++, p--)
*p = *(p - j);
/*
* Add leading spaces - same number as the
* shift size
*/
for (; j > 0; j--)
page_buf[4 + j - 1] = '\040';
count = MIN(bp->b_bcount,
SATA_ID_SERIAL_LEN + 4);
bcopy(page_buf, bp->b_un.b_addr, count);
break;
case INQUIRY_DEV_IDENTIFICATION_PAGE:
/*
* We may want to implement this page, when
* identifiers are common for SATA devices
* But not now.
*/
/*FALLTHROUGH*/
default:
/* Request for unsupported VPD page */
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code =
SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
}
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_resid = scsipkt->pkt_cdbp[4] > count ?
scsipkt->pkt_cdbp[4] - count : 0;
}
done:
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n",
scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL) {
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Request Sense.
* Emulated command (ATA version for SATA hard disks)
* Always NO SENSE, because any sense data should be reported by ARQ sense.
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*
* Note: There is a mismatch between already implemented Informational
* Exception Mode Select page 0x1C and this function.
* When MRIE bit is set in page 0x1C, Request Sense is supposed to return
* NO SENSE and set additional sense code to the exception code - this is not
* implemented here.
*/
static int
sata_txlt_request_sense(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense sense;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
int rval, reason;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
*scsipkt->pkt_scbp = STATUS_GOOD;
if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
/*
* Because it is fully emulated command storing data
* programatically in the specified buffer, release
* preallocated DMA resources before storing data in the buffer,
* so no unwanted DMA sync would take place.
*/
int count = MIN(bp->b_bcount,
sizeof (struct scsi_extended_sense));
sata_scsi_dmafree(NULL, scsipkt);
bzero(&sense, sizeof (struct scsi_extended_sense));
sense.es_valid = 0; /* Valid LBA */
sense.es_class = 7; /* Response code 0x70 - current err */
sense.es_key = KEY_NO_SENSE;
sense.es_add_len = 6; /* Additional length */
/* Copy no more than requested */
bcopy(&sense, bp->b_un.b_addr, count);
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_resid = 0;
}
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n",
scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Test Unit Ready
* At the moment this is an emulated command (ATA version for SATA hard disks).
* May be translated into Check Power Mode command in the future
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_test_unit_ready(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense *sense;
int power_state;
int rval, reason;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
/* At this moment, emulate it rather than execute anything */
power_state = SATA_PWRMODE_ACTIVE;
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
switch (power_state) {
case SATA_PWRMODE_ACTIVE:
case SATA_PWRMODE_IDLE:
*scsipkt->pkt_scbp = STATUS_GOOD;
break;
default:
/* PWR mode standby */
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_NOT_READY;
sense->es_add_code = SD_SCSI_ASC_LU_NOT_READY;
break;
}
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Start Stop Unit
* Translation depends on a command:
* Start Unit translated into Idle Immediate
* Stop Unit translated into Standby Immediate
* Unload Media / NOT SUPPORTED YET
* Load Media / NOT SUPPROTED YET
* Power condition bits are ignored, so is Immediate bit
* Requesting synchronous execution.
*
* Returns TRAN_ACCEPT or code returned by sata_hba_start() and
* appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_start_stop_unit(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
struct scsi_extended_sense *sense;
sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
int cport = SATA_TXLT_CPORT(spx);
int rval, reason;
int synch;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_start_stop_unit: %d\n", scsipkt->pkt_scbp[4] & 1);
mutex_enter(&SATA_CPORT_MUTEX(shi, cport));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
if (scsipkt->pkt_cdbp[4] & 2) {
/* Load/Unload Media - invalid request */
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
scmd->satacmd_addr_type = 0;
scmd->satacmd_sec_count_lsb = 0;
scmd->satacmd_lba_low_lsb = 0;
scmd->satacmd_lba_mid_lsb = 0;
scmd->satacmd_lba_high_lsb = 0;
scmd->satacmd_features_reg = 0;
scmd->satacmd_device_reg = 0;
scmd->satacmd_status_reg = 0;
if (scsipkt->pkt_cdbp[4] & 1) {
/* Start Unit */
scmd->satacmd_cmd_reg = SATAC_IDLE_IM;
} else {
/* Stop Unit */
scmd->satacmd_cmd_reg = SATAC_STANDBY_IM;
}
if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
/* Need to set-up a callback function */
spx->txlt_sata_pkt->satapkt_comp =
sata_txlt_nodata_cmd_completion;
synch = FALSE;
} else {
spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_SYNCH;
synch = TRUE;
}
/* Transfer command to HBA */
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
return (rval);
}
/*
* If execution is non-synchronous,
* a callback function will handle potential errors, translate
* the response and will do a callback to a target driver.
* If it was synchronous, check execution status using the same
* framework callback.
*/
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
if (synch) {
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"synchronous execution status %x\n",
spx->txlt_sata_pkt->satapkt_reason);
sata_txlt_nodata_cmd_completion(spx->txlt_sata_pkt);
}
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Read Capacity.
* Emulated command for SATA disks.
* Capacity is retrieved from cached Idenifty Device data.
* Identify Device data shows effective disk capacity, not the native
* capacity, which may be limitted by Set Max Address command.
* This is ATA version for SATA hard disks.
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_read_capacity(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
sata_drive_info_t *sdinfo;
uint64_t val;
uchar_t *rbuf;
int rval, reason;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_read_capacity: ", NULL);
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
*scsipkt->pkt_scbp = STATUS_GOOD;
if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
/*
* Because it is fully emulated command storing data
* programatically in the specified buffer, release
* preallocated DMA resources before storing data in the buffer,
* so no unwanted DMA sync would take place.
*/
sata_scsi_dmafree(NULL, scsipkt);
sdinfo = sata_get_device_info(
spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
/* Last logical block address */
val = sdinfo->satadrv_capacity - 1;
rbuf = (uchar_t *)bp->b_un.b_addr;
/* Need to swap endians to match scsi format */
rbuf[0] = (val >> 24) & 0xff;
rbuf[1] = (val >> 16) & 0xff;
rbuf[2] = (val >> 8) & 0xff;
rbuf[3] = val & 0xff;
/* block size - always 512 bytes, for now */
rbuf[4] = 0;
rbuf[5] = 0;
rbuf[6] = 0x02;
rbuf[7] = 0;
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_resid = 0;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst, "%d\n",
sdinfo->satadrv_capacity -1);
}
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
/*
* If a callback was requested, do it now.
*/
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Mode Sense.
* Translated into appropriate SATA command or emulated.
* Saved Values Page Control (03) are not supported.
*
* NOTE: only caching mode sense page is currently implemented.
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_mode_sense(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
sata_drive_info_t *sdinfo;
sata_id_t *sata_id;
struct scsi_extended_sense *sense;
int len, bdlen, count, alc_len;
int pc; /* Page Control code */
uint8_t *buf; /* mode sense buffer */
int rval, reason;
SATADBG2(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_mode_sense, pc %x page code 0x%02x\n",
spx->txlt_scsi_pkt->pkt_cdbp[2] >> 6,
spx->txlt_scsi_pkt->pkt_cdbp[2] & 0x3f);
buf = kmem_zalloc(1024, KM_SLEEP);
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
kmem_free(buf, 1024);
return (rval);
}
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
pc = scsipkt->pkt_cdbp[2] >> 6;
if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
/*
* Because it is fully emulated command storing data
* programatically in the specified buffer, release
* preallocated DMA resources before storing data in the buffer,
* so no unwanted DMA sync would take place.
*/
sata_scsi_dmafree(NULL, scsipkt);
len = 0;
bdlen = 0;
if (!(scsipkt->pkt_cdbp[1] & 8)) {
if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SENSE_G1 &&
(scsipkt->pkt_cdbp[0] & 0x10))
bdlen = 16;
else
bdlen = 8;
}
/* Build mode parameter header */
if (spx->txlt_scsi_pkt->pkt_cdbp[0] == SCMD_MODE_SENSE) {
/* 4-byte mode parameter header */
buf[len++] = 0; /* mode data length */
buf[len++] = 0; /* medium type */
buf[len++] = 0; /* dev-specific param */
buf[len++] = bdlen; /* Block Descriptor length */
} else {
/* 8-byte mode parameter header */
buf[len++] = 0; /* mode data length */
buf[len++] = 0;
buf[len++] = 0; /* medium type */
buf[len++] = 0; /* dev-specific param */
if (bdlen == 16)
buf[len++] = 1; /* long lba descriptor */
else
buf[len++] = 0;
buf[len++] = 0;
buf[len++] = 0; /* Block Descriptor length */
buf[len++] = bdlen;
}
sdinfo = sata_get_device_info(
spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
/* Build block descriptor only if not disabled (DBD) */
if ((scsipkt->pkt_cdbp[1] & 0x08) == 0) {
/* Block descriptor - direct-access device format */
if (bdlen == 8) {
/* build regular block descriptor */
buf[len++] =
(sdinfo->satadrv_capacity >> 24) & 0xff;
buf[len++] =
(sdinfo->satadrv_capacity >> 16) & 0xff;
buf[len++] =
(sdinfo->satadrv_capacity >> 8) & 0xff;
buf[len++] = sdinfo->satadrv_capacity & 0xff;
buf[len++] = 0; /* density code */
buf[len++] = 0;
if (sdinfo->satadrv_type ==
SATA_DTYPE_ATADISK)
buf[len++] = 2;
else
/* ATAPI */
buf[len++] = 8;
buf[len++] = 0;
} else if (bdlen == 16) {
/* Long LBA Accepted */
/* build long lba block descriptor */
#ifndef __lock_lint
buf[len++] =
(sdinfo->satadrv_capacity >> 56) & 0xff;
buf[len++] =
(sdinfo->satadrv_capacity >> 48) & 0xff;
buf[len++] =
(sdinfo->satadrv_capacity >> 40) & 0xff;
buf[len++] =
(sdinfo->satadrv_capacity >> 32) & 0xff;
#endif
buf[len++] =
(sdinfo->satadrv_capacity >> 24) & 0xff;
buf[len++] =
(sdinfo->satadrv_capacity >> 16) & 0xff;
buf[len++] =
(sdinfo->satadrv_capacity >> 8) & 0xff;
buf[len++] = sdinfo->satadrv_capacity & 0xff;
buf[len++] = 0;
buf[len++] = 0; /* density code */
buf[len++] = 0;
buf[len++] = 0;
if (sdinfo->satadrv_type ==
SATA_DTYPE_ATADISK)
buf[len++] = 2;
else
/* ATAPI */
buf[len++] = 8;
buf[len++] = 0;
}
}
sata_id = &sdinfo->satadrv_id;
/*
* Add requested pages.
* Page 3 and 4 are obsolete and we are not supporting them.
* We deal now with:
* caching (read/write cache control).
* We should eventually deal with following mode pages:
* error recovery (0x01),
* power condition (0x1a),
* exception control page (enables SMART) (0x1c),
* enclosure management (ses),
* protocol-specific port mode (port control).
*/
switch (scsipkt->pkt_cdbp[2] & 0x3f) {
case MODEPAGE_RW_ERRRECOV:
/* DAD_MODE_ERR_RECOV */
/* R/W recovery */
len += sata_build_msense_page_1(sdinfo, pc, buf+len);
break;
case MODEPAGE_CACHING:
/* DAD_MODE_CACHE */
/* Reject not supported request for saved parameters */
if (pc == 3) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code =
SD_SCSI_ASC_SAVING_PARAMS_NOT_SUPPORTED;
goto done;
}
/* caching */
len += sata_build_msense_page_8(sdinfo, pc, buf+len);
break;
case MODEPAGE_INFO_EXCPT:
/* exception cntrl */
if (sata_id->ai_cmdset82 & SATA_SMART_SUPPORTED) {
len += sata_build_msense_page_1c(sdinfo, pc,
buf+len);
}
else
goto err;
break;
case MODEPAGE_POWER_COND:
/* DAD_MODE_POWER_COND */
/* power condition */
len += sata_build_msense_page_1a(sdinfo, pc, buf+len);
break;
case MODEPAGE_ACOUSTIC_MANAG:
/* acoustic management */
len += sata_build_msense_page_30(sdinfo, pc, buf+len);
break;
case MODEPAGE_ALLPAGES:
/* all pages */
len += sata_build_msense_page_1(sdinfo, pc, buf+len);
len += sata_build_msense_page_8(sdinfo, pc, buf+len);
len += sata_build_msense_page_1a(sdinfo, pc, buf+len);
if (sata_id->ai_cmdset82 & SATA_SMART_SUPPORTED) {
len += sata_build_msense_page_1c(sdinfo, pc,
buf+len);
}
len += sata_build_msense_page_30(sdinfo, pc, buf+len);
break;
default:
err:
/* Invalid request */
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
/* fix total mode data length */
if (spx->txlt_scsi_pkt->pkt_cdbp[0] == SCMD_MODE_SENSE) {
/* 4-byte mode parameter header */
buf[0] = len - 1; /* mode data length */
} else {
buf[0] = (len -2) >> 8;
buf[1] = (len -2) & 0xff;
}
/* Check allocation length */
if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SENSE) {
alc_len = scsipkt->pkt_cdbp[4];
} else {
alc_len = scsipkt->pkt_cdbp[7];
alc_len = (len << 8) | scsipkt->pkt_cdbp[8];
}
/*
* We do not check for possible parameters truncation
* (alc_len < len) assuming that the target driver works
* correctly. Just avoiding overrun.
* Copy no more than requested and possible, buffer-wise.
*/
count = MIN(alc_len, len);
count = MIN(bp->b_bcount, count);
bcopy(buf, bp->b_un.b_addr, count);
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_resid = alc_len > count ? alc_len - count : 0;
}
*scsipkt->pkt_scbp = STATUS_GOOD;
done:
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
(void) kmem_free(buf, 1024);
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Mode Select.
* Translated into appropriate SATA command or emulated.
* Saving parameters is not supported.
* Changing device capacity is not supported (although theoretically
* possible by executing SET FEATURES/SET MAX ADDRESS)
*
* Assumption is that the target driver is working correctly.
*
* More than one SATA command may be executed to perform operations specified
* by mode select pages. The first error terminates further execution.
* Operations performed successully are not backed-up in such case.
*
* NOTE: Implemented pages:
* - caching page
* - informational exception page
* - acoustic management page
* Caching setup is remembered so it could be re-stored in case of
* an unexpected device reset.
*
* Returns TRAN_XXXX.
* If TRAN_ACCEPT is returned, appropriate values are set in scsi_pkt fields.
*/
static int
sata_txlt_mode_select(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
struct scsi_extended_sense *sense;
int len, pagelen, count, pllen;
uint8_t *buf; /* mode select buffer */
int rval, stat, reason;
uint_t nointr_flag;
int dmod = 0;
SATADBG2(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_mode_select, pc %x page code 0x%02x\n",
spx->txlt_scsi_pkt->pkt_cdbp[2] >> 6,
spx->txlt_scsi_pkt->pkt_cdbp[2] & 0x3f);
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
/*
* If in interrupt context, reject this packet because it may result
* in issuing a synchronous command to HBA.
*/
if (servicing_interrupt()) {
SATADBG1(SATA_DBG_INTR_CTX, spx->txlt_sata_hba_inst,
"sata_txlt_mode_select: rejecting command because "
"of interrupt context\n", NULL);
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (TRAN_BUSY);
}
rval = TRAN_ACCEPT;
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
nointr_flag = scsipkt->pkt_flags & FLAG_NOINTR;
/* Reject not supported request */
if (! (scsipkt->pkt_cdbp[1] & 0x10)) { /* No support for PF bit = 0 */
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SELECT) {
pllen = scsipkt->pkt_cdbp[4];
} else {
pllen = scsipkt->pkt_cdbp[7];
pllen = (pllen << 8) | scsipkt->pkt_cdbp[7];
}
*scsipkt->pkt_scbp = STATUS_GOOD; /* Presumed outcome */
if (bp != NULL && bp->b_un.b_addr && bp->b_bcount && pllen != 0) {
buf = (uint8_t *)bp->b_un.b_addr;
count = MIN(bp->b_bcount, pllen);
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_resid = 0;
pllen = count;
/*
* Check the header to skip the block descriptor(s) - we
* do not support setting device capacity.
* Existing macros do not recognize long LBA dscriptor,
* hence manual calculation.
*/
if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SELECT) {
/* 6-bytes CMD, 4 bytes header */
if (count <= 4)
goto done; /* header only */
len = buf[3] + 4;
} else {
/* 10-bytes CMD, 8 bytes header */
if (count <= 8)
goto done; /* header only */
len = buf[6];
len = (len << 8) + buf[7] + 8;
}
if (len >= count)
goto done; /* header + descriptor(s) only */
pllen -= len; /* remaining data length */
/*
* We may be executing SATA command and want to execute it
* in SYNCH mode, regardless of scsi_pkt setting.
* Save scsi_pkt setting and indicate SYNCH mode
*/
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL) {
scsipkt->pkt_flags |= FLAG_NOINTR;
}
spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_SYNCH;
/*
* len is now the offset to a first mode select page
* Process all pages
*/
while (pllen > 0) {
switch ((int)buf[len]) {
case MODEPAGE_CACHING:
/* No support for SP (saving) */
if (scsipkt->pkt_cdbp[1] & 0x01) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code =
SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
stat = sata_mode_select_page_8(spx,
(struct mode_cache_scsi3 *)&buf[len],
pllen, &pagelen, &rval, &dmod);
/*
* The pagelen value indicates the number of
* parameter bytes already processed.
* The rval is the return value from
* sata_tran_start().
* The stat indicates the overall status of
* the operation(s).
*/
if (stat != SATA_SUCCESS)
/*
* Page processing did not succeed -
* all error info is already set-up,
* just return
*/
pllen = 0; /* this breaks the loop */
else {
len += pagelen;
pllen -= pagelen;
}
break;
case MODEPAGE_INFO_EXCPT:
stat = sata_mode_select_page_1c(spx,
(struct mode_info_excpt_page *)&buf[len],
pllen, &pagelen, &rval, &dmod);
/*
* The pagelen value indicates the number of
* parameter bytes already processed.
* The rval is the return value from
* sata_tran_start().
* The stat indicates the overall status of
* the operation(s).
*/
if (stat != SATA_SUCCESS)
/*
* Page processing did not succeed -
* all error info is already set-up,
* just return
*/
pllen = 0; /* this breaks the loop */
else {
len += pagelen;
pllen -= pagelen;
}
break;
case MODEPAGE_ACOUSTIC_MANAG:
stat = sata_mode_select_page_30(spx,
(struct mode_acoustic_management *)
&buf[len], pllen, &pagelen, &rval, &dmod);
/*
* The pagelen value indicates the number of
* parameter bytes already processed.
* The rval is the return value from
* sata_tran_start().
* The stat indicates the overall status of
* the operation(s).
*/
if (stat != SATA_SUCCESS)
/*
* Page processing did not succeed -
* all error info is already set-up,
* just return
*/
pllen = 0; /* this breaks the loop */
else {
len += pagelen;
pllen -= pagelen;
}
break;
default:
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code =
SD_SCSI_ASC_INVALID_FIELD_IN_PARAMS_LIST;
goto done;
}
}
}
done:
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
/*
* If device parameters were modified, fetch and store the new
* Identify Device data. Since port mutex could have been released
* for accessing HBA driver, we need to re-check device existence.
*/
if (dmod != 0) {
sata_drive_info_t new_sdinfo, *sdinfo;
int rv = 0;
/*
* Following statement has to be changed if this function is
* used for devices other than SATA hard disks.
*/
new_sdinfo.satadrv_type = SATA_DTYPE_ATADISK;
new_sdinfo.satadrv_addr =
spx->txlt_sata_pkt->satapkt_device.satadev_addr;
rv = sata_fetch_device_identify_data(spx->txlt_sata_hba_inst,
&new_sdinfo);
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
/*
* Since port mutex could have been released when
* accessing HBA driver, we need to re-check that the
* framework still holds the device info structure.
*/
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
if (sdinfo != NULL) {
/*
* Device still has info structure in the
* sata framework. Copy newly fetched info
*/
if (rv == 0) {
sdinfo->satadrv_id = new_sdinfo.satadrv_id;
sata_save_drive_settings(sdinfo);
} else {
/*
* Could not fetch new data - invalidate
* sata_drive_info. That makes device
* unusable.
*/
sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
sdinfo->satadrv_state = SATA_STATE_UNKNOWN;
}
}
if (rv != 0 || sdinfo == NULL) {
/*
* This changes the overall mode select completion
* reason to a failed one !!!!!
*/
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
scsipkt->pkt_reason = CMD_INCOMPLETE;
rval = TRAN_ACCEPT;
}
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
}
/* Restore the scsi pkt flags */
scsipkt->pkt_flags &= ~FLAG_NOINTR;
scsipkt->pkt_flags |= nointr_flag;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (rval);
}
/*
* Translate command: Log Sense
*/
static int
sata_txlt_log_sense(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
sata_drive_info_t *sdinfo;
struct scsi_extended_sense *sense;
int len, count, alc_len;
int pc; /* Page Control code */
int page_code; /* Page code */
uint8_t *buf; /* log sense buffer */
int rval, reason;
#define MAX_LOG_SENSE_PAGE_SIZE 512
SATADBG2(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_log_sense, pc 0x%x, page code 0x%x\n",
spx->txlt_scsi_pkt->pkt_cdbp[2] >> 6,
spx->txlt_scsi_pkt->pkt_cdbp[2] & 0x3f);
buf = kmem_zalloc(MAX_LOG_SENSE_PAGE_SIZE, KM_SLEEP);
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
kmem_free(buf, MAX_LOG_SENSE_PAGE_SIZE);
return (rval);
}
/*
* If in interrupt context, reject this packet because it may result
* in issuing a synchronous command to HBA.
*/
if (servicing_interrupt()) {
SATADBG1(SATA_DBG_INTR_CTX, spx->txlt_sata_hba_inst,
"sata_log_sense: rejecting command because "
"of interrupt context\n", NULL);
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
kmem_free(buf, MAX_LOG_SENSE_PAGE_SIZE);
return (TRAN_BUSY);
}
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
pc = scsipkt->pkt_cdbp[2] >> 6;
page_code = scsipkt->pkt_cdbp[2] & 0x3f;
/* Reject not supported request for all but cumulative values */
switch (pc) {
case PC_CUMULATIVE_VALUES:
break;
default:
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
switch (page_code) {
case PAGE_CODE_GET_SUPPORTED_LOG_PAGES:
case PAGE_CODE_SELF_TEST_RESULTS:
case PAGE_CODE_INFORMATION_EXCEPTIONS:
case PAGE_CODE_SMART_READ_DATA:
break;
default:
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
/*
* Because log sense uses local buffers for data retrieval from
* the devices and sets the data programatically in the
* original specified buffer, release preallocated DMA
* resources before storing data in the original buffer,
* so no unwanted DMA sync would take place.
*/
sata_id_t *sata_id;
sata_scsi_dmafree(NULL, scsipkt);
len = 0;
/* Build log parameter header */
buf[len++] = page_code; /* page code as in the CDB */
buf[len++] = 0; /* reserved */
buf[len++] = 0; /* Zero out page length for now (MSB) */
buf[len++] = 0; /* (LSB) */
sdinfo = sata_get_device_info(
spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
/*
* Add requested pages.
*/
switch (page_code) {
case PAGE_CODE_GET_SUPPORTED_LOG_PAGES:
len = sata_build_lsense_page_0(sdinfo, buf + len);
break;
case PAGE_CODE_SELF_TEST_RESULTS:
sata_id = &sdinfo->satadrv_id;
if ((! (sata_id->ai_cmdset84 &
SATA_SMART_SELF_TEST_SUPPORTED)) ||
(! (sata_id->ai_features87 &
SATA_SMART_SELF_TEST_SUPPORTED))) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code =
SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
len = sata_build_lsense_page_10(sdinfo, buf + len,
spx->txlt_sata_hba_inst);
break;
case PAGE_CODE_INFORMATION_EXCEPTIONS:
sata_id = &sdinfo->satadrv_id;
if (! (sata_id->ai_cmdset82 & SATA_SMART_SUPPORTED)) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code =
SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
if (! (sata_id->ai_features85 & SATA_SMART_ENABLED)) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ABORTED_COMMAND;
sense->es_add_code =
SCSI_ASC_ATA_DEV_FEAT_NOT_ENABLED;
sense->es_qual_code =
SCSI_ASCQ_ATA_DEV_FEAT_NOT_ENABLED;
goto done;
}
len = sata_build_lsense_page_2f(sdinfo, buf + len,
spx->txlt_sata_hba_inst);
break;
case PAGE_CODE_SMART_READ_DATA:
sata_id = &sdinfo->satadrv_id;
if (! (sata_id->ai_cmdset82 & SATA_SMART_SUPPORTED)) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code =
SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
if (! (sata_id->ai_features85 & SATA_SMART_ENABLED)) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ABORTED_COMMAND;
sense->es_add_code =
SCSI_ASC_ATA_DEV_FEAT_NOT_ENABLED;
sense->es_qual_code =
SCSI_ASCQ_ATA_DEV_FEAT_NOT_ENABLED;
goto done;
}
/* This page doesn't include a page header */
len = sata_build_lsense_page_30(sdinfo, buf,
spx->txlt_sata_hba_inst);
goto no_header;
default:
/* Invalid request */
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
goto done;
}
/* set parameter log sense data length */
buf[2] = len >> 8; /* log sense length (MSB) */
buf[3] = len & 0xff; /* log sense length (LSB) */
len += SCSI_LOG_PAGE_HDR_LEN;
ASSERT(len <= MAX_LOG_SENSE_PAGE_SIZE);
no_header:
/* Check allocation length */
alc_len = scsipkt->pkt_cdbp[7];
alc_len = (len << 8) | scsipkt->pkt_cdbp[8];
/*
* We do not check for possible parameters truncation
* (alc_len < len) assuming that the target driver works
* correctly. Just avoiding overrun.
* Copy no more than requested and possible, buffer-wise.
*/
count = MIN(alc_len, len);
count = MIN(bp->b_bcount, count);
bcopy(buf, bp->b_un.b_addr, count);
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_resid = alc_len > count ? alc_len - count : 0;
}
*scsipkt->pkt_scbp = STATUS_GOOD;
done:
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
(void) kmem_free(buf, MAX_LOG_SENSE_PAGE_SIZE);
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* Translate command: Log Select
* Not implemented at this time - returns invalid command response.
*/
static int
sata_txlt_log_select(sata_pkt_txlate_t *spx)
{
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_log_select\n", NULL);
return (sata_txlt_invalid_command(spx));
}
/*
* Translate command: Read (various types).
* Translated into appropriate type of ATA READ command
* for SATA hard disks.
* Both the device capabilities and requested operation mode are
* considered.
*
* Following scsi cdb fields are ignored:
* rdprotect, dpo, fua, fua_nv, group_number.
*
* If SATA_ENABLE_QUEUING flag is set (in the global SATA HBA framework
* enable variable sata_func_enable), the capability of the controller and
* capability of a device are checked and if both support queueing, read
* request will be translated to READ_DMA_QUEUEING or READ_DMA_QUEUEING_EXT
* command rather than plain READ_XXX command.
* If SATA_ENABLE_NCQ flag is set in addition to SATA_ENABLE_QUEUING flag and
* both the controller and device suport such functionality, the read
* request will be translated to READ_FPDMA_QUEUED command.
* In both cases the maximum queue depth is derived as minimum of:
* HBA capability,device capability and sata_max_queue_depth variable setting.
* The value passed to HBA driver is decremented by 1, because only 5 bits are
* used to pass max queue depth value, and the maximum possible queue depth
* is 32.
*
* Returns TRAN_ACCEPT or code returned by sata_hba_start() and
* appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_read(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
sata_drive_info_t *sdinfo;
sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
int cport = SATA_TXLT_CPORT(spx);
uint16_t sec_count;
uint64_t lba;
int rval, reason;
int synch;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
/*
* Extract LBA and sector count from scsi CDB.
*/
switch ((uint_t)scsipkt->pkt_cdbp[0]) {
case SCMD_READ:
/* 6-byte scsi read cmd : 0x08 */
lba = (scsipkt->pkt_cdbp[1] & 0x1f);
lba = (lba << 8) | scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
sec_count = scsipkt->pkt_cdbp[4];
/* sec_count 0 will be interpreted as 256 by a device */
break;
case SCMD_READ_G1:
/* 10-bytes scsi read command : 0x28 */
lba = scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
lba = (lba << 8) | scsipkt->pkt_cdbp[4];
lba = (lba << 8) | scsipkt->pkt_cdbp[5];
sec_count = scsipkt->pkt_cdbp[7];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
break;
case SCMD_READ_G5:
/* 12-bytes scsi read command : 0xA8 */
lba = scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
lba = (lba << 8) | scsipkt->pkt_cdbp[4];
lba = (lba << 8) | scsipkt->pkt_cdbp[5];
sec_count = scsipkt->pkt_cdbp[6];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[7];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[9];
break;
case SCMD_READ_G4:
/* 16-bytes scsi read command : 0x88 */
lba = scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
lba = (lba << 8) | scsipkt->pkt_cdbp[4];
lba = (lba << 8) | scsipkt->pkt_cdbp[5];
lba = (lba << 8) | scsipkt->pkt_cdbp[6];
lba = (lba << 8) | scsipkt->pkt_cdbp[7];
lba = (lba << 8) | scsipkt->pkt_cdbp[8];
lba = (lba << 8) | scsipkt->pkt_cdbp[9];
sec_count = scsipkt->pkt_cdbp[10];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[11];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[12];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[13];
break;
default:
/* Unsupported command */
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (sata_txlt_invalid_command(spx));
}
/*
* Check if specified address exceeds device capacity
*/
if ((lba >= sdinfo->satadrv_capacity) ||
((lba + sec_count) > sdinfo->satadrv_capacity)) {
/* LBA out of range */
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (sata_txlt_lba_out_of_range(spx));
}
/*
* For zero-length transfer, emulate good completion of the command
* (reasons for rejecting the command were already checked).
* No DMA resources were allocated.
*/
if (spx->txlt_dma_cookie_list == NULL) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (sata_emul_rw_completion(spx));
}
/*
* Build cmd block depending on the device capability and
* requested operation mode.
* Do not bother with non-dma mode - we are working only with
* devices supporting DMA.
*/
scmd->satacmd_addr_type = ATA_ADDR_LBA;
scmd->satacmd_device_reg = SATA_ADH_LBA;
scmd->satacmd_cmd_reg = SATAC_READ_DMA;
if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA48) {
scmd->satacmd_addr_type = ATA_ADDR_LBA48;
scmd->satacmd_cmd_reg = SATAC_READ_DMA_EXT;
scmd->satacmd_sec_count_msb = sec_count >> 8;
#ifndef __lock_lint
scmd->satacmd_lba_low_msb = (lba >> 24) & 0xff;
scmd->satacmd_lba_mid_msb = (lba >> 32) & 0xff;
scmd->satacmd_lba_high_msb = lba >> 40;
#endif
} else if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA28) {
scmd->satacmd_addr_type = ATA_ADDR_LBA28;
scmd->satacmd_device_reg = SATA_ADH_LBA | ((lba >> 24) & 0xf);
}
scmd->satacmd_sec_count_lsb = sec_count & 0xff;
scmd->satacmd_lba_low_lsb = lba & 0xff;
scmd->satacmd_lba_mid_lsb = (lba >> 8) & 0xff;
scmd->satacmd_lba_high_lsb = (lba >> 16) & 0xff;
scmd->satacmd_features_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
/*
* Check if queueing commands should be used and switch
* to appropriate command if possible
*/
if (sata_func_enable & SATA_ENABLE_QUEUING) {
boolean_t using_queuing;
/* Queuing supported by controller and device? */
if ((sata_func_enable & SATA_ENABLE_NCQ) &&
(sdinfo->satadrv_features_support &
SATA_DEV_F_NCQ) &&
(SATA_FEATURES(spx->txlt_sata_hba_inst) &
SATA_CTLF_NCQ)) {
using_queuing = B_TRUE;
/* NCQ supported - use FPDMA READ */
scmd->satacmd_cmd_reg =
SATAC_READ_FPDMA_QUEUED;
scmd->satacmd_features_reg_ext =
scmd->satacmd_sec_count_msb;
scmd->satacmd_sec_count_msb = 0;
} else if ((sdinfo->satadrv_features_support &
SATA_DEV_F_TCQ) &&
(SATA_FEATURES(spx->txlt_sata_hba_inst) &
SATA_CTLF_QCMD)) {
using_queuing = B_TRUE;
/* Legacy queueing */
if (sdinfo->satadrv_features_support &
SATA_DEV_F_LBA48) {
scmd->satacmd_cmd_reg =
SATAC_READ_DMA_QUEUED_EXT;
scmd->satacmd_features_reg_ext =
scmd->satacmd_sec_count_msb;
scmd->satacmd_sec_count_msb = 0;
} else {
scmd->satacmd_cmd_reg =
SATAC_READ_DMA_QUEUED;
}
} else /* NCQ nor legacy queuing not supported */
using_queuing = B_FALSE;
/*
* If queuing, the sector count goes in the features register
* and the secount count will contain the tag.
*/
if (using_queuing) {
scmd->satacmd_features_reg =
scmd->satacmd_sec_count_lsb;
scmd->satacmd_sec_count_lsb = 0;
scmd->satacmd_flags.sata_queued = B_TRUE;
/* Set-up maximum queue depth */
scmd->satacmd_flags.sata_max_queue_depth =
sdinfo->satadrv_max_queue_depth - 1;
} else if (sdinfo->satadrv_features_enabled &
SATA_DEV_F_E_UNTAGGED_QING) {
/*
* Although NCQ/TCQ is not enabled, untagged queuing
* may be still used.
* Set-up the maximum untagged queue depth.
* Use controller's queue depth from sata_hba_tran.
* SATA HBA drivers may ignore this value and rely on
* the internal limits.For drivers that do not
* ignore untaged queue depth, limit the value to
* SATA_MAX_QUEUE_DEPTH (32), as this is the
* largest value that can be passed via
* satacmd_flags.sata_max_queue_depth.
*/
scmd->satacmd_flags.sata_max_queue_depth =
SATA_QDEPTH(shi) <= SATA_MAX_QUEUE_DEPTH ?
SATA_QDEPTH(shi) - 1: SATA_MAX_QUEUE_DEPTH - 1;
} else {
scmd->satacmd_flags.sata_max_queue_depth = 0;
}
} else
scmd->satacmd_flags.sata_max_queue_depth = 0;
SATADBG3(SATA_DBG_HBA_IF, spx->txlt_sata_hba_inst,
"sata_txlt_read cmd 0x%2x, lba %llx, sec count %x\n",
scmd->satacmd_cmd_reg, lba, sec_count);
if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
/* Need callback function */
spx->txlt_sata_pkt->satapkt_comp = sata_txlt_rw_completion;
synch = FALSE;
} else
synch = TRUE;
/* Transfer command to HBA */
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
return (rval);
}
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
/*
* If execution is non-synchronous,
* a callback function will handle potential errors, translate
* the response and will do a callback to a target driver.
* If it was synchronous, check execution status using the same
* framework callback.
*/
if (synch) {
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"synchronous execution status %x\n",
spx->txlt_sata_pkt->satapkt_reason);
sata_txlt_rw_completion(spx->txlt_sata_pkt);
}
return (TRAN_ACCEPT);
}
/*
* SATA translate command: Write (various types)
* Translated into appropriate type of ATA WRITE command
* for SATA hard disks.
* Both the device capabilities and requested operation mode are
* considered.
*
* Following scsi cdb fields are ignored:
* rwprotect, dpo, fua, fua_nv, group_number.
*
* If SATA_ENABLE_QUEUING flag is set (in the global SATA HBA framework
* enable variable sata_func_enable), the capability of the controller and
* capability of a device are checked and if both support queueing, write
* request will be translated to WRITE_DMA_QUEUEING or WRITE_DMA_QUEUEING_EXT
* command rather than plain WRITE_XXX command.
* If SATA_ENABLE_NCQ flag is set in addition to SATA_ENABLE_QUEUING flag and
* both the controller and device suport such functionality, the write
* request will be translated to WRITE_FPDMA_QUEUED command.
* In both cases the maximum queue depth is derived as minimum of:
* HBA capability,device capability and sata_max_queue_depth variable setting.
* The value passed to HBA driver is decremented by 1, because only 5 bits are
* used to pass max queue depth value, and the maximum possible queue depth
* is 32.
*
* Returns TRAN_ACCEPT or code returned by sata_hba_start() and
* appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_write(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
sata_drive_info_t *sdinfo;
sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
int cport = SATA_TXLT_CPORT(spx);
uint16_t sec_count;
uint64_t lba;
int rval, reason;
int synch;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
scmd->satacmd_flags.sata_data_direction = SATA_DIR_WRITE;
/*
* Extract LBA and sector count from scsi CDB
*/
switch ((uint_t)scsipkt->pkt_cdbp[0]) {
case SCMD_WRITE:
/* 6-byte scsi read cmd : 0x0A */
lba = (scsipkt->pkt_cdbp[1] & 0x1f);
lba = (lba << 8) | scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
sec_count = scsipkt->pkt_cdbp[4];
/* sec_count 0 will be interpreted as 256 by a device */
break;
case SCMD_WRITE_G1:
/* 10-bytes scsi write command : 0x2A */
lba = scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
lba = (lba << 8) | scsipkt->pkt_cdbp[4];
lba = (lba << 8) | scsipkt->pkt_cdbp[5];
sec_count = scsipkt->pkt_cdbp[7];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
break;
case SCMD_WRITE_G5:
/* 12-bytes scsi read command : 0xAA */
lba = scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
lba = (lba << 8) | scsipkt->pkt_cdbp[4];
lba = (lba << 8) | scsipkt->pkt_cdbp[5];
sec_count = scsipkt->pkt_cdbp[6];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[7];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[9];
break;
case SCMD_WRITE_G4:
/* 16-bytes scsi write command : 0x8A */
lba = scsipkt->pkt_cdbp[2];
lba = (lba << 8) | scsipkt->pkt_cdbp[3];
lba = (lba << 8) | scsipkt->pkt_cdbp[4];
lba = (lba << 8) | scsipkt->pkt_cdbp[5];
lba = (lba << 8) | scsipkt->pkt_cdbp[6];
lba = (lba << 8) | scsipkt->pkt_cdbp[7];
lba = (lba << 8) | scsipkt->pkt_cdbp[8];
lba = (lba << 8) | scsipkt->pkt_cdbp[9];
sec_count = scsipkt->pkt_cdbp[10];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[11];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[12];
sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[13];
break;
default:
/* Unsupported command */
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (sata_txlt_invalid_command(spx));
}
/*
* Check if specified address and length exceeds device capacity
*/
if ((lba >= sdinfo->satadrv_capacity) ||
((lba + sec_count) > sdinfo->satadrv_capacity)) {
/* LBA out of range */
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (sata_txlt_lba_out_of_range(spx));
}
/*
* For zero-length transfer, emulate good completion of the command
* (reasons for rejecting the command were already checked).
* No DMA resources were allocated.
*/
if (spx->txlt_dma_cookie_list == NULL) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (sata_emul_rw_completion(spx));
}
/*
* Build cmd block depending on the device capability and
* requested operation mode.
* Do not bother with non-dma mode- we are working only with
* devices supporting DMA.
*/
scmd->satacmd_addr_type = ATA_ADDR_LBA;
scmd->satacmd_device_reg = SATA_ADH_LBA;
scmd->satacmd_cmd_reg = SATAC_WRITE_DMA;
if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA48) {
scmd->satacmd_addr_type = ATA_ADDR_LBA48;
scmd->satacmd_cmd_reg = SATAC_WRITE_DMA_EXT;
scmd->satacmd_sec_count_msb = sec_count >> 8;
scmd->satacmd_lba_low_msb = (lba >> 24) & 0xff;
#ifndef __lock_lint
scmd->satacmd_lba_mid_msb = (lba >> 32) & 0xff;
scmd->satacmd_lba_high_msb = lba >> 40;
#endif
} else if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA28) {
scmd->satacmd_addr_type = ATA_ADDR_LBA28;
scmd->satacmd_device_reg = SATA_ADH_LBA | ((lba >> 24) & 0xf);
}
scmd->satacmd_sec_count_lsb = sec_count & 0xff;
scmd->satacmd_lba_low_lsb = lba & 0xff;
scmd->satacmd_lba_mid_lsb = (lba >> 8) & 0xff;
scmd->satacmd_lba_high_lsb = (lba >> 16) & 0xff;
scmd->satacmd_features_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
/*
* Check if queueing commands should be used and switch
* to appropriate command if possible
*/
if (sata_func_enable & SATA_ENABLE_QUEUING) {
boolean_t using_queuing;
/* Queuing supported by controller and device? */
if ((sata_func_enable & SATA_ENABLE_NCQ) &&
(sdinfo->satadrv_features_support &
SATA_DEV_F_NCQ) &&
(SATA_FEATURES(spx->txlt_sata_hba_inst) &
SATA_CTLF_NCQ)) {
using_queuing = B_TRUE;
/* NCQ supported - use FPDMA WRITE */
scmd->satacmd_cmd_reg =
SATAC_WRITE_FPDMA_QUEUED;
scmd->satacmd_features_reg_ext =
scmd->satacmd_sec_count_msb;
scmd->satacmd_sec_count_msb = 0;
} else if ((sdinfo->satadrv_features_support &
SATA_DEV_F_TCQ) &&
(SATA_FEATURES(spx->txlt_sata_hba_inst) &
SATA_CTLF_QCMD)) {
using_queuing = B_TRUE;
/* Legacy queueing */
if (sdinfo->satadrv_features_support &
SATA_DEV_F_LBA48) {
scmd->satacmd_cmd_reg =
SATAC_WRITE_DMA_QUEUED_EXT;
scmd->satacmd_features_reg_ext =
scmd->satacmd_sec_count_msb;
scmd->satacmd_sec_count_msb = 0;
} else {
scmd->satacmd_cmd_reg =
SATAC_WRITE_DMA_QUEUED;
}
} else /* NCQ nor legacy queuing not supported */
using_queuing = B_FALSE;
if (using_queuing) {
scmd->satacmd_features_reg =
scmd->satacmd_sec_count_lsb;
scmd->satacmd_sec_count_lsb = 0;
scmd->satacmd_flags.sata_queued = B_TRUE;
/* Set-up maximum queue depth */
scmd->satacmd_flags.sata_max_queue_depth =
sdinfo->satadrv_max_queue_depth - 1;
} else if (sdinfo->satadrv_features_enabled &
SATA_DEV_F_E_UNTAGGED_QING) {
/*
* Although NCQ/TCQ is not enabled, untagged queuing
* may be still used.
* Set-up the maximum untagged queue depth.
* Use controller's queue depth from sata_hba_tran.
* SATA HBA drivers may ignore this value and rely on
* the internal limits. For drivera that do not
* ignore untaged queue depth, limit the value to
* SATA_MAX_QUEUE_DEPTH (32), as this is the
* largest value that can be passed via
* satacmd_flags.sata_max_queue_depth.
*/
scmd->satacmd_flags.sata_max_queue_depth =
SATA_QDEPTH(shi) <= SATA_MAX_QUEUE_DEPTH ?
SATA_QDEPTH(shi) - 1: SATA_MAX_QUEUE_DEPTH - 1;
} else {
scmd->satacmd_flags.sata_max_queue_depth = 0;
}
} else
scmd->satacmd_flags.sata_max_queue_depth = 0;
SATADBG3(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_write cmd 0x%2x, lba %llx, sec count %x\n",
scmd->satacmd_cmd_reg, lba, sec_count);
if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
/* Need callback function */
spx->txlt_sata_pkt->satapkt_comp = sata_txlt_rw_completion;
synch = FALSE;
} else
synch = TRUE;
/* Transfer command to HBA */
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
return (rval);
}
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
/*
* If execution is non-synchronous,
* a callback function will handle potential errors, translate
* the response and will do a callback to a target driver.
* If it was synchronous, check execution status using the same
* framework callback.
*/
if (synch) {
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"synchronous execution status %x\n",
spx->txlt_sata_pkt->satapkt_reason);
sata_txlt_rw_completion(spx->txlt_sata_pkt);
}
return (TRAN_ACCEPT);
}
/*
* Implements SCSI SBC WRITE BUFFER command download microcode option
*/
static int
sata_txlt_write_buffer(sata_pkt_txlate_t *spx)
{
#define WB_DOWNLOAD_MICROCODE_AND_REVERT_MODE 4
#define WB_DOWNLOAD_MICROCODE_AND_SAVE_MODE 5
sata_hba_inst_t *sata_hba_inst = SATA_TXLT_HBA_INST(spx);
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct sata_pkt *sata_pkt = spx->txlt_sata_pkt;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
struct scsi_extended_sense *sense;
int rval, mode, sector_count, reason;
int cport = SATA_TXLT_CPORT(spx);
mode = scsipkt->pkt_cdbp[1] & 0x1f;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_write_buffer, mode 0x%x\n", mode);
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if ((rval = sata_txlt_generic_pkt_info(spx, &reason)) != TRAN_ACCEPT) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
/*
* If in interrupt context, reject this packet because it would issue
* a synchronous command to HBA.
*/
if (servicing_interrupt()) {
SATADBG1(SATA_DBG_INTR_CTX, spx->txlt_sata_hba_inst,
"sata_txlt_write_buffer: rejecting command because "
"of interrupt context\n", NULL);
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (TRAN_BUSY);
}
/* Use synchronous mode */
spx->txlt_sata_pkt->satapkt_op_mode
|= SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_WRITE;
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
/*
* The SCSI to ATA translation specification only calls
* for WB_DOWNLOAD_MICROCODE_AND_SAVE_MODE.
* WB_DOWNLOAD_MICROC_AND_REVERT_MODE is implemented, but
* ATA 8 (draft) got rid of download microcode for temp
* and it is even optional for ATA 7, so it may be aborted.
* WB_DOWNLOAD_MICROCODE_WITH_OFFSET is not implemented as
* it is not specified and the buffer offset for SCSI is a 16-bit
* value in bytes, but for ATA it is a 16-bit offset in 512 byte
* sectors. Thus the offset really doesn't buy us anything.
* If and when ATA 8 is stabilized and the SCSI to ATA specification
* is revised, this can be revisisted.
*/
/* Reject not supported request */
switch (mode) {
case WB_DOWNLOAD_MICROCODE_AND_REVERT_MODE:
scmd->satacmd_features_reg = SATA_DOWNLOAD_MCODE_TEMP;
break;
case WB_DOWNLOAD_MICROCODE_AND_SAVE_MODE:
scmd->satacmd_features_reg = SATA_DOWNLOAD_MCODE_SAVE;
break;
default:
goto bad_param;
}
*scsipkt->pkt_scbp = STATUS_GOOD; /* Presumed outcome */
scmd->satacmd_cmd_reg = SATAC_DOWNLOAD_MICROCODE;
if ((bp->b_bcount % SATA_DISK_SECTOR_SIZE) != 0)
goto bad_param;
sector_count = bp->b_bcount / SATA_DISK_SECTOR_SIZE;
scmd->satacmd_sec_count_lsb = (uint8_t)sector_count;
scmd->satacmd_lba_low_lsb = ((uint16_t)sector_count) >> 8;
scmd->satacmd_lba_mid_lsb = 0;
scmd->satacmd_lba_high_lsb = 0;
scmd->satacmd_device_reg = 0;
spx->txlt_sata_pkt->satapkt_comp = NULL;
scmd->satacmd_addr_type = 0;
/* Transfer command to HBA */
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
mutex_exit(&SATA_CPORT_MUTEX(sata_hba_inst, cport));
return (rval);
}
mutex_exit(&SATA_CPORT_MUTEX(sata_hba_inst, cport));
/* Then we need synchronous check the status of the disk */
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS;
if (sata_pkt->satapkt_reason == SATA_PKT_COMPLETED) {
scsipkt->pkt_reason = CMD_CMPLT;
/* Download commmand succeed, so probe and identify device */
sata_reidentify_device(spx);
} else {
/* Something went wrong, microcode download command failed */
scsipkt->pkt_reason = CMD_INCOMPLETE;
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
switch (sata_pkt->satapkt_reason) {
case SATA_PKT_PORT_ERROR:
/*
* We have no device data. Assume no data transfered.
*/
sense->es_key = KEY_HARDWARE_ERROR;
break;
case SATA_PKT_DEV_ERROR:
if (sata_pkt->satapkt_cmd.satacmd_status_reg &
SATA_STATUS_ERR) {
/*
* determine dev error reason from error
* reg content
*/
sata_decode_device_error(spx, sense);
break;
}
/* No extended sense key - no info available */
break;
case SATA_PKT_TIMEOUT:
scsipkt->pkt_reason = CMD_TIMEOUT;
scsipkt->pkt_statistics |=
STAT_TIMEOUT | STAT_DEV_RESET;
/* No extended sense key ? */
break;
case SATA_PKT_ABORTED:
scsipkt->pkt_reason = CMD_ABORTED;
scsipkt->pkt_statistics |= STAT_ABORTED;
/* No extended sense key ? */
break;
case SATA_PKT_RESET:
/* pkt aborted by an explicit reset from a host */
scsipkt->pkt_reason = CMD_RESET;
scsipkt->pkt_statistics |= STAT_DEV_RESET;
break;
default:
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_txlt_nodata_cmd_completion: "
"invalid packet completion reason %d",
sata_pkt->satapkt_reason));
scsipkt->pkt_reason = CMD_TRAN_ERR;
break;
}
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0)
/* scsi callback required */
scsi_hba_pkt_comp(scsipkt);
}
return (TRAN_ACCEPT);
bad_param:
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL) {
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == 0) {
/* Scheduling the callback failed */
rval = TRAN_BUSY;
}
}
return (rval);
}
/*
* Re-identify device after doing a firmware download.
*/
static void
sata_reidentify_device(sata_pkt_txlate_t *spx)
{
#define DOWNLOAD_WAIT_TIME_SECS 60
#define DOWNLOAD_WAIT_INTERVAL_SECS 1
int rval;
int retry_cnt;
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_hba_inst_t *sata_hba_inst = spx->txlt_sata_hba_inst;
sata_device_t sata_device = spx->txlt_sata_pkt->satapkt_device;
sata_drive_info_t *sdinfo;
/*
* Before returning good status, probe device.
* Device probing will get IDENTIFY DEVICE data, if possible.
* The assumption is that the new microcode is applied by the
* device. It is a caller responsibility to verify this.
*/
for (retry_cnt = 0;
retry_cnt < DOWNLOAD_WAIT_TIME_SECS / DOWNLOAD_WAIT_INTERVAL_SECS;
retry_cnt++) {
rval = sata_probe_device(sata_hba_inst, &sata_device);
if (rval == SATA_SUCCESS) { /* Set default features */
sdinfo = sata_get_device_info(sata_hba_inst,
&sata_device);
if (sata_initialize_device(sata_hba_inst, sdinfo) !=
SATA_SUCCESS) {
/* retry */
rval = sata_initialize_device(sata_hba_inst,
sdinfo);
if (rval == SATA_RETRY)
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d pmport %d -"
" default device features could not"
" be set. Device may not operate "
"as expected.",
sata_device.satadev_addr.cport,
sata_device.satadev_addr.pmport);
}
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0)
scsi_hba_pkt_comp(scsipkt);
return;
} else if (rval == SATA_RETRY) {
delay(drv_usectohz(1000000 *
DOWNLOAD_WAIT_INTERVAL_SECS));
continue;
} else /* failed - no reason to retry */
break;
}
/*
* Something went wrong, device probing failed.
*/
SATA_LOG_D((sata_hba_inst, CE_WARN,
"Cannot probe device after downloading microcode\n"));
/* Reset device to force retrying the probe. */
(void) (*SATA_RESET_DPORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0)
scsi_hba_pkt_comp(scsipkt);
}
/*
* Translate command: Synchronize Cache.
* Translates into Flush Cache command for SATA hard disks.
*
* Returns TRAN_ACCEPT or code returned by sata_hba_start() and
* appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_synchronize_cache(sata_pkt_txlate_t *spx)
{
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
int cport = SATA_TXLT_CPORT(spx);
int rval, reason;
int synch;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
scmd->satacmd_addr_type = 0;
scmd->satacmd_cmd_reg = SATAC_FLUSH_CACHE;
scmd->satacmd_device_reg = 0;
scmd->satacmd_sec_count_lsb = 0;
scmd->satacmd_lba_low_lsb = 0;
scmd->satacmd_lba_mid_lsb = 0;
scmd->satacmd_lba_high_lsb = 0;
scmd->satacmd_features_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"sata_txlt_synchronize_cache\n", NULL);
if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
/* Need to set-up a callback function */
spx->txlt_sata_pkt->satapkt_comp =
sata_txlt_nodata_cmd_completion;
synch = FALSE;
} else
synch = TRUE;
/* Transfer command to HBA */
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
return (rval);
}
mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
/*
* If execution non-synchronous, it had to be completed
* a callback function will handle potential errors, translate
* the response and will do a callback to a target driver.
* If it was synchronous, check status, using the same
* framework callback.
*/
if (synch) {
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"synchronous execution status %x\n",
spx->txlt_sata_pkt->satapkt_reason);
sata_txlt_nodata_cmd_completion(spx->txlt_sata_pkt);
}
return (TRAN_ACCEPT);
}
/*
* Send pkt to SATA HBA driver
*
* This function may be called only if the operation is requested by scsi_pkt,
* i.e. scsi_pkt is not NULL.
*
* This function has to be called with cport mutex held. It does release
* the mutex when it calls HBA driver sata_tran_start function and
* re-acquires it afterwards.
*
* If return value is 0, pkt was accepted, -1 otherwise
* rval is set to appropriate sata_scsi_start return value.
*
* Note 1:If HBA driver returns value other than TRAN_ACCEPT, it should not
* have called the sata_pkt callback function for this packet.
*
* The scsi callback has to be performed by the caller of this routine.
*
* Note 2: No port multiplier support for now.
*/
static int
sata_hba_start(sata_pkt_txlate_t *spx, int *rval)
{
int stat, cport;
sata_hba_inst_t *sata_hba_inst = spx->txlt_sata_hba_inst;
sata_drive_info_t *sdinfo;
sata_device_t *sata_device;
uint8_t cmd;
struct sata_cmd_flags cmd_flags;
ASSERT(spx->txlt_sata_pkt != NULL);
cport = SATA_TXLT_CPORT(spx);
ASSERT(mutex_owned(&SATA_CPORT_MUTEX(sata_hba_inst, cport)));
sdinfo = sata_get_device_info(sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
ASSERT(sdinfo != NULL);
/* Clear device reset state? */
if (sdinfo->satadrv_event_flags & SATA_EVNT_CLEAR_DEVICE_RESET) {
spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags.
sata_clear_dev_reset = B_TRUE;
sdinfo->satadrv_event_flags &= ~SATA_EVNT_CLEAR_DEVICE_RESET;
SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
"sata_hba_start: clearing device reset state\n", NULL);
}
cmd = spx->txlt_sata_pkt->satapkt_cmd.satacmd_cmd_reg;
cmd_flags = spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags;
sata_device = &spx->txlt_sata_pkt->satapkt_device;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Sata cmd 0x%2x\n", cmd);
stat = (*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst),
spx->txlt_sata_pkt);
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
/*
* If sata pkt was accepted and executed in asynchronous mode, i.e.
* with the sata callback, the sata_pkt could be already destroyed
* by the time we check ther return status from the hba_start()
* function, because sata_scsi_destroy_pkt() could have been already
* called (perhaps in the interrupt context). So, in such case, there
* should be no references to it. In other cases, sata_pkt still
* exists.
*/
if (stat == SATA_TRAN_ACCEPTED) {
/*
* pkt accepted for execution.
* If it was executed synchronously, it is already completed
* and pkt completion_reason indicates completion status.
*/
*rval = TRAN_ACCEPT;
return (0);
}
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
switch (stat) {
case SATA_TRAN_QUEUE_FULL:
/*
* Controller detected queue full condition.
*/
SATADBG1(SATA_DBG_HBA_IF, sata_hba_inst,
"sata_hba_start: queue full\n", NULL);
spx->txlt_scsi_pkt->pkt_reason = CMD_INCOMPLETE;
*spx->txlt_scsi_pkt->pkt_scbp = STATUS_QFULL;
*rval = TRAN_BUSY;
break;
case SATA_TRAN_PORT_ERROR:
/*
* Communication/link with device or general port error
* detected before pkt execution begun.
*/
if (spx->txlt_sata_pkt->satapkt_device.satadev_addr.qual ==
SATA_ADDR_CPORT ||
spx->txlt_sata_pkt->satapkt_device.satadev_addr.qual ==
SATA_ADDR_DCPORT)
sata_log(sata_hba_inst, CE_CONT,
"SATA port %d error",
sata_device->satadev_addr.cport);
else
sata_log(sata_hba_inst, CE_CONT,
"SATA port %d pmport %d error\n",
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport);
/*
* Update the port/device structure.
* sata_pkt should be still valid. Since port error is
* returned, sata_device content should reflect port
* state - it means, that sata address have been changed,
* because original packet's sata address refered to a device
* attached to some port.
*/
sata_update_port_info(sata_hba_inst, sata_device);
spx->txlt_scsi_pkt->pkt_reason = CMD_TRAN_ERR;
*rval = TRAN_FATAL_ERROR;
break;
case SATA_TRAN_CMD_UNSUPPORTED:
/*
* Command rejected by HBA as unsupported. It was HBA driver
* that rejected the command, command was not sent to
* an attached device.
*/
if ((sdinfo != NULL) &&
(sdinfo->satadrv_state & SATA_DSTATE_RESET))
SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
"sat_hba_start: cmd 0x%2x rejected "
"with SATA_TRAN_CMD_UNSUPPORTED status\n", cmd);
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
(void) sata_txlt_invalid_command(spx);
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
*rval = TRAN_ACCEPT;
break;
case SATA_TRAN_BUSY:
/*
* Command rejected by HBA because other operation prevents
* accepting the packet, or device is in RESET condition.
*/
if (sdinfo != NULL) {
sdinfo->satadrv_state =
spx->txlt_sata_pkt->satapkt_device.satadev_state;
if (sdinfo->satadrv_state & SATA_DSTATE_RESET) {
SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
"sata_hba_start: cmd 0x%2x rejected "
"because of device reset condition\n",
cmd);
} else {
SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
"sata_hba_start: cmd 0x%2x rejected "
"with SATA_TRAN_BUSY status\n",
cmd);
}
}
spx->txlt_scsi_pkt->pkt_reason = CMD_INCOMPLETE;
*rval = TRAN_BUSY;
break;
default:
/* Unrecognized HBA response */
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_start: unrecognized HBA response "
"to cmd : 0x%2x resp 0x%x", cmd, rval));
spx->txlt_scsi_pkt->pkt_reason = CMD_TRAN_ERR;
*rval = TRAN_FATAL_ERROR;
break;
}
/*
* If we got here, the packet was rejected.
* Check if we need to remember reset state clearing request
*/
if (cmd_flags.sata_clear_dev_reset) {
/*
* Check if device is still configured - it may have
* disapeared from the configuration
*/
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
if (sdinfo != NULL) {
/*
* Restore the flag that requests clearing of
* the device reset state,
* so the next sata packet may carry it to HBA.
*/
sdinfo->satadrv_event_flags |=
SATA_EVNT_CLEAR_DEVICE_RESET;
}
}
return (-1);
}
/*
* Scsi response setup for invalid LBA
*
* Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
*/
static int
sata_txlt_lba_out_of_range(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense *sense;
scsipkt->pkt_reason = CMD_CMPLT;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
*scsipkt->pkt_scbp = STATUS_CHECK;
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_LBA_OUT_OF_RANGE;
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL)
/* scsi callback required */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp, (void *) scsipkt,
TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
return (TRAN_ACCEPT);
}
/*
* Analyze device status and error registers and translate them into
* appropriate scsi sense codes.
* NOTE: non-packet commands only for now
*/
static void
sata_decode_device_error(sata_pkt_txlate_t *spx,
struct scsi_extended_sense *sense)
{
uint8_t err_reg = spx->txlt_sata_pkt->satapkt_cmd.satacmd_error_reg;
ASSERT(sense != NULL);
ASSERT(spx->txlt_sata_pkt->satapkt_cmd.satacmd_status_reg &
SATA_STATUS_ERR);
if (err_reg & SATA_ERROR_ICRC) {
sense->es_key = KEY_ABORTED_COMMAND;
sense->es_add_code = 0x08; /* Communication failure */
return;
}
if (err_reg & SATA_ERROR_UNC) {
sense->es_key = KEY_MEDIUM_ERROR;
/* Information bytes (LBA) need to be set by a caller */
return;
}
/* ADD HERE: MC error bit handling for ATAPI CD/DVD */
if (err_reg & (SATA_ERROR_MCR | SATA_ERROR_NM)) {
sense->es_key = KEY_UNIT_ATTENTION;
sense->es_add_code = 0x3a; /* No media present */
return;
}
if (err_reg & SATA_ERROR_IDNF) {
if (err_reg & SATA_ERROR_ABORT) {
sense->es_key = KEY_ABORTED_COMMAND;
} else {
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = 0x21; /* LBA out of range */
}
return;
}
if (err_reg & SATA_ERROR_ABORT) {
ASSERT(spx->txlt_sata_pkt != NULL);
sense->es_key = KEY_ABORTED_COMMAND;
return;
}
}
/*
* Extract error LBA from sata_pkt.satapkt_cmd register fields
*/
static void
sata_extract_error_lba(sata_pkt_txlate_t *spx, uint64_t *lba)
{
sata_cmd_t *sata_cmd = &spx->txlt_sata_pkt->satapkt_cmd;
*lba = 0;
if (sata_cmd->satacmd_addr_type == ATA_ADDR_LBA48) {
*lba = sata_cmd->satacmd_lba_high_msb;
*lba = (*lba << 8) | sata_cmd->satacmd_lba_mid_msb;
*lba = (*lba << 8) | sata_cmd->satacmd_lba_low_msb;
} else if (sata_cmd->satacmd_addr_type == ATA_ADDR_LBA28) {
*lba = sata_cmd->satacmd_device_reg & 0xf;
}
*lba = (*lba << 8) | sata_cmd->satacmd_lba_high_lsb;
*lba = (*lba << 8) | sata_cmd->satacmd_lba_mid_lsb;
*lba = (*lba << 8) | sata_cmd->satacmd_lba_low_lsb;
}
/*
* This is fixed sense format - if LBA exceeds the info field size,
* no valid info will be returned (valid bit in extended sense will
* be set to 0).
*/
static struct scsi_extended_sense *
sata_arq_sense(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_arq_status *arqs;
struct scsi_extended_sense *sense;
/* Fill ARQ sense data */
scsipkt->pkt_state |= STATE_ARQ_DONE;
arqs = (struct scsi_arq_status *)scsipkt->pkt_scbp;
*(uchar_t *)&arqs->sts_status = STATUS_CHECK;
*(uchar_t *)&arqs->sts_rqpkt_status = STATUS_GOOD;
arqs->sts_rqpkt_reason = CMD_CMPLT;
arqs->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_XFERRED_DATA | STATE_SENT_CMD | STATE_GOT_STATUS;
arqs->sts_rqpkt_resid = 0;
sense = &arqs->sts_sensedata;
bzero(sense, sizeof (struct scsi_extended_sense));
sata_fixed_sense_data_preset(sense);
return (sense);
}
/*
* Emulated SATA Read/Write command completion for zero-length requests.
* This request always succedes, so in synchronous mode it always returns
* TRAN_ACCEPT, and in non-synchronous mode it may return TRAN_BUSY if the
* callback cannot be scheduled.
*/
static int
sata_emul_rw_completion(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
scsipkt->pkt_reason = CMD_CMPLT;
*scsipkt->pkt_scbp = STATUS_GOOD;
if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
/* scsi callback required - have to schedule it */
if (taskq_dispatch(SATA_TXLT_TASKQ(spx),
(task_func_t *)scsipkt->pkt_comp,
(void *)scsipkt, TQ_SLEEP) == NULL)
/* Scheduling the callback failed */
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
/*
* Translate completion status of SATA read/write commands into scsi response.
* pkt completion_reason is checked to determine the completion status.
* Do scsi callback if necessary.
*
* Note: this function may be called also for synchronously executed
* commands.
* This function may be used only if scsi_pkt is non-NULL.
*/
static void
sata_txlt_rw_completion(sata_pkt_t *sata_pkt)
{
sata_pkt_txlate_t *spx =
(sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
sata_cmd_t *scmd = &sata_pkt->satapkt_cmd;
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense *sense;
uint64_t lba;
struct buf *bp;
int rval;
if (sata_pkt->satapkt_reason == SATA_PKT_COMPLETED) {
/* Normal completion */
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS;
scsipkt->pkt_reason = CMD_CMPLT;
*scsipkt->pkt_scbp = STATUS_GOOD;
if (spx->txlt_tmp_buf != NULL) {
/* Temporary buffer was used */
bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
if (bp->b_flags & B_READ) {
rval = ddi_dma_sync(
spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORCPU);
ASSERT(rval == DDI_SUCCESS);
bcopy(spx->txlt_tmp_buf, bp->b_un.b_addr,
bp->b_bcount);
}
}
} else {
/*
* Something went wrong - analyze return
*/
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
scsipkt->pkt_reason = CMD_INCOMPLETE;
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
ASSERT(sense != NULL);
/*
* SATA_PKT_DEV_ERROR is the only case where we may be able to
* extract from device registers the failing LBA.
*/
if (sata_pkt->satapkt_reason == SATA_PKT_DEV_ERROR) {
if ((scmd->satacmd_addr_type == ATA_ADDR_LBA48) &&
(scmd->satacmd_lba_mid_msb != 0 ||
scmd->satacmd_lba_high_msb != 0)) {
/*
* We have problem reporting this cmd LBA
* in fixed sense data format, because of
* the size of the scsi LBA fields.
*/
sense->es_valid = 0;
} else {
sata_extract_error_lba(spx, &lba);
sense->es_info_1 = (lba & 0xFF000000) >> 24;
sense->es_info_2 = (lba & 0xFF0000) >> 16;
sense->es_info_3 = (lba & 0xFF00) >> 8;
sense->es_info_4 = lba & 0xFF;
}
} else {
/* Invalid extended sense info */
sense->es_valid = 0;
}
switch (sata_pkt->satapkt_reason) {
case SATA_PKT_PORT_ERROR:
/* We may want to handle DEV GONE state as well */
/*
* We have no device data. Assume no data transfered.
*/
sense->es_key = KEY_HARDWARE_ERROR;
break;
case SATA_PKT_DEV_ERROR:
if (sata_pkt->satapkt_cmd.satacmd_status_reg &
SATA_STATUS_ERR) {
/*
* determine dev error reason from error
* reg content
*/
sata_decode_device_error(spx, sense);
if (sense->es_key == KEY_MEDIUM_ERROR) {
switch (scmd->satacmd_cmd_reg) {
case SATAC_READ_DMA:
case SATAC_READ_DMA_EXT:
case SATAC_READ_DMA_QUEUED:
case SATAC_READ_DMA_QUEUED_EXT:
case SATAC_READ_FPDMA_QUEUED:
/* Unrecovered read error */
sense->es_add_code =
SD_SCSI_ASC_UNREC_READ_ERR;
break;
case SATAC_WRITE_DMA:
case SATAC_WRITE_DMA_EXT:
case SATAC_WRITE_DMA_QUEUED:
case SATAC_WRITE_DMA_QUEUED_EXT:
case SATAC_WRITE_FPDMA_QUEUED:
/* Write error */
sense->es_add_code =
SD_SCSI_ASC_WRITE_ERR;
break;
default:
/* Internal error */
SATA_LOG_D((
spx->txlt_sata_hba_inst,
CE_WARN,
"sata_txlt_rw_completion :"
"internal error - invalid "
"command 0x%2x",
scmd->satacmd_cmd_reg));
break;
}
}
break;
}
/* No extended sense key - no info available */
scsipkt->pkt_reason = CMD_INCOMPLETE;
break;
case SATA_PKT_TIMEOUT:
scsipkt->pkt_reason = CMD_TIMEOUT;
scsipkt->pkt_statistics |=
STAT_TIMEOUT | STAT_DEV_RESET;
sense->es_key = KEY_ABORTED_COMMAND;
break;
case SATA_PKT_ABORTED:
scsipkt->pkt_reason = CMD_ABORTED;
scsipkt->pkt_statistics |= STAT_ABORTED;
sense->es_key = KEY_ABORTED_COMMAND;
break;
case SATA_PKT_RESET:
scsipkt->pkt_reason = CMD_RESET;
scsipkt->pkt_statistics |= STAT_DEV_RESET;
sense->es_key = KEY_ABORTED_COMMAND;
break;
default:
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_txlt_rw_completion: "
"invalid packet completion reason"));
scsipkt->pkt_reason = CMD_TRAN_ERR;
break;
}
}
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0)
/* scsi callback required */
scsi_hba_pkt_comp(scsipkt);
}
/*
* Translate completion status of non-data commands (i.e. commands returning
* no data).
* pkt completion_reason is checked to determine the completion status.
* Do scsi callback if necessary (FLAG_NOINTR == 0)
*
* Note: this function may be called also for synchronously executed
* commands.
* This function may be used only if scsi_pkt is non-NULL.
*/
static void
sata_txlt_nodata_cmd_completion(sata_pkt_t *sata_pkt)
{
sata_pkt_txlate_t *spx =
(sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense *sense;
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
if (sata_pkt->satapkt_reason == SATA_PKT_COMPLETED) {
/* Normal completion */
scsipkt->pkt_reason = CMD_CMPLT;
*scsipkt->pkt_scbp = STATUS_GOOD;
} else {
/* Something went wrong */
scsipkt->pkt_reason = CMD_INCOMPLETE;
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
switch (sata_pkt->satapkt_reason) {
case SATA_PKT_PORT_ERROR:
/*
* We have no device data. Assume no data transfered.
*/
sense->es_key = KEY_HARDWARE_ERROR;
break;
case SATA_PKT_DEV_ERROR:
if (sata_pkt->satapkt_cmd.satacmd_status_reg &
SATA_STATUS_ERR) {
/*
* determine dev error reason from error
* reg content
*/
sata_decode_device_error(spx, sense);
break;
}
/* No extended sense key - no info available */
break;
case SATA_PKT_TIMEOUT:
scsipkt->pkt_reason = CMD_TIMEOUT;
scsipkt->pkt_statistics |=
STAT_TIMEOUT | STAT_DEV_RESET;
/* No extended sense key ? */
break;
case SATA_PKT_ABORTED:
scsipkt->pkt_reason = CMD_ABORTED;
scsipkt->pkt_statistics |= STAT_ABORTED;
/* No extended sense key ? */
break;
case SATA_PKT_RESET:
/* pkt aborted by an explicit reset from a host */
scsipkt->pkt_reason = CMD_RESET;
scsipkt->pkt_statistics |= STAT_DEV_RESET;
break;
default:
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_txlt_nodata_cmd_completion: "
"invalid packet completion reason %d",
sata_pkt->satapkt_reason));
scsipkt->pkt_reason = CMD_TRAN_ERR;
break;
}
}
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0)
/* scsi callback required */
scsi_hba_pkt_comp(scsipkt);
}
/*
* Build Mode sense R/W recovery page
* NOT IMPLEMENTED
*/
static int
sata_build_msense_page_1(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(sdinfo))
_NOTE(ARGUNUSED(pcntrl))
_NOTE(ARGUNUSED(buf))
#endif
return (0);
}
/*
* Build Mode sense caching page - scsi-3 implementation.
* Page length distinguishes previous format from scsi-3 format.
* buf must have space for 0x12 bytes.
* Only DRA (disable read ahead ) and WCE (write cache enable) are changeable.
*
*/
static int
sata_build_msense_page_8(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
{
struct mode_cache_scsi3 *page = (struct mode_cache_scsi3 *)buf;
sata_id_t *sata_id = &sdinfo->satadrv_id;
/*
* Most of the fields are set to 0, being not supported and/or disabled
*/
bzero(buf, PAGELENGTH_DAD_MODE_CACHE_SCSI3);
/* Saved paramters not supported */
if (pcntrl == 3)
return (0);
if (pcntrl == 0 || pcntrl == 2) {
/*
* For now treat current and default parameters as same
* That may have to change, if target driver will complain
*/
page->mode_page.code = MODEPAGE_CACHING; /* PS = 0 */
page->mode_page.length = PAGELENGTH_DAD_MODE_CACHE_SCSI3;
if (SATA_READ_AHEAD_SUPPORTED(*sata_id) &&
!SATA_READ_AHEAD_ENABLED(*sata_id)) {
page->dra = 1; /* Read Ahead disabled */
page->rcd = 1; /* Read Cache disabled */
}
if (SATA_WRITE_CACHE_SUPPORTED(*sata_id) &&
SATA_WRITE_CACHE_ENABLED(*sata_id))
page->wce = 1; /* Write Cache enabled */
} else {
/* Changeable parameters */
page->mode_page.code = MODEPAGE_CACHING;
page->mode_page.length = PAGELENGTH_DAD_MODE_CACHE_SCSI3;
if (SATA_READ_AHEAD_SUPPORTED(*sata_id)) {
page->dra = 1;
page->rcd = 1;
}
if (SATA_WRITE_CACHE_SUPPORTED(*sata_id))
page->wce = 1;
}
return (PAGELENGTH_DAD_MODE_CACHE_SCSI3 +
sizeof (struct mode_page));
}
/*
* Build Mode sense exception cntrl page
*/
static int
sata_build_msense_page_1c(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
{
struct mode_info_excpt_page *page = (struct mode_info_excpt_page *)buf;
sata_id_t *sata_id = &sdinfo->satadrv_id;
/*
* Most of the fields are set to 0, being not supported and/or disabled
*/
bzero(buf, PAGELENGTH_INFO_EXCPT);
page->mode_page.code = MODEPAGE_INFO_EXCPT;
page->mode_page.length = PAGELENGTH_INFO_EXCPT;
/* Indicate that this is page is saveable */
page->mode_page.ps = 1;
/*
* We will return the same data for default, current and saved page.
* The only changeable bit is dexcpt and that bit is required
* by the ATA specification to be preserved across power cycles.
*/
if (pcntrl != 1) {
page->dexcpt = !(sata_id->ai_features85 & SATA_SMART_SUPPORTED);
page->mrie = MRIE_ONLY_ON_REQUEST;
}
else
page->dexcpt = 1; /* Only changeable parameter */
return (PAGELENGTH_INFO_EXCPT + sizeof (struct mode_info_excpt_page));
}
static int
sata_build_msense_page_30(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
{
struct mode_acoustic_management *page =
(struct mode_acoustic_management *)buf;
sata_id_t *sata_id = &sdinfo->satadrv_id;
/*
* Most of the fields are set to 0, being not supported and/or disabled
*/
bzero(buf, PAGELENGTH_DAD_MODE_ACOUSTIC_MANAGEMENT);
switch (pcntrl) {
case P_CNTRL_DEFAULT:
/* default paramters not supported */
return (0);
case P_CNTRL_CURRENT:
case P_CNTRL_SAVED:
/* Saved and current are supported and are identical */
page->mode_page.code = MODEPAGE_ACOUSTIC_MANAG;
page->mode_page.length =
PAGELENGTH_DAD_MODE_ACOUSTIC_MANAGEMENT;
page->mode_page.ps = 1;
/* Word 83 indicates if feature is supported */
/* If feature is not supported */
if (!(sata_id->ai_cmdset83 & SATA_ACOUSTIC_MGMT)) {
page->acoustic_manag_enable =
ACOUSTIC_DISABLED;
} else {
page->acoustic_manag_enable =
((sata_id->ai_features86 & SATA_ACOUSTIC_MGMT)
!= 0);
/* Word 94 inidicates the value */
#ifdef _LITTLE_ENDIAN
page->acoustic_manag_level =
(uchar_t)sata_id->ai_acoustic;
page->vendor_recommended_value =
sata_id->ai_acoustic >> 8;
#else
page->acoustic_manag_level =
sata_id->ai_acoustic >> 8;
page->vendor_recommended_value =
(uchar_t)sata_id->ai_acoustic;
#endif
}
break;
case P_CNTRL_CHANGEABLE:
page->mode_page.code = MODEPAGE_ACOUSTIC_MANAG;
page->mode_page.length =
PAGELENGTH_DAD_MODE_ACOUSTIC_MANAGEMENT;
page->mode_page.ps = 1;
/* Word 83 indicates if the feature is supported */
if (sata_id->ai_cmdset83 & SATA_ACOUSTIC_MGMT) {
page->acoustic_manag_enable =
ACOUSTIC_ENABLED;
page->acoustic_manag_level = 0xff;
}
break;
}
return (PAGELENGTH_DAD_MODE_ACOUSTIC_MANAGEMENT +
sizeof (struct mode_page));
}
/*
* Build Mode sense power condition page
* NOT IMPLEMENTED.
*/
static int
sata_build_msense_page_1a(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(sdinfo))
_NOTE(ARGUNUSED(pcntrl))
_NOTE(ARGUNUSED(buf))
#endif
return (0);
}
/*
* Process mode select caching page 8 (scsi3 format only).
* Read Ahead (same as read cache) and Write Cache may be turned on and off
* if these features are supported by the device. If these features are not
* supported, quietly ignore them.
* This function fails only if the SET FEATURE command sent to
* the device fails. The page format is not varified, assuming that the
* target driver operates correctly - if parameters length is too short,
* we just drop the page.
* Two command may be sent if both Read Cache/Read Ahead and Write Cache
* setting have to be changed.
* SET FEATURE command is executed synchronously, i.e. we wait here until
* it is completed, regardless of the scsi pkt directives.
*
* Note: Mode Select Caching page RCD and DRA bits are tied together, i.e.
* changing DRA will change RCD.
*
* More than one SATA command may be executed to perform operations specified
* by mode select pages. The first error terminates further execution.
* Operations performed successully are not backed-up in such case.
*
* Return SATA_SUCCESS if operation succeeded, SATA_FAILURE otherwise.
* If operation resulted in changing device setup, dmod flag should be set to
* one (1). If parameters were not changed, dmod flag should be set to 0.
* Upon return, if operation required sending command to the device, the rval
* should be set to the value returned by sata_hba_start. If operation
* did not require device access, rval should be set to TRAN_ACCEPT.
* The pagelen should be set to the length of the page.
*
* This function has to be called with a port mutex held.
*
* Returns SATA_SUCCESS if operation was successful, SATA_FAILURE otherwise.
*/
int
sata_mode_select_page_8(sata_pkt_txlate_t *spx, struct mode_cache_scsi3 *page,
int parmlen, int *pagelen, int *rval, int *dmod)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_drive_info_t *sdinfo;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
sata_id_t *sata_id;
struct scsi_extended_sense *sense;
int wce, dra; /* Current settings */
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
sata_id = &sdinfo->satadrv_id;
*dmod = 0;
/* Verify parameters length. If too short, drop it */
if (PAGELENGTH_DAD_MODE_CACHE_SCSI3 +
sizeof (struct mode_page) < parmlen) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_PARAMS_LIST;
*pagelen = parmlen;
*rval = TRAN_ACCEPT;
return (SATA_FAILURE);
}
*pagelen = PAGELENGTH_DAD_MODE_CACHE_SCSI3 + sizeof (struct mode_page);
/*
* We can manipulate only write cache and read ahead
* (read cache) setting.
*/
if (!SATA_READ_AHEAD_SUPPORTED(*sata_id) &&
!SATA_WRITE_CACHE_SUPPORTED(*sata_id)) {
/*
* None of the features is supported - ignore
*/
*rval = TRAN_ACCEPT;
return (SATA_SUCCESS);
}
/* Current setting of Read Ahead (and Read Cache) */
if (SATA_READ_AHEAD_ENABLED(*sata_id))
dra = 0; /* 0 == not disabled */
else
dra = 1;
/* Current setting of Write Cache */
if (SATA_WRITE_CACHE_ENABLED(*sata_id))
wce = 1;
else
wce = 0;
if (page->dra == dra && page->wce == wce && page->rcd == dra) {
/* nothing to do */
*rval = TRAN_ACCEPT;
return (SATA_SUCCESS);
}
/*
* Need to flip some setting
* Set-up Internal SET FEATURES command(s)
*/
scmd->satacmd_flags.sata_data_direction = SATA_DIR_NODATA_XFER;
scmd->satacmd_addr_type = 0;
scmd->satacmd_device_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
if (page->dra != dra || page->rcd != dra) {
/* Need to flip read ahead setting */
if (dra == 0)
/* Disable read ahead / read cache */
scmd->satacmd_features_reg =
SATAC_SF_DISABLE_READ_AHEAD;
else
/* Enable read ahead / read cache */
scmd->satacmd_features_reg =
SATAC_SF_ENABLE_READ_AHEAD;
/* Transfer command to HBA */
if (sata_hba_start(spx, rval) != 0)
/*
* Pkt not accepted for execution.
*/
return (SATA_FAILURE);
*dmod = 1;
/* Now process return */
if (spx->txlt_sata_pkt->satapkt_reason !=
SATA_PKT_COMPLETED) {
goto failure; /* Terminate */
}
}
/* Note that the packet is not removed, so it could be re-used */
if (page->wce != wce) {
/* Need to flip Write Cache setting */
if (page->wce == 1)
/* Enable write cache */
scmd->satacmd_features_reg =
SATAC_SF_ENABLE_WRITE_CACHE;
else
/* Disable write cache */
scmd->satacmd_features_reg =
SATAC_SF_DISABLE_WRITE_CACHE;
/* Transfer command to HBA */
if (sata_hba_start(spx, rval) != 0)
/*
* Pkt not accepted for execution.
*/
return (SATA_FAILURE);
*dmod = 1;
/* Now process return */
if (spx->txlt_sata_pkt->satapkt_reason !=
SATA_PKT_COMPLETED) {
goto failure;
}
}
return (SATA_SUCCESS);
failure:
sata_xlate_errors(spx);
return (SATA_FAILURE);
}
/*
* Process mode select informational exceptions control page 0x1c
*
* The only changeable bit is dexcpt (disable exceptions).
* MRIE (method of reporting informational exceptions) must be
* "only on request".
* This page applies to informational exceptions that report
* additional sense codes with the ADDITIONAL SENSE CODE field set to 5Dh
* (e.g.,FAILURE PREDICTION THRESHOLD EXCEEDED) or 0Bh (e.g., WARNING_).
* Informational exception conditions occur as the result of background scan
* errors, background self-test errors, or vendor specific events within a
* logical unit. An informational exception condition may occur asynchronous
* to any commands.
*
* Returns: SATA_SUCCESS if operation succeeded, SATA_FAILURE otherwise.
* If operation resulted in changing device setup, dmod flag should be set to
* one (1). If parameters were not changed, dmod flag should be set to 0.
* Upon return, if operation required sending command to the device, the rval
* should be set to the value returned by sata_hba_start. If operation
* did not require device access, rval should be set to TRAN_ACCEPT.
* The pagelen should be set to the length of the page.
*
* This function has to be called with a port mutex held.
*
* Returns SATA_SUCCESS if operation was successful, SATA_FAILURE otherwise.
*
* Cannot be called in the interrupt context.
*/
static int
sata_mode_select_page_1c(
sata_pkt_txlate_t *spx,
struct mode_info_excpt_page *page,
int parmlen,
int *pagelen,
int *rval,
int *dmod)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
sata_drive_info_t *sdinfo;
sata_id_t *sata_id;
struct scsi_extended_sense *sense;
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
sata_id = &sdinfo->satadrv_id;
*dmod = 0;
/* Verify parameters length. If too short, drop it */
if (((PAGELENGTH_INFO_EXCPT + sizeof (struct mode_page)) < parmlen) ||
page->perf || page->test || (page->mrie != MRIE_ONLY_ON_REQUEST)) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_PARAMS_LIST;
*pagelen = parmlen;
*rval = TRAN_ACCEPT;
return (SATA_FAILURE);
}
*pagelen = PAGELENGTH_INFO_EXCPT + sizeof (struct mode_page);
if (! (sata_id->ai_cmdset82 & SATA_SMART_SUPPORTED)) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_CDB;
*pagelen = parmlen;
*rval = TRAN_ACCEPT;
return (SATA_FAILURE);
}
/* If already in the state requested, we are done */
if (page->dexcpt == ! (sata_id->ai_features85 & SATA_SMART_ENABLED)) {
/* nothing to do */
*rval = TRAN_ACCEPT;
return (SATA_SUCCESS);
}
scmd->satacmd_flags.sata_data_direction = SATA_DIR_NODATA_XFER;
/* Build SMART_ENABLE or SMART_DISABLE command */
scmd->satacmd_addr_type = 0; /* N/A */
scmd->satacmd_lba_mid_lsb = SMART_MAGIC_VAL_1;
scmd->satacmd_lba_high_lsb = SMART_MAGIC_VAL_2;
scmd->satacmd_features_reg = page->dexcpt ?
SATA_SMART_DISABLE_OPS : SATA_SMART_ENABLE_OPS;
scmd->satacmd_device_reg = 0; /* Always device 0 */
scmd->satacmd_cmd_reg = SATAC_SMART;
/* Transfer command to HBA */
if (sata_hba_start(spx, rval) != 0)
/*
* Pkt not accepted for execution.
*/
return (SATA_FAILURE);
*dmod = 1; /* At least may have been modified */
/* Now process return */
if (spx->txlt_sata_pkt->satapkt_reason == SATA_PKT_COMPLETED)
return (SATA_SUCCESS);
/* Packet did not complete successfully */
sata_xlate_errors(spx);
return (SATA_FAILURE);
}
/*
* Process mode select acoustic management control page 0x30
*
*
* This function has to be called with a port mutex held.
*
* Returns SATA_SUCCESS if operation was successful, SATA_FAILURE otherwise.
*
* Cannot be called in the interrupt context.
*/
int
sata_mode_select_page_30(sata_pkt_txlate_t *spx, struct
mode_acoustic_management *page, int parmlen, int *pagelen,
int *rval, int *dmod)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_drive_info_t *sdinfo;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
sata_id_t *sata_id;
struct scsi_extended_sense *sense;
sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
&spx->txlt_sata_pkt->satapkt_device);
sata_id = &sdinfo->satadrv_id;
*dmod = 0;
/* If parmlen is too short or the feature is not supported, drop it */
if (((PAGELENGTH_DAD_MODE_ACOUSTIC_MANAGEMENT +
sizeof (struct mode_page)) < parmlen) ||
(! (sata_id->ai_cmdset83 & SATA_ACOUSTIC_MGMT))) {
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
sense->es_key = KEY_ILLEGAL_REQUEST;
sense->es_add_code = SD_SCSI_ASC_INVALID_FIELD_IN_PARAMS_LIST;
*pagelen = parmlen;
*rval = TRAN_ACCEPT;
return (SATA_FAILURE);
}
*pagelen = PAGELENGTH_DAD_MODE_ACOUSTIC_MANAGEMENT +
sizeof (struct mode_page);
/*
* We can enable and disable acoustice management and
* set the acoustic management level.
*/
/*
* Set-up Internal SET FEATURES command(s)
*/
scmd->satacmd_flags.sata_data_direction = SATA_DIR_NODATA_XFER;
scmd->satacmd_addr_type = 0;
scmd->satacmd_device_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
if (page->acoustic_manag_enable) {
scmd->satacmd_features_reg = SATAC_SF_ENABLE_ACOUSTIC;
scmd->satacmd_sec_count_lsb = page->acoustic_manag_level;
} else { /* disabling acoustic management */
scmd->satacmd_features_reg = SATAC_SF_DISABLE_ACOUSTIC;
}
/* Transfer command to HBA */
if (sata_hba_start(spx, rval) != 0)
/*
* Pkt not accepted for execution.
*/
return (SATA_FAILURE);
/* Now process return */
if (spx->txlt_sata_pkt->satapkt_reason != SATA_PKT_COMPLETED) {
sata_xlate_errors(spx);
return (SATA_FAILURE);
}
*dmod = 1;
return (SATA_SUCCESS);
}
/*
* sata_build_lsense_page0() is used to create the
* SCSI LOG SENSE page 0 (supported log pages)
*
* Currently supported pages are 0, 0x10, 0x2f and 0x30
* (supported log pages, self-test results, informational exceptions
* and Sun vendor specific ATA SMART data).
*
* Takes a sata_drive_info t * and the address of a buffer
* in which to create the page information.
*
* Returns the number of bytes valid in the buffer.
*/
static int
sata_build_lsense_page_0(sata_drive_info_t *sdinfo, uint8_t *buf)
{
struct log_parameter *lpp = (struct log_parameter *)buf;
uint8_t *page_ptr = (uint8_t *)lpp->param_values;
int num_pages_supported = 1; /* Always have GET_SUPPORTED_LOG_PAGES */
sata_id_t *sata_id = &sdinfo->satadrv_id;
lpp->param_code[0] = 0;
lpp->param_code[1] = 0;
lpp->param_ctrl_flags = LOG_CTRL_LP | LOG_CTRL_LBIN;
*page_ptr++ = PAGE_CODE_GET_SUPPORTED_LOG_PAGES;
if (sata_id->ai_cmdset82 & SATA_SMART_SUPPORTED) {
if (sata_id->ai_cmdset84 & SATA_SMART_SELF_TEST_SUPPORTED) {
*page_ptr++ = PAGE_CODE_SELF_TEST_RESULTS;
++num_pages_supported;
}
*page_ptr++ = PAGE_CODE_INFORMATION_EXCEPTIONS;
++num_pages_supported;
*page_ptr++ = PAGE_CODE_SMART_READ_DATA;
++num_pages_supported;
}
lpp->param_len = num_pages_supported;
return ((&lpp->param_values[0] - (uint8_t *)lpp) +
num_pages_supported);
}
/*
* sata_build_lsense_page_10() is used to create the
* SCSI LOG SENSE page 0x10 (self-test results)
*
* Takes a sata_drive_info t * and the address of a buffer
* in which to create the page information as well as a sata_hba_inst_t *.
*
* Returns the number of bytes valid in the buffer.
*
* Note: Self test and SMART data is accessible in device log pages.
* The log pages can be accessed by SMART READ/WRITE LOG (up to 255 sectors
* of data can be transferred by a single command), or by the General Purpose
* Logging commands (GPL) READ LOG EXT and WRITE LOG EXT (up to 65,535 sectors
* - approximately 33MB - can be transferred by a single command.
* The SCT Command response (either error or command) is the same for both
* the SMART and GPL methods of issuing commands.
* This function uses READ LOG EXT command when drive supports LBA48, and
* SMART READ command otherwise.
*
* Since above commands are executed in a synchronous mode, this function
* should not be called in an interrupt context.
*/
static int
sata_build_lsense_page_10(
sata_drive_info_t *sdinfo,
uint8_t *buf,
sata_hba_inst_t *sata_hba_inst)
{
struct log_parameter *lpp = (struct log_parameter *)buf;
int rval;
if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA48) {
struct smart_ext_selftest_log *ext_selftest_log;
ext_selftest_log = kmem_zalloc(
sizeof (struct smart_ext_selftest_log), KM_SLEEP);
rval = sata_ext_smart_selftest_read_log(sata_hba_inst, sdinfo,
ext_selftest_log, 0);
if (rval == 0) {
int index, start_index;
struct smart_ext_selftest_log_entry *entry;
static const struct smart_ext_selftest_log_entry empty =
{0};
uint16_t block_num;
int count;
boolean_t only_one_block = B_FALSE;
index = ext_selftest_log->
smart_ext_selftest_log_index[0];
index |= ext_selftest_log->
smart_ext_selftest_log_index[1] << 8;
if (index == 0)
goto out;
--index; /* Correct for 0 origin */
start_index = index; /* remember where we started */
block_num = index / ENTRIES_PER_EXT_SELFTEST_LOG_BLK;
if (block_num != 0) {
rval = sata_ext_smart_selftest_read_log(
sata_hba_inst, sdinfo, ext_selftest_log,
block_num);
if (rval != 0)
goto out;
}
index %= ENTRIES_PER_EXT_SELFTEST_LOG_BLK;
entry =
&ext_selftest_log->
smart_ext_selftest_log_entries[index];
for (count = 1;
count <= SCSI_ENTRIES_IN_LOG_SENSE_SELFTEST_RESULTS;
++count) {
uint8_t status;
uint8_t code;
uint8_t sense_key;
uint8_t add_sense_code;
uint8_t add_sense_code_qual;
/* If this is an unused entry, we are done */
if (bcmp(entry, &empty, sizeof (empty)) == 0) {
/* Broken firmware on some disks */
if (index + 1 ==
ENTRIES_PER_EXT_SELFTEST_LOG_BLK) {
--entry;
--index;
if (bcmp(entry, &empty,
sizeof (empty)) == 0)
goto out;
} else
goto out;
}
if (only_one_block &&
start_index == index)
goto out;
lpp->param_code[0] = 0;
lpp->param_code[1] = count;
lpp->param_ctrl_flags =
LOG_CTRL_LP | LOG_CTRL_LBIN;
lpp->param_len =
SCSI_LOG_SENSE_SELFTEST_PARAM_LEN;
status = entry->smart_ext_selftest_log_status;
status >>= 4;
switch (status) {
case 0:
default:
sense_key = KEY_NO_SENSE;
add_sense_code =
SD_SCSI_ASC_NO_ADD_SENSE;
add_sense_code_qual = 0;
break;
case 1:
sense_key = KEY_ABORTED_COMMAND;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_81;
break;
case 2:
sense_key = KEY_ABORTED_COMMAND;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_82;
break;
case 3:
sense_key = KEY_ABORTED_COMMAND;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_83;
break;
case 4:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_84;
break;
case 5:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_85;
break;
case 6:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_86;
break;
case 7:
sense_key = KEY_MEDIUM_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_87;
break;
case 8:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_88;
break;
}
code = 0; /* unspecified */
status |= (code << 4);
lpp->param_values[0] = status;
lpp->param_values[1] = 0; /* unspecified */
lpp->param_values[2] = entry->
smart_ext_selftest_log_timestamp[1];
lpp->param_values[3] = entry->
smart_ext_selftest_log_timestamp[0];
if (status != 0) {
lpp->param_values[4] = 0;
lpp->param_values[5] = 0;
lpp->param_values[6] = entry->
smart_ext_selftest_log_failing_lba
[5];
lpp->param_values[7] = entry->
smart_ext_selftest_log_failing_lba
[4];
lpp->param_values[8] = entry->
smart_ext_selftest_log_failing_lba
[3];
lpp->param_values[9] = entry->
smart_ext_selftest_log_failing_lba
[2];
lpp->param_values[10] = entry->
smart_ext_selftest_log_failing_lba
[1];
lpp->param_values[11] = entry->
smart_ext_selftest_log_failing_lba
[0];
} else { /* No bad block address */
lpp->param_values[4] = 0xff;
lpp->param_values[5] = 0xff;
lpp->param_values[6] = 0xff;
lpp->param_values[7] = 0xff;
lpp->param_values[8] = 0xff;
lpp->param_values[9] = 0xff;
lpp->param_values[10] = 0xff;
lpp->param_values[11] = 0xff;
}
lpp->param_values[12] = sense_key;
lpp->param_values[13] = add_sense_code;
lpp->param_values[14] = add_sense_code_qual;
lpp->param_values[15] = 0; /* undefined */
lpp = (struct log_parameter *)
(((uint8_t *)lpp) +
SCSI_LOG_PARAM_HDR_LEN +
SCSI_LOG_SENSE_SELFTEST_PARAM_LEN);
--index; /* Back up to previous entry */
if (index < 0) {
if (block_num > 0) {
--block_num;
} else {
struct read_log_ext_directory
logdir;
rval =
sata_read_log_ext_directory(
sata_hba_inst, sdinfo,
&logdir);
if (rval == -1)
goto out;
if ((logdir.read_log_ext_vers
[0] == 0) &&
(logdir.read_log_ext_vers
[1] == 0))
goto out;
block_num =
logdir.read_log_ext_nblks
[EXT_SMART_SELFTEST_LOG_PAGE
- 1][0];
block_num |= logdir.
read_log_ext_nblks
[EXT_SMART_SELFTEST_LOG_PAGE
- 1][1] << 8;
--block_num;
only_one_block =
(block_num == 0);
}
rval = sata_ext_smart_selftest_read_log(
sata_hba_inst, sdinfo,
ext_selftest_log, block_num);
if (rval != 0)
goto out;
index =
ENTRIES_PER_EXT_SELFTEST_LOG_BLK -
1;
}
index %= ENTRIES_PER_EXT_SELFTEST_LOG_BLK;
entry = &ext_selftest_log->
smart_ext_selftest_log_entries[index];
}
}
out:
kmem_free(ext_selftest_log,
sizeof (struct smart_ext_selftest_log));
} else {
struct smart_selftest_log *selftest_log;
selftest_log = kmem_zalloc(sizeof (struct smart_selftest_log),
KM_SLEEP);
rval = sata_smart_selftest_log(sata_hba_inst, sdinfo,
selftest_log);
if (rval == 0) {
int index;
int count;
struct smart_selftest_log_entry *entry;
static const struct smart_selftest_log_entry empty =
{ 0 };
index = selftest_log->smart_selftest_log_index;
if (index == 0)
goto done;
--index; /* Correct for 0 origin */
entry = &selftest_log->
smart_selftest_log_entries[index];
for (count = 1;
count <= SCSI_ENTRIES_IN_LOG_SENSE_SELFTEST_RESULTS;
++count) {
uint8_t status;
uint8_t code;
uint8_t sense_key;
uint8_t add_sense_code;
uint8_t add_sense_code_qual;
if (bcmp(entry, &empty, sizeof (empty)) == 0)
goto done;
lpp->param_code[0] = 0;
lpp->param_code[1] = count;
lpp->param_ctrl_flags =
LOG_CTRL_LP | LOG_CTRL_LBIN;
lpp->param_len =
SCSI_LOG_SENSE_SELFTEST_PARAM_LEN;
status = entry->smart_selftest_log_status;
status >>= 4;
switch (status) {
case 0:
default:
sense_key = KEY_NO_SENSE;
add_sense_code =
SD_SCSI_ASC_NO_ADD_SENSE;
break;
case 1:
sense_key = KEY_ABORTED_COMMAND;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_81;
break;
case 2:
sense_key = KEY_ABORTED_COMMAND;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_82;
break;
case 3:
sense_key = KEY_ABORTED_COMMAND;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_83;
break;
case 4:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_84;
break;
case 5:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_85;
break;
case 6:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_86;
break;
case 7:
sense_key = KEY_MEDIUM_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_87;
break;
case 8:
sense_key = KEY_HARDWARE_ERROR;
add_sense_code =
DIAGNOSTIC_FAILURE_ON_COMPONENT;
add_sense_code_qual = SCSI_COMPONENT_88;
break;
}
code = 0; /* unspecified */
status |= (code << 4);
lpp->param_values[0] = status;
lpp->param_values[1] = 0; /* unspecified */
lpp->param_values[2] = entry->
smart_selftest_log_timestamp[1];
lpp->param_values[3] = entry->
smart_selftest_log_timestamp[0];
if (status != 0) {
lpp->param_values[4] = 0;
lpp->param_values[5] = 0;
lpp->param_values[6] = 0;
lpp->param_values[7] = 0;
lpp->param_values[8] = entry->
smart_selftest_log_failing_lba[3];
lpp->param_values[9] = entry->
smart_selftest_log_failing_lba[2];
lpp->param_values[10] = entry->
smart_selftest_log_failing_lba[1];
lpp->param_values[11] = entry->
smart_selftest_log_failing_lba[0];
} else { /* No block address */
lpp->param_values[4] = 0xff;
lpp->param_values[5] = 0xff;
lpp->param_values[6] = 0xff;
lpp->param_values[7] = 0xff;
lpp->param_values[8] = 0xff;
lpp->param_values[9] = 0xff;
lpp->param_values[10] = 0xff;
lpp->param_values[11] = 0xff;
}
lpp->param_values[12] = sense_key;
lpp->param_values[13] = add_sense_code;
lpp->param_values[14] = add_sense_code_qual;
lpp->param_values[15] = 0; /* undefined */
lpp = (struct log_parameter *)
(((uint8_t *)lpp) +
SCSI_LOG_PARAM_HDR_LEN +
SCSI_LOG_SENSE_SELFTEST_PARAM_LEN);
--index; /* back up to previous entry */
if (index < 0) {
index =
NUM_SMART_SELFTEST_LOG_ENTRIES - 1;
}
entry = &selftest_log->
smart_selftest_log_entries[index];
}
}
done:
kmem_free(selftest_log, sizeof (struct smart_selftest_log));
}
return ((SCSI_LOG_PARAM_HDR_LEN + SCSI_LOG_SENSE_SELFTEST_PARAM_LEN) *
SCSI_ENTRIES_IN_LOG_SENSE_SELFTEST_RESULTS);
}
/*
* sata_build_lsense_page_2f() is used to create the
* SCSI LOG SENSE page 0x2f (informational exceptions)
*
* Takes a sata_drive_info t * and the address of a buffer
* in which to create the page information as well as a sata_hba_inst_t *.
*
* Returns the number of bytes valid in the buffer.
*
* Because it invokes function(s) that send synchronously executed command
* to the HBA, it cannot be called in the interrupt context.
*/
static int
sata_build_lsense_page_2f(
sata_drive_info_t *sdinfo,
uint8_t *buf,
sata_hba_inst_t *sata_hba_inst)
{
struct log_parameter *lpp = (struct log_parameter *)buf;
int rval;
uint8_t *smart_data;
uint8_t temp;
sata_id_t *sata_id;
#define SMART_NO_TEMP 0xff
lpp->param_code[0] = 0;
lpp->param_code[1] = 0;
lpp->param_ctrl_flags = LOG_CTRL_LP | LOG_CTRL_LBIN;
/* Now get the SMART status w.r.t. threshold exceeded */
rval = sata_fetch_smart_return_status(sata_hba_inst, sdinfo);
switch (rval) {
case 1:
lpp->param_values[0] = SCSI_PREDICTED_FAILURE;
lpp->param_values[1] = SCSI_GENERAL_HD_FAILURE;
break;
case 0:
case -1: /* failed to get data */
lpp->param_values[0] = 0; /* No failure predicted */
lpp->param_values[1] = 0;
break;
#if defined(SATA_DEBUG)
default:
cmn_err(CE_PANIC, "sata_build_lsense_page_2f bad return value");
/* NOTREACHED */
#endif
}
sata_id = &sdinfo->satadrv_id;
if (! (sata_id->ai_sctsupport & SATA_SCT_CMD_TRANS_SUP))
temp = SMART_NO_TEMP;
else {
/* Now get the temperature */
smart_data = kmem_zalloc(512, KM_SLEEP);
rval = sata_smart_read_log(sata_hba_inst, sdinfo, smart_data,
SCT_STATUS_LOG_PAGE, 1);
if (rval == -1)
temp = SMART_NO_TEMP;
else {
temp = smart_data[200];
if (temp & 0x80) {
if (temp & 0x7f)
temp = 0;
else
temp = SMART_NO_TEMP;
}
}
kmem_free(smart_data, 512);
}
lpp->param_values[2] = temp; /* most recent temperature */
lpp->param_values[3] = 0; /* required vendor specific byte */
lpp->param_len = SCSI_INFO_EXCEPTIONS_PARAM_LEN;
return (SCSI_INFO_EXCEPTIONS_PARAM_LEN + SCSI_LOG_PARAM_HDR_LEN);
}
/*
* sata_build_lsense_page_30() is used to create the
* SCSI LOG SENSE page 0x30 (Sun's vendor specific page for ATA SMART data).
*
* Takes a sata_drive_info t * and the address of a buffer
* in which to create the page information as well as a sata_hba_inst_t *.
*
* Returns the number of bytes valid in the buffer.
*/
static int
sata_build_lsense_page_30(
sata_drive_info_t *sdinfo,
uint8_t *buf,
sata_hba_inst_t *sata_hba_inst)
{
struct smart_data *smart_data = (struct smart_data *)buf;
int rval;
/* Now do the SMART READ DATA */
rval = sata_fetch_smart_data(sata_hba_inst, sdinfo, smart_data);
if (rval == -1)
return (0);
return (sizeof (struct smart_data));
}
/* ************************** ATAPI-SPECIFIC FUNCTIONS ********************** */
/*
* Start command for ATAPI device.
* This function processes scsi_pkt requests.
* Now CD/DVD, tape and ATAPI disk devices are supported.
* Most commands are packet without any translation into Packet Command.
* Some may be trapped and executed as SATA commands (not clear which one).
*
* Returns TRAN_ACCEPT if command is accepted for execution (or completed
* execution).
* Returns other TRAN_XXXX codes if command is not accepted or completed
* (see return values for sata_hba_start()).
*
* Note:
* Inquiry cdb format differs between transport version 2 and 3.
* However, the transport version 3 devices that were checked did not adhere
* to the specification (ignored MSB of the allocation length). Therefore,
* the transport version is not checked, but Inquiry allocation length is
* truncated to 255 bytes if the original allocation length set-up by the
* target driver is greater than 255 bytes.
*/
static int
sata_txlt_atapi(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
sata_hba_inst_t *sata_hba = SATA_TXLT_HBA_INST(spx);
sata_drive_info_t *sdinfo = sata_get_device_info(sata_hba,
&spx->txlt_sata_pkt->satapkt_device);
int cport = SATA_TXLT_CPORT(spx);
int cdblen;
int rval, reason;
int synch;
union scsi_cdb *cdbp = (union scsi_cdb *)scsipkt->pkt_cdbp;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (((rval = sata_txlt_generic_pkt_info(spx, &reason)) !=
TRAN_ACCEPT) || (reason == CMD_DEV_GONE)) {
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (rval);
}
/*
* ATAPI device executes some ATA commands in addition to those
* commands sent via PACKET command. These ATA commands may be
* executed by the regular SATA translation functions. None needs
* to be captured now.
*
* Commands sent via PACKET command include:
* MMC command set for ATAPI CD/DVD device
* SSC command set for ATAPI TAPE device
* SBC command set for ATAPI disk device
*
*/
/* Check the size of cdb */
cdblen = scsi_cdb_size[GETGROUP(cdbp)];
if (cdblen > sdinfo->satadrv_atapi_cdb_len) {
sata_log(NULL, CE_WARN,
"sata: invalid ATAPI cdb length %d",
scsipkt->pkt_cdblen);
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
return (TRAN_BADPKT);
}
SATAATAPITRACE(spx, cdblen);
/*
* For non-read/write commands we need to
* map buffer
*/
switch ((uint_t)scsipkt->pkt_cdbp[0]) {
case SCMD_READ:
case SCMD_READ_G1:
case SCMD_READ_G5:
case SCMD_READ_G4:
case SCMD_WRITE:
case SCMD_WRITE_G1:
case SCMD_WRITE_G5:
case SCMD_WRITE_G4:
break;
default:
if (bp != NULL) {
if (bp->b_flags & (B_PHYS | B_PAGEIO))
bp_mapin(bp);
}
break;
}
/*
* scmd->satacmd_flags.sata_data_direction default -
* SATA_DIR_NODATA_XFER - is set by
* sata_txlt_generic_pkt_info().
*/
if (scmd->satacmd_bp) {
if (scmd->satacmd_bp->b_flags & B_READ) {
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
} else {
scmd->satacmd_flags.sata_data_direction =
SATA_DIR_WRITE;
}
}
/*
* Set up ATAPI packet command.
*/
sata_atapi_packet_cmd_setup(scmd, sdinfo);
/* Copy cdb into sata_cmd */
scmd->satacmd_acdb_len = sdinfo->satadrv_atapi_cdb_len;
bzero(scmd->satacmd_acdb, SATA_ATAPI_MAX_CDB_LEN);
bcopy(cdbp, scmd->satacmd_acdb, cdblen);
/* See note in the command header */
if (scmd->satacmd_acdb[0] == SCMD_INQUIRY) {
if (scmd->satacmd_acdb[3] != 0)
scmd->satacmd_acdb[4] = 255;
}
#ifdef SATA_DEBUG
if (sata_debug_flags & SATA_DBG_ATAPI) {
uint8_t *p = scmd->satacmd_acdb;
char buf[3 * SATA_ATAPI_MAX_CDB_LEN];
(void) snprintf(buf, SATA_ATAPI_MAX_CDB_LEN,
"%02x %02x %02x %02x %02x %02x %02x %02x "
"%2x %02x %02x %02x %02x %02x %02x %02x",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
buf[(3 * SATA_ATAPI_MAX_CDB_LEN) - 1] = '\0';
cmn_err(CE_NOTE, "ATAPI cdb: %s\n", buf);
}
#endif
/*
* Preset request sense data to NO SENSE.
* If there is no way to get error information via Request Sense,
* the packet request sense data would not have to be modified by HBA,
* but it could be returned as is.
*/
bzero(scmd->satacmd_rqsense, SATA_ATAPI_RQSENSE_LEN);
sata_fixed_sense_data_preset(
(struct scsi_extended_sense *)scmd->satacmd_rqsense);
if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
/* Need callback function */
spx->txlt_sata_pkt->satapkt_comp = sata_txlt_atapi_completion;
synch = FALSE;
} else
synch = TRUE;
/* Transfer command to HBA */
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
mutex_exit(&SATA_CPORT_MUTEX(sata_hba, cport));
return (rval);
}
mutex_exit(&SATA_CPORT_MUTEX(sata_hba, cport));
/*
* If execution is non-synchronous,
* a callback function will handle potential errors, translate
* the response and will do a callback to a target driver.
* If it was synchronous, use the same framework callback to check
* an execution status.
*/
if (synch) {
SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
"synchronous execution status %x\n",
spx->txlt_sata_pkt->satapkt_reason);
sata_txlt_atapi_completion(spx->txlt_sata_pkt);
}
return (TRAN_ACCEPT);
}
/*
* ATAPI Packet command completion.
*
* Failure of the command passed via Packet command are considered device
* error. SATA HBA driver would have to retrieve error data (via Request
* Sense command delivered via error retrieval sata packet) and copy it
* to satacmd_rqsense array. From there, it is moved into scsi pkt sense data.
*/
static void
sata_txlt_atapi_completion(sata_pkt_t *sata_pkt)
{
sata_pkt_txlate_t *spx =
(sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense *sense;
struct buf *bp;
int rval;
#ifdef SATA_DEBUG
uint8_t *rqsp = sata_pkt->satapkt_cmd.satacmd_rqsense;
#endif
scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
if (sata_pkt->satapkt_reason == SATA_PKT_COMPLETED) {
/* Normal completion */
if (sata_pkt->satapkt_cmd.satacmd_bp != NULL)
scsipkt->pkt_state |= STATE_XFERRED_DATA;
scsipkt->pkt_reason = CMD_CMPLT;
*scsipkt->pkt_scbp = STATUS_GOOD;
if (spx->txlt_tmp_buf != NULL) {
/* Temporary buffer was used */
bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
if (bp->b_flags & B_READ) {
rval = ddi_dma_sync(
spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORCPU);
ASSERT(rval == DDI_SUCCESS);
bcopy(spx->txlt_tmp_buf, bp->b_un.b_addr,
bp->b_bcount);
}
}
} else {
/*
* Something went wrong - analyze return
*/
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
if (sata_pkt->satapkt_reason == SATA_PKT_DEV_ERROR) {
/*
* pkt_reason should be CMD_CMPLT for DEVICE ERROR.
* Under this condition ERR bit is set for ATA command,
* and CHK bit set for ATAPI command.
*
* Please check st_intr & sdintr about how pkt_reason
* is used.
*/
scsipkt->pkt_reason = CMD_CMPLT;
/*
* We may not have ARQ data if there was a double
* error. But sense data in sata packet was pre-set
* with NO SENSE so it is valid even if HBA could
* not retrieve a real sense data.
* Just copy this sense data into scsi pkt sense area.
*/
bcopy(sata_pkt->satapkt_cmd.satacmd_rqsense, sense,
SATA_ATAPI_MIN_RQSENSE_LEN);
#ifdef SATA_DEBUG
if (sata_debug_flags & SATA_DBG_SCSI_IF) {
sata_log(spx->txlt_sata_hba_inst, CE_WARN,
"sata_txlt_atapi_completion: %02x\n"
"RQSENSE: %02x %02x %02x %02x %02x %02x "
" %02x %02x %02x %02x %02x %02x "
" %02x %02x %02x %02x %02x %02x\n",
scsipkt->pkt_reason,
rqsp[0], rqsp[1], rqsp[2], rqsp[3],
rqsp[4], rqsp[5], rqsp[6], rqsp[7],
rqsp[8], rqsp[9], rqsp[10], rqsp[11],
rqsp[12], rqsp[13], rqsp[14], rqsp[15],
rqsp[16], rqsp[17]);
}
#endif
} else {
switch (sata_pkt->satapkt_reason) {
case SATA_PKT_PORT_ERROR:
/*
* We have no device data.
*/
scsipkt->pkt_reason = CMD_INCOMPLETE;
scsipkt->pkt_state &= ~(STATE_GOT_BUS |
STATE_GOT_TARGET | STATE_SENT_CMD |
STATE_GOT_STATUS);
sense->es_key = KEY_HARDWARE_ERROR;
break;
case SATA_PKT_TIMEOUT:
scsipkt->pkt_reason = CMD_TIMEOUT;
scsipkt->pkt_statistics |=
STAT_TIMEOUT | STAT_DEV_RESET;
/*
* Need to check if HARDWARE_ERROR/
* TIMEOUT_ON_LOGICAL_UNIT 4/3E/2 would be more
* appropriate.
*/
break;
case SATA_PKT_ABORTED:
scsipkt->pkt_reason = CMD_ABORTED;
scsipkt->pkt_statistics |= STAT_ABORTED;
/* Should we set key COMMAND_ABPRTED? */
break;
case SATA_PKT_RESET:
scsipkt->pkt_reason = CMD_RESET;
scsipkt->pkt_statistics |= STAT_DEV_RESET;
/*
* May be we should set Unit Attention /
* Reset. Perhaps the same should be
* returned for disks....
*/
sense->es_key = KEY_UNIT_ATTENTION;
sense->es_add_code = SD_SCSI_ASC_RESET;
break;
default:
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_txlt_atapi_completion: "
"invalid packet completion reason"));
scsipkt->pkt_reason = CMD_TRAN_ERR;
scsipkt->pkt_state &= ~(STATE_GOT_BUS |
STATE_GOT_TARGET | STATE_SENT_CMD |
STATE_GOT_STATUS);
break;
}
}
}
SATAATAPITRACE(spx, 0);
if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
scsipkt->pkt_comp != NULL) {
/* scsi callback required */
(*scsipkt->pkt_comp)(scsipkt);
}
}
/*
* Set up error retrieval sata command for ATAPI Packet Command error data
* recovery.
*
* Returns SATA_SUCCESS when data buffer is allocated and packet set-up,
* returns SATA_FAILURE otherwise.
*/
static int
sata_atapi_err_ret_cmd_setup(sata_pkt_txlate_t *spx, sata_drive_info_t *sdinfo)
{
sata_pkt_t *spkt = spx->txlt_sata_pkt;
sata_cmd_t *scmd;
struct buf *bp;
/*
* Allocate dma-able buffer error data.
* Buffer allocation will take care of buffer alignment and other DMA
* attributes.
*/
bp = sata_alloc_local_buffer(spx, SATA_ATAPI_MIN_RQSENSE_LEN);
if (bp == NULL) {
SATADBG1(SATA_DBG_ATAPI, spx->txlt_sata_hba_inst,
"sata_get_err_retrieval_pkt: "
"cannot allocate buffer for error data", NULL);
return (SATA_FAILURE);
}
bp_mapin(bp); /* make data buffer accessible */
/* Operation modes are up to the caller */
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback - may be changed by the caller */
spkt->satapkt_comp = NULL;
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
sata_atapi_packet_cmd_setup(scmd, sdinfo);
/*
* Set-up acdb. Request Sense CDB (packet command content) is
* not in DMA-able buffer. Its handling is HBA-specific (how
* it is transfered into packet FIS).
*/
scmd->satacmd_acdb_len = sdinfo->satadrv_atapi_cdb_len;
bcopy(sata_rqsense_cdb, scmd->satacmd_acdb, SATA_ATAPI_RQSENSE_CDB_LEN);
/* Following zeroing of pad bytes may not be necessary */
bzero(&scmd->satacmd_acdb[SATA_ATAPI_RQSENSE_CDB_LEN],
sdinfo->satadrv_atapi_cdb_len - SATA_ATAPI_RQSENSE_CDB_LEN);
/*
* Set-up pointer to the buffer handle, so HBA can sync buffer
* before accessing it. Handle is in usual place in translate struct.
*/
scmd->satacmd_err_ret_buf_handle = &spx->txlt_buf_dma_handle;
/*
* Preset request sense data to NO SENSE.
* Here it is redundant, only for a symetry with scsi-originated
* packets. It should not be used for anything but debugging.
*/
bzero(scmd->satacmd_rqsense, SATA_ATAPI_RQSENSE_LEN);
sata_fixed_sense_data_preset(
(struct scsi_extended_sense *)scmd->satacmd_rqsense);
ASSERT(scmd->satacmd_num_dma_cookies != 0);
ASSERT(scmd->satacmd_dma_cookie_list != NULL);
return (SATA_SUCCESS);
}
/*
* Set-up ATAPI packet command.
* Data transfer direction has to be set-up in sata_cmd structure prior to
* calling this function.
*
* Returns void
*/
static void
sata_atapi_packet_cmd_setup(sata_cmd_t *scmd, sata_drive_info_t *sdinfo)
{
scmd->satacmd_addr_type = 0; /* N/A */
scmd->satacmd_sec_count_lsb = 0; /* no tag */
scmd->satacmd_lba_low_lsb = 0; /* N/A */
scmd->satacmd_lba_mid_lsb = (uint8_t)SATA_ATAPI_MAX_BYTES_PER_DRQ;
scmd->satacmd_lba_high_lsb =
(uint8_t)(SATA_ATAPI_MAX_BYTES_PER_DRQ >> 8);
scmd->satacmd_cmd_reg = SATAC_PACKET; /* Command */
/*
* We want all data to be transfered via DMA.
* But specify it only if drive supports DMA and DMA mode is
* selected - some drives are sensitive about it.
* Hopefully it wil work for all drives....
*/
if (sdinfo->satadrv_settings & SATA_DEV_DMA)
scmd->satacmd_features_reg = SATA_ATAPI_F_DMA;
/*
* Features register requires special care for devices that use
* Serial ATA bridge - they need an explicit specification of
* the data transfer direction for Packet DMA commands.
* Setting this bit is harmless if DMA is not used.
*
* Many drives do not implement word 80, specifying what ATA/ATAPI
* spec they follow.
* We are arbitrarily following the latest SerialATA 2.6 spec,
* which uses ATA/ATAPI 6 specification for Identify Data, unless
* ATA/ATAPI-7 support is explicitly indicated.
*/
if (sdinfo->satadrv_id.ai_majorversion != 0 &&
sdinfo->satadrv_id.ai_majorversion != 0xffff &&
(sdinfo->satadrv_id.ai_majorversion & SATA_MAJVER_7) != 0) {
/*
* Specification of major version is valid and version 7
* is supported. It does automatically imply that all
* spec features are supported. For now, we assume that
* DMADIR setting is valid. ATA/ATAPI7 spec is incomplete.
*/
if ((sdinfo->satadrv_id.ai_dirdma &
SATA_ATAPI_ID_DMADIR_REQ) != 0) {
if (scmd->satacmd_flags.sata_data_direction ==
SATA_DIR_READ)
scmd->satacmd_features_reg |=
SATA_ATAPI_F_DATA_DIR_READ;
}
}
}
#ifdef SATA_DEBUG
/* Display 18 bytes of Inquiry data */
static void
sata_show_inqry_data(uint8_t *buf)
{
struct scsi_inquiry *inq = (struct scsi_inquiry *)buf;
uint8_t *p;
cmn_err(CE_NOTE, "Inquiry data:");
cmn_err(CE_NOTE, "device type %x", inq->inq_dtype);
cmn_err(CE_NOTE, "removable media %x", inq->inq_rmb);
cmn_err(CE_NOTE, "version %x", inq->inq_ansi);
cmn_err(CE_NOTE, "ATAPI transport version %d",
SATA_ATAPI_TRANS_VERSION(inq));
cmn_err(CE_NOTE, "response data format %d, aenc %d",
inq->inq_rdf, inq->inq_aenc);
cmn_err(CE_NOTE, " additional length %d", inq->inq_len);
cmn_err(CE_NOTE, "tpgs %d", inq->inq_tpgs);
p = (uint8_t *)inq->inq_vid;
cmn_err(CE_NOTE, "vendor id (binary): %02x %02x %02x %02x "
"%02x %02x %02x %02x",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
p = (uint8_t *)inq->inq_vid;
cmn_err(CE_NOTE, "vendor id: %c %c %c %c %c %c %c %c",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
p = (uint8_t *)inq->inq_pid;
cmn_err(CE_NOTE, "product id (binary): %02x %02x %02x %02x "
"%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
p = (uint8_t *)inq->inq_pid;
cmn_err(CE_NOTE, "product id: %c %c %c %c %c %c %c %c "
"%c %c %c %c %c %c %c %c",
p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
p = (uint8_t *)inq->inq_revision;
cmn_err(CE_NOTE, "revision (binary): %02x %02x %02x %02x",
p[0], p[1], p[2], p[3]);
p = (uint8_t *)inq->inq_revision;
cmn_err(CE_NOTE, "revision: %c %c %c %c",
p[0], p[1], p[2], p[3]);
}
static void
sata_save_atapi_trace(sata_pkt_txlate_t *spx, int count)
{
struct scsi_pkt *scsi_pkt = spx->txlt_scsi_pkt;
if (scsi_pkt == NULL)
return;
if (count != 0) {
/* saving cdb */
bzero(sata_atapi_trace[sata_atapi_trace_index].acdb,
SATA_ATAPI_MAX_CDB_LEN);
bcopy(scsi_pkt->pkt_cdbp,
sata_atapi_trace[sata_atapi_trace_index].acdb, count);
} else {
bcopy(&((struct scsi_arq_status *)scsi_pkt->pkt_scbp)->
sts_sensedata,
sata_atapi_trace[sata_atapi_trace_index].arqs,
SATA_ATAPI_MIN_RQSENSE_LEN);
sata_atapi_trace[sata_atapi_trace_index].scsi_pkt_reason =
scsi_pkt->pkt_reason;
sata_atapi_trace[sata_atapi_trace_index].sata_pkt_reason =
spx->txlt_sata_pkt->satapkt_reason;
if (++sata_atapi_trace_index >= 64)
sata_atapi_trace_index = 0;
}
}
#endif
/*
* Fetch inquiry data from ATAPI device
* Returns SATA_SUCCESS if operation was successfull, SATA_FAILURE otherwise.
*
* Note:
* inqb pointer does not point to a DMA-able buffer. It is a local buffer
* where the caller expects to see the inquiry data.
*
*/
static int
sata_get_atapi_inquiry_data(sata_hba_inst_t *sata_hba,
sata_address_t *saddr, struct scsi_inquiry *inq)
{
sata_pkt_txlate_t *spx;
sata_pkt_t *spkt;
struct buf *bp;
sata_drive_info_t *sdinfo;
sata_cmd_t *scmd;
int rval;
uint8_t *rqsp;
#ifdef SATA_DEBUG
char msg_buf[MAXPATHLEN];
#endif
ASSERT(sata_hba != NULL);
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, NULL);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (SATA_FAILURE);
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = *saddr;
/* scsi_inquiry size buffer */
bp = sata_alloc_local_buffer(spx, sizeof (struct scsi_inquiry));
if (bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
SATA_LOG_D((sata_hba, CE_WARN,
"sata_get_atapi_inquiry_data: "
"cannot allocate data buffer"));
return (SATA_FAILURE);
}
bp_mapin(bp); /* make data buffer accessible */
scmd = &spkt->satapkt_cmd;
ASSERT(scmd->satacmd_num_dma_cookies != 0);
ASSERT(scmd->satacmd_dma_cookie_list != NULL);
/* Use synchronous mode */
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
spkt->satapkt_comp = NULL;
spkt->satapkt_time = sata_default_pkt_time;
/* Issue inquiry command - 6 bytes cdb, data transfer, read */
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
sdinfo = sata_get_device_info(sata_hba,
&spx->txlt_sata_pkt->satapkt_device);
if (sdinfo == NULL) {
/* we have to be carefull about the disapearing device */
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
rval = SATA_FAILURE;
goto cleanup;
}
sata_atapi_packet_cmd_setup(scmd, sdinfo);
/*
* Set-up acdb. This works for atapi transport version 2 and later.
*/
scmd->satacmd_acdb_len = sdinfo->satadrv_atapi_cdb_len;
bzero(scmd->satacmd_acdb, SATA_ATAPI_MAX_CDB_LEN);
scmd->satacmd_acdb[0] = 0x12; /* Inquiry */
scmd->satacmd_acdb[1] = 0x00;
scmd->satacmd_acdb[2] = 0x00;
scmd->satacmd_acdb[3] = 0x00;
scmd->satacmd_acdb[4] = sizeof (struct scsi_inquiry);
scmd->satacmd_acdb[5] = 0x00;
sata_fixed_sense_data_preset(
(struct scsi_extended_sense *)scmd->satacmd_rqsense);
/* Transfer command to HBA */
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
SATADBG1(SATA_DBG_ATAPI, sata_hba,
"sata_get_atapi_inquiry_data: "
"Packet not accepted for execution - ret: %02x", rval);
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
rval = SATA_FAILURE;
goto cleanup;
}
mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
if (spkt->satapkt_reason == SATA_PKT_COMPLETED) {
SATADBG1(SATA_DBG_ATAPI, sata_hba,
"sata_get_atapi_inquiry_data: "
"Packet completed successfully - ret: %02x", rval);
if (spx->txlt_buf_dma_handle != NULL) {
/*
* Sync buffer. Handle is in usual place in translate
* struct.
*/
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORCPU);
ASSERT(rval == DDI_SUCCESS);
}
/*
* Normal completion - copy data into caller's buffer
*/
bcopy(bp->b_un.b_addr, (uint8_t *)inq,
sizeof (struct scsi_inquiry));
#ifdef SATA_DEBUG
if (sata_debug_flags & SATA_DBG_ATAPI) {
sata_show_inqry_data((uint8_t *)inq);
}
#endif
rval = SATA_SUCCESS;
} else {
/*
* Something went wrong - analyze return - check rqsense data
*/
rval = SATA_FAILURE;
if (spkt->satapkt_reason == SATA_PKT_DEV_ERROR) {
/*
* ARQ data hopefull show something other than NO SENSE
*/
rqsp = scmd->satacmd_rqsense;
#ifdef SATA_DEBUG
if (sata_debug_flags & SATA_DBG_ATAPI) {
msg_buf[0] = '\0';
(void) snprintf(msg_buf, MAXPATHLEN,
"ATAPI packet completion reason: %02x\n"
"RQSENSE: %02x %02x %02x %02x %02x %02x\n"
" %02x %02x %02x %02x %02x %02x\n"
" %02x %02x %02x %02x %02x %02x",
spkt->satapkt_reason,
rqsp[0], rqsp[1], rqsp[2], rqsp[3],
rqsp[4], rqsp[5], rqsp[6], rqsp[7],
rqsp[8], rqsp[9], rqsp[10], rqsp[11],
rqsp[12], rqsp[13], rqsp[14], rqsp[15],
rqsp[16], rqsp[17]);
sata_log(spx->txlt_sata_hba_inst, CE_WARN,
"%s", msg_buf);
}
#endif
} else {
switch (spkt->satapkt_reason) {
case SATA_PKT_PORT_ERROR:
SATADBG1(SATA_DBG_ATAPI, sata_hba,
"sata_get_atapi_inquiry_data: "
"packet reason: port error", NULL);
break;
case SATA_PKT_TIMEOUT:
SATADBG1(SATA_DBG_ATAPI, sata_hba,
"sata_get_atapi_inquiry_data: "
"packet reason: timeout", NULL);
break;
case SATA_PKT_ABORTED:
SATADBG1(SATA_DBG_ATAPI, sata_hba,
"sata_get_atapi_inquiry_data: "
"packet reason: aborted", NULL);
break;
case SATA_PKT_RESET:
SATADBG1(SATA_DBG_ATAPI, sata_hba,
"sata_get_atapi_inquiry_data: "
"packet reason: reset\n", NULL);
break;
default:
SATADBG1(SATA_DBG_ATAPI, sata_hba,
"sata_get_atapi_inquiry_data: "
"invalid packet reason: %02x\n",
spkt->satapkt_reason);
break;
}
}
}
cleanup:
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
#if 0
#ifdef SATA_DEBUG
/*
* Test ATAPI packet command.
* Single threaded test: send packet command in synch mode, process completion
*
*/
static void
sata_test_atapi_packet_command(sata_hba_inst_t *sata_hba_inst, int cport)
{
sata_pkt_txlate_t *spx;
sata_pkt_t *spkt;
struct buf *bp;
sata_device_t sata_device;
sata_drive_info_t *sdinfo;
sata_cmd_t *scmd;
int rval;
uint8_t *rqsp;
ASSERT(sata_hba_inst != NULL);
sata_device.satadev_addr.cport = cport;
sata_device.satadev_addr.pmport = 0;
sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
sata_device.satadev_rev = SATA_DEVICE_REV;
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if (sdinfo == NULL) {
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"no device info for cport %d",
sata_device.satadev_addr.cport);
return;
}
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, NULL);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return;
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sata_device.satadev_addr;
/* 1024k buffer */
bp = sata_alloc_local_buffer(spx, 1024);
if (bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"cannot allocate data buffer");
return;
}
bp_mapin(bp); /* make data buffer accessible */
scmd = &spkt->satapkt_cmd;
ASSERT(scmd->satacmd_num_dma_cookies != 0);
ASSERT(scmd->satacmd_dma_cookie_list != NULL);
/* Use synchronous mode */
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback - may be changed by the caller */
spkt->satapkt_comp = NULL;
spkt->satapkt_time = sata_default_pkt_time;
/* Issue inquiry command - 6 bytes cdb, data transfer, read */
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
sata_atapi_packet_cmd_setup(scmd, sdinfo);
/* Set-up acdb. */
scmd->satacmd_acdb_len = sdinfo->satadrv_atapi_cdb_len;
bzero(scmd->satacmd_acdb, SATA_ATAPI_MAX_CDB_LEN);
scmd->satacmd_acdb[0] = 0x12; /* Inquiry */
scmd->satacmd_acdb[1] = 0x00;
scmd->satacmd_acdb[2] = 0x00;
scmd->satacmd_acdb[3] = 0x00;
scmd->satacmd_acdb[4] = sizeof (struct scsi_inquiry);
scmd->satacmd_acdb[5] = 0x00;
sata_fixed_sense_data_preset(
(struct scsi_extended_sense *)scmd->satacmd_rqsense);
/* Transfer command to HBA */
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if (sata_hba_start(spx, &rval) != 0) {
/* Pkt not accepted for execution */
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"Packet not accepted for execution - ret: %02x", rval);
mutex_exit(
&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
goto cleanup;
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if (spx->txlt_buf_dma_handle != NULL) {
/*
* Sync buffer. Handle is in usual place in translate struct.
*/
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORCPU);
ASSERT(rval == DDI_SUCCESS);
}
if (spkt->satapkt_reason == SATA_PKT_COMPLETED) {
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"Packet completed successfully");
/*
* Normal completion - show inquiry data
*/
sata_show_inqry_data((uint8_t *)bp->b_un.b_addr);
} else {
/*
* Something went wrong - analyze return - check rqsense data
*/
if (spkt->satapkt_reason == SATA_PKT_DEV_ERROR) {
/*
* ARQ data hopefull show something other than NO SENSE
*/
rqsp = scmd->satacmd_rqsense;
sata_log(spx->txlt_sata_hba_inst, CE_WARN,
"ATAPI packet completion reason: %02x\n"
"RQSENSE: %02x %02x %02x %02x %02x %02x "
" %02x %02x %02x %02x %02x %02x "
" %02x %02x %02x %02x %02x %02x\n",
spkt->satapkt_reason,
rqsp[0], rqsp[1], rqsp[2], rqsp[3],
rqsp[4], rqsp[5], rqsp[6], rqsp[7],
rqsp[8], rqsp[9], rqsp[10], rqsp[11],
rqsp[12], rqsp[13], rqsp[14], rqsp[15],
rqsp[16], rqsp[17]);
} else {
switch (spkt->satapkt_reason) {
case SATA_PKT_PORT_ERROR:
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"packet reason: port error\n");
break;
case SATA_PKT_TIMEOUT:
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"packet reason: timeout\n");
break;
case SATA_PKT_ABORTED:
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"packet reason: aborted\n");
break;
case SATA_PKT_RESET:
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"packet reason: reset\n");
break;
default:
sata_log(sata_hba_inst, CE_WARN,
"sata_test_atapi_packet_command: "
"invalid packet reason: %02x\n",
spkt->satapkt_reason);
break;
}
}
}
cleanup:
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
}
#endif /* SATA_DEBUG */
#endif /* 1 */
/* ************************** LOCAL HELPER FUNCTIONS *********************** */
/*
* Validate sata_tran info
* SATA_FAILURE returns if structure is inconsistent or structure revision
* does not match one used by the framework.
*
* Returns SATA_SUCCESS if sata_hba_tran has matching revision and contains
* required function pointers.
* Returns SATA_FAILURE otherwise.
*/
static int
sata_validate_sata_hba_tran(dev_info_t *dip, sata_hba_tran_t *sata_tran)
{
/*
* SATA_TRAN_HBA_REV is the current (highest) revision number
* of the SATA interface.
*/
if (sata_tran->sata_tran_hba_rev > SATA_TRAN_HBA_REV) {
sata_log(NULL, CE_WARN,
"sata: invalid sata_hba_tran version %d for driver %s",
sata_tran->sata_tran_hba_rev, ddi_driver_name(dip));
return (SATA_FAILURE);
}
if (dip != sata_tran->sata_tran_hba_dip) {
SATA_LOG_D((NULL, CE_WARN,
"sata: inconsistent sata_tran_hba_dip "
"%p / %p", sata_tran->sata_tran_hba_dip, dip));
return (SATA_FAILURE);
}
if (sata_tran->sata_tran_probe_port == NULL ||
sata_tran->sata_tran_start == NULL ||
sata_tran->sata_tran_abort == NULL ||
sata_tran->sata_tran_reset_dport == NULL ||
sata_tran->sata_tran_hotplug_ops == NULL ||
sata_tran->sata_tran_hotplug_ops->sata_tran_port_activate == NULL ||
sata_tran->sata_tran_hotplug_ops->sata_tran_port_deactivate ==
NULL) {
SATA_LOG_D((NULL, CE_WARN, "sata: sata_hba_tran missing "
"required functions"));
}
return (SATA_SUCCESS);
}
/*
* Remove HBA instance from sata_hba_list.
*/
static void
sata_remove_hba_instance(dev_info_t *dip)
{
sata_hba_inst_t *sata_hba_inst;
mutex_enter(&sata_mutex);
for (sata_hba_inst = sata_hba_list;
sata_hba_inst != (struct sata_hba_inst *)NULL;
sata_hba_inst = sata_hba_inst->satahba_next) {
if (sata_hba_inst->satahba_dip == dip)
break;
}
if (sata_hba_inst == (struct sata_hba_inst *)NULL) {
#ifdef SATA_DEBUG
cmn_err(CE_WARN, "sata_remove_hba_instance: "
"unknown HBA instance\n");
#endif
ASSERT(FALSE);
}
if (sata_hba_inst == sata_hba_list) {
sata_hba_list = sata_hba_inst->satahba_next;
if (sata_hba_list) {
sata_hba_list->satahba_prev =
(struct sata_hba_inst *)NULL;
}
if (sata_hba_inst == sata_hba_list_tail) {
sata_hba_list_tail = NULL;
}
} else if (sata_hba_inst == sata_hba_list_tail) {
sata_hba_list_tail = sata_hba_inst->satahba_prev;
if (sata_hba_list_tail) {
sata_hba_list_tail->satahba_next =
(struct sata_hba_inst *)NULL;
}
} else {
sata_hba_inst->satahba_prev->satahba_next =
sata_hba_inst->satahba_next;
sata_hba_inst->satahba_next->satahba_prev =
sata_hba_inst->satahba_prev;
}
mutex_exit(&sata_mutex);
}
/*
* Probe all SATA ports of the specified HBA instance.
* The assumption is that there are no target and attachment point minor nodes
* created by the boot subsystems, so we do not need to prune device tree.
*
* This function is called only from sata_hba_attach(). It does not have to
* be protected by controller mutex, because the hba_attached flag is not set
* yet and no one would be touching this HBA instance other than this thread.
* Determines if port is active and what type of the device is attached
* (if any). Allocates necessary structures for each port.
*
* An AP (Attachement Point) node is created for each SATA device port even
* when there is no device attached.
*/
static void
sata_probe_ports(sata_hba_inst_t *sata_hba_inst)
{
dev_info_t *dip = SATA_DIP(sata_hba_inst);
int ncport, npmport;
sata_cport_info_t *cportinfo;
sata_drive_info_t *drive;
sata_pmult_info_t *pminfo;
sata_pmport_info_t *pmportinfo;
sata_device_t sata_device;
int rval;
dev_t minor_number;
char name[16];
clock_t start_time, cur_time;
/*
* Probe controller ports first, to find port status and
* any port multiplier attached.
*/
for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst); ncport++) {
/* allocate cport structure */
cportinfo = kmem_zalloc(sizeof (sata_cport_info_t), KM_SLEEP);
ASSERT(cportinfo != NULL);
mutex_init(&cportinfo->cport_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_enter(&cportinfo->cport_mutex);
cportinfo->cport_addr.cport = ncport;
cportinfo->cport_addr.pmport = 0;
cportinfo->cport_addr.qual = SATA_ADDR_CPORT;
cportinfo->cport_state &= ~SATA_PORT_STATE_CLEAR_MASK;
cportinfo->cport_state |= SATA_STATE_PROBING;
SATA_CPORT_INFO(sata_hba_inst, ncport) = cportinfo;
/*
* Regardless if a port is usable or not, create
* an attachment point
*/
mutex_exit(&cportinfo->cport_mutex);
minor_number =
SATA_MAKE_AP_MINOR(ddi_get_instance(dip), ncport, 0, 0);
(void) sprintf(name, "%d", ncport);
if (ddi_create_minor_node(dip, name, S_IFCHR,
minor_number, DDI_NT_SATA_ATTACHMENT_POINT, 0) !=
DDI_SUCCESS) {
sata_log(sata_hba_inst, CE_WARN, "sata_hba_attach: "
"cannot create SATA attachment point for port %d",
ncport);
}
/* Probe port */
start_time = ddi_get_lbolt();
reprobe_cport:
sata_device.satadev_addr.cport = ncport;
sata_device.satadev_addr.pmport = 0;
sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
sata_device.satadev_rev = SATA_DEVICE_REV;
rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(dip, &sata_device);
mutex_enter(&cportinfo->cport_mutex);
sata_update_port_scr(&cportinfo->cport_scr, &sata_device);
if (rval != SATA_SUCCESS) {
/* Something went wrong? Fail the port */
cportinfo->cport_state = SATA_PSTATE_FAILED;
mutex_exit(&cportinfo->cport_mutex);
continue;
}
cportinfo->cport_state &= ~SATA_STATE_PROBING;
cportinfo->cport_state |= SATA_STATE_PROBED;
cportinfo->cport_dev_type = sata_device.satadev_type;
cportinfo->cport_state |= SATA_STATE_READY;
if (cportinfo->cport_dev_type == SATA_DTYPE_NONE) {
mutex_exit(&cportinfo->cport_mutex);
continue;
}
if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
/*
* There is some device attached.
* Allocate device info structure
*/
if (SATA_CPORTINFO_DRV_INFO(cportinfo) == NULL) {
mutex_exit(&cportinfo->cport_mutex);
SATA_CPORTINFO_DRV_INFO(cportinfo) =
kmem_zalloc(sizeof (sata_drive_info_t),
KM_SLEEP);
mutex_enter(&cportinfo->cport_mutex);
}
drive = SATA_CPORTINFO_DRV_INFO(cportinfo);
drive->satadrv_addr = cportinfo->cport_addr;
drive->satadrv_addr.qual = SATA_ADDR_DCPORT;
drive->satadrv_type = cportinfo->cport_dev_type;
drive->satadrv_state = SATA_STATE_UNKNOWN;
mutex_exit(&cportinfo->cport_mutex);
if (sata_add_device(dip, sata_hba_inst, ncport, 0) !=
SATA_SUCCESS) {
/*
* Plugged device was not correctly identified.
* Retry, within a SATA_DEV_IDENTIFY_TIMEOUT
*/
cur_time = ddi_get_lbolt();
if ((cur_time - start_time) <
drv_usectohz(SATA_DEV_IDENTIFY_TIMEOUT)) {
/* sleep for a while */
delay(drv_usectohz(
SATA_DEV_RETRY_DLY));
goto reprobe_cport;
}
}
} else {
mutex_exit(&cportinfo->cport_mutex);
ASSERT(cportinfo->cport_dev_type == SATA_DTYPE_PMULT);
pminfo = kmem_zalloc(sizeof (sata_pmult_info_t),
KM_SLEEP);
mutex_enter(&cportinfo->cport_mutex);
ASSERT(pminfo != NULL);
SATA_CPORTINFO_PMULT_INFO(cportinfo) = pminfo;
pminfo->pmult_addr.cport = cportinfo->cport_addr.cport;
pminfo->pmult_addr.pmport = SATA_PMULT_HOSTPORT;
pminfo->pmult_addr.qual = SATA_ADDR_PMPORT;
pminfo->pmult_num_dev_ports =
sata_device.satadev_add_info;
mutex_init(&pminfo->pmult_mutex, NULL, MUTEX_DRIVER,
NULL);
pminfo->pmult_state = SATA_STATE_PROBING;
mutex_exit(&cportinfo->cport_mutex);
/* Probe Port Multiplier ports */
for (npmport = 0;
npmport < pminfo->pmult_num_dev_ports;
npmport++) {
pmportinfo = kmem_zalloc(
sizeof (sata_pmport_info_t), KM_SLEEP);
mutex_enter(&cportinfo->cport_mutex);
ASSERT(pmportinfo != NULL);
pmportinfo->pmport_addr.cport = ncport;
pmportinfo->pmport_addr.pmport = npmport;
pmportinfo->pmport_addr.qual =
SATA_ADDR_PMPORT;
pminfo->pmult_dev_port[npmport] = pmportinfo;
mutex_init(&pmportinfo->pmport_mutex, NULL,
MUTEX_DRIVER, NULL);
mutex_exit(&cportinfo->cport_mutex);
/* Create an attachment point */
minor_number = SATA_MAKE_AP_MINOR(
ddi_get_instance(dip), ncport, npmport, 1);
(void) sprintf(name, "%d.%d", ncport, npmport);
if (ddi_create_minor_node(dip, name, S_IFCHR,
minor_number, DDI_NT_SATA_ATTACHMENT_POINT,
0) != DDI_SUCCESS) {
sata_log(sata_hba_inst, CE_WARN,
"sata_hba_attach: "
"cannot create SATA attachment "
"point for port %d pmult port %d",
ncport, npmport);
}
start_time = ddi_get_lbolt();
reprobe_pmport:
sata_device.satadev_addr.pmport = npmport;
sata_device.satadev_addr.qual =
SATA_ADDR_PMPORT;
rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(dip, &sata_device);
mutex_enter(&cportinfo->cport_mutex);
/* sata_update_port_info() */
sata_update_port_scr(&pmportinfo->pmport_scr,
&sata_device);
if (rval != SATA_SUCCESS) {
pmportinfo->pmport_state =
SATA_PSTATE_FAILED;
mutex_exit(&cportinfo->cport_mutex);
continue;
}
pmportinfo->pmport_state &=
~SATA_STATE_PROBING;
pmportinfo->pmport_state |= SATA_STATE_PROBED;
pmportinfo->pmport_dev_type =
sata_device.satadev_type;
pmportinfo->pmport_state |= SATA_STATE_READY;
if (pmportinfo->pmport_dev_type ==
SATA_DTYPE_NONE) {
mutex_exit(&cportinfo->cport_mutex);
continue;
}
/* Port multipliers cannot be chained */
ASSERT(pmportinfo->pmport_dev_type !=
SATA_DTYPE_PMULT);
/*
* There is something attached to Port
* Multiplier device port
* Allocate device info structure
*/
if (pmportinfo->pmport_sata_drive == NULL) {
mutex_exit(&cportinfo->cport_mutex);
pmportinfo->pmport_sata_drive =
kmem_zalloc(
sizeof (sata_drive_info_t),
KM_SLEEP);
mutex_enter(&cportinfo->cport_mutex);
}
drive = pmportinfo->pmport_sata_drive;
drive->satadrv_addr.cport =
pmportinfo->pmport_addr.cport;
drive->satadrv_addr.pmport = npmport;
drive->satadrv_addr.qual = SATA_ADDR_DPMPORT;
drive->satadrv_type = pmportinfo->
pmport_dev_type;
drive->satadrv_state = SATA_STATE_UNKNOWN;
mutex_exit(&cportinfo->cport_mutex);
if (sata_add_device(dip, sata_hba_inst, ncport,
npmport) != SATA_SUCCESS) {
/*
* Plugged device was not correctly
* identified. Retry, within the
* SATA_DEV_IDENTIFY_TIMEOUT
*/
cur_time = ddi_get_lbolt();
if ((cur_time - start_time) <
drv_usectohz(
SATA_DEV_IDENTIFY_TIMEOUT)) {
/* sleep for a while */
delay(drv_usectohz(
SATA_DEV_RETRY_DLY));
goto reprobe_pmport;
}
}
}
pmportinfo->pmport_state =
SATA_STATE_PROBED | SATA_STATE_READY;
}
}
}
/*
* Add SATA device for specified HBA instance & port (SCSI target
* device nodes).
* This function is called (indirectly) only from sata_hba_attach().
* A target node is created when there is a supported type device attached,
* but may be removed if it cannot be put online.
*
* This function cannot be called from an interrupt context.
*
* ONLY DISK TARGET NODES ARE CREATED NOW
*
* Returns SATA_SUCCESS when port/device was fully processed, SATA_FAILURE when
* device identification failed - adding a device could be retried.
*
*/
static int
sata_add_device(dev_info_t *pdip, sata_hba_inst_t *sata_hba_inst, int cport,
int pmport)
{
sata_cport_info_t *cportinfo;
sata_pmult_info_t *pminfo;
sata_pmport_info_t *pmportinfo;
dev_info_t *cdip; /* child dip */
sata_device_t sata_device;
int rval;
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
ASSERT(cportinfo->cport_dev_type != SATA_DTYPE_NONE);
mutex_enter(&cportinfo->cport_mutex);
/*
* Some device is attached to a controller port.
* We rely on controllers distinquishing between no-device,
* attached port multiplier and other kind of attached device.
* We need to get Identify Device data and determine
* positively the dev type before trying to attach
* the target driver.
*/
sata_device.satadev_rev = SATA_DEVICE_REV;
if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
/*
* Not port multiplier.
*/
sata_device.satadev_addr = cportinfo->cport_addr;
sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
mutex_exit(&cportinfo->cport_mutex);
rval = sata_probe_device(sata_hba_inst, &sata_device);
if (rval != SATA_SUCCESS ||
sata_device.satadev_type == SATA_DTYPE_UNKNOWN)
return (SATA_FAILURE);
mutex_enter(&cportinfo->cport_mutex);
sata_show_drive_info(sata_hba_inst,
SATA_CPORTINFO_DRV_INFO(cportinfo));
if ((sata_device.satadev_type & SATA_VALID_DEV_TYPE) == 0) {
/*
* Could not determine device type or
* a device is not supported.
* Degrade this device to unknown.
*/
cportinfo->cport_dev_type = SATA_DTYPE_UNKNOWN;
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
cportinfo->cport_dev_type = sata_device.satadev_type;
cportinfo->cport_tgtnode_clean = B_TRUE;
mutex_exit(&cportinfo->cport_mutex);
/*
* Initialize device to the desired state. Even if it
* fails, the device will still attach but syslog
* will show the warning.
*/
if (sata_initialize_device(sata_hba_inst,
SATA_CPORTINFO_DRV_INFO(cportinfo)) != SATA_SUCCESS) {
/* Retry */
rval = sata_initialize_device(sata_hba_inst,
SATA_CPORTINFO_DRV_INFO(cportinfo));
if (rval == SATA_RETRY)
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d - "
"default device features could not be set."
" Device may not operate as expected.",
cportinfo->cport_addr.cport);
}
cdip = sata_create_target_node(pdip, sata_hba_inst,
&sata_device.satadev_addr);
mutex_enter(&cportinfo->cport_mutex);
if (cdip == NULL) {
/*
* Attaching target node failed.
* We retain sata_drive_info structure...
*/
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
(SATA_CPORTINFO_DRV_INFO(cportinfo))->
satadrv_state = SATA_STATE_READY;
} else {
/* This must be Port Multiplier type */
if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_add_device: "
"unrecognized dev type %x",
cportinfo->cport_dev_type));
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
pminfo = SATA_CPORTINFO_PMULT_INFO(cportinfo);
pmportinfo = pminfo->pmult_dev_port[pmport];
sata_device.satadev_addr = pmportinfo->pmport_addr;
sata_device.satadev_addr.qual = SATA_ADDR_DPMPORT;
mutex_exit(&cportinfo->cport_mutex);
rval = sata_probe_device(sata_hba_inst, &sata_device);
if (rval != SATA_SUCCESS ||
sata_device.satadev_type == SATA_DTYPE_UNKNOWN) {
return (SATA_FAILURE);
}
mutex_enter(&cportinfo->cport_mutex);
sata_show_drive_info(sata_hba_inst,
SATA_CPORTINFO_DRV_INFO(cportinfo));
if ((sata_device.satadev_type & SATA_VALID_DEV_TYPE) == 0) {
/*
* Could not determine device type.
* Degrade this device to unknown.
*/
pmportinfo->pmport_dev_type = SATA_DTYPE_UNKNOWN;
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
pmportinfo->pmport_dev_type = sata_device.satadev_type;
pmportinfo->pmport_tgtnode_clean = B_TRUE;
mutex_exit(&cportinfo->cport_mutex);
/*
* Initialize device to the desired state.
* Even if it fails, the device will still
* attach but syslog will show the warning.
*/
if (sata_initialize_device(sata_hba_inst,
pmportinfo->pmport_sata_drive) != SATA_SUCCESS) {
/* Retry */
rval = sata_initialize_device(sata_hba_inst,
pmportinfo->pmport_sata_drive);
if (rval == SATA_RETRY)
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d pmport %d - "
"default device features could not be set."
" Device may not operate as expected.",
pmportinfo->pmport_addr.cport,
pmportinfo->pmport_addr.pmport);
}
cdip = sata_create_target_node(pdip, sata_hba_inst,
&sata_device.satadev_addr);
mutex_enter(&cportinfo->cport_mutex);
if (cdip == NULL) {
/*
* Attaching target node failed.
* We retain sata_drive_info structure...
*/
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
pmportinfo->pmport_sata_drive->satadrv_state |=
SATA_STATE_READY;
}
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
/*
* Create scsi target node for attached device, create node properties and
* attach the node.
* The node could be removed if the device onlining fails.
*
* A dev_info_t pointer is returned if operation is successful, NULL is
* returned otherwise.
*
* No port multiplier support.
*/
static dev_info_t *
sata_create_target_node(dev_info_t *dip, sata_hba_inst_t *sata_hba_inst,
sata_address_t *sata_addr)
{
dev_info_t *cdip = NULL;
int rval;
char *nname = NULL;
char **compatible = NULL;
int ncompatible;
struct scsi_inquiry inq;
sata_device_t sata_device;
sata_drive_info_t *sdinfo;
int target;
int i;
sata_device.satadev_rev = SATA_DEVICE_REV;
sata_device.satadev_addr = *sata_addr;
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, sata_addr->cport)));
sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
target = SATA_TO_SCSI_TARGET(sata_addr->cport,
sata_addr->pmport, sata_addr->qual);
if (sdinfo == NULL) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_addr->cport)));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_create_target_node: no sdinfo for target %x",
target));
return (NULL);
}
/*
* create or get scsi inquiry data, expected by
* scsi_hba_nodename_compatible_get()
* SATA hard disks get Identify Data translated into Inguiry Data.
* ATAPI devices respond directly to Inquiry request.
*/
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
sata_identdev_to_inquiry(sata_hba_inst, sdinfo,
(uint8_t *)&inq);
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_addr->cport)));
} else { /* Assume supported ATAPI device */
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_addr->cport)));
if (sata_get_atapi_inquiry_data(sata_hba_inst, sata_addr,
&inq) == SATA_FAILURE)
return (NULL);
/*
* Save supported ATAPI transport version
*/
sdinfo->satadrv_atapi_trans_ver =
SATA_ATAPI_TRANS_VERSION(&inq);
}
/* determine the node name and compatible */
scsi_hba_nodename_compatible_get(&inq, NULL,
inq.inq_dtype, NULL, &nname, &compatible, &ncompatible);
#ifdef SATA_DEBUG
if (sata_debug_flags & SATA_DBG_NODES) {
if (nname == NULL) {
cmn_err(CE_NOTE, "sata_create_target_node: "
"cannot determine nodename for target %d\n",
target);
} else {
cmn_err(CE_WARN, "sata_create_target_node: "
"target %d nodename: %s\n", target, nname);
}
if (compatible == NULL) {
cmn_err(CE_WARN,
"sata_create_target_node: no compatible name\n");
} else {
for (i = 0; i < ncompatible; i++) {
cmn_err(CE_WARN, "sata_create_target_node: "
"compatible name: %s\n", compatible[i]);
}
}
}
#endif
/* if nodename can't be determined, log error and exit */
if (nname == NULL) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_create_target_node: cannot determine nodename "
"for target %d\n", target));
scsi_hba_nodename_compatible_free(nname, compatible);
return (NULL);
}
/*
* Create scsi target node
*/
ndi_devi_alloc_sleep(dip, nname, (pnode_t)DEVI_SID_NODEID, &cdip);
rval = ndi_prop_update_string(DDI_DEV_T_NONE, cdip,
"device-type", "scsi");
if (rval != DDI_PROP_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
"updating device_type prop failed %d", rval));
goto fail;
}
/*
* Create target node properties: target & lun
*/
rval = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "target", target);
if (rval != DDI_PROP_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
"updating target prop failed %d", rval));
goto fail;
}
rval = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "lun", 0);
if (rval != DDI_PROP_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
"updating target prop failed %d", rval));
goto fail;
}
if (sdinfo->satadrv_type & SATA_DTYPE_ATAPI) {
/*
* Add "variant" property
*/
rval = ndi_prop_update_string(DDI_DEV_T_NONE, cdip,
"variant", "atapi");
if (rval != DDI_PROP_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_create_target_node: variant atapi "
"property could not be created: %d", rval));
goto fail;
}
}
/* decorate the node with compatible */
if (ndi_prop_update_string_array(DDI_DEV_T_NONE, cdip, "compatible",
compatible, ncompatible) != DDI_PROP_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_create_target_node: FAIL compatible props cdip 0x%p",
(void *)cdip));
goto fail;
}
/*
* Now, try to attach the driver. If probing of the device fails,
* the target node may be removed
*/
rval = ndi_devi_online(cdip, NDI_ONLINE_ATTACH);
scsi_hba_nodename_compatible_free(nname, compatible);
if (rval == NDI_SUCCESS)
return (cdip);
/* target node was removed - are we sure? */
return (NULL);
fail:
scsi_hba_nodename_compatible_free(nname, compatible);
ddi_prop_remove_all(cdip);
rval = ndi_devi_free(cdip);
if (rval != NDI_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
"node removal failed %d", rval));
}
sata_log(sata_hba_inst, CE_WARN, "sata_create_target_node: "
"cannot create target node for SATA device at port %d",
sata_addr->cport);
return (NULL);
}
/*
* Re-probe sata port, check for a device and attach info
* structures when necessary. Identify Device data is fetched, if possible.
* Assumption: sata address is already validated.
* SATA_SUCCESS is returned if port is re-probed sucessfully, regardless of
* the presence of a device and its type.
*
* flag arg specifies that the function should try multiple times to identify
* device type and to initialize it, or it should return immediately on failure.
* SATA_DEV_IDENTIFY_RETRY - retry
* SATA_DEV_IDENTIFY_NORETRY - no retry
*
* SATA_FAILURE is returned if one of the operations failed.
*
* This function cannot be called in interrupt context - it may sleep.
*
* NOte: Port multiplier is not supported yet, although there may be some
* pieces of code referencing to it.
*/
static int
sata_reprobe_port(sata_hba_inst_t *sata_hba_inst, sata_device_t *sata_device,
int flag)
{
sata_cport_info_t *cportinfo;
sata_drive_info_t *sdinfo, *osdinfo;
boolean_t init_device = B_FALSE;
int prev_device_type = SATA_DTYPE_NONE;
int prev_device_settings = 0;
int prev_device_state = 0;
clock_t start_time;
int retry = B_FALSE;
int rval_probe, rval_init;
/* We only care about host sata cport for now */
cportinfo = SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport);
osdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
if (osdinfo != NULL) {
/*
* We are re-probing port with a previously attached device.
* Save previous device type and settings.
*/
prev_device_type = cportinfo->cport_dev_type;
prev_device_settings = osdinfo->satadrv_settings;
prev_device_state = osdinfo->satadrv_state;
}
if (flag == SATA_DEV_IDENTIFY_RETRY) {
start_time = ddi_get_lbolt();
retry = B_TRUE;
}
retry_probe:
/* probe port */
mutex_enter(&cportinfo->cport_mutex);
cportinfo->cport_state &= ~SATA_PORT_STATE_CLEAR_MASK;
cportinfo->cport_state |= SATA_STATE_PROBING;
mutex_exit(&cportinfo->cport_mutex);
rval_probe = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device);
mutex_enter(&cportinfo->cport_mutex);
if (rval_probe != SATA_SUCCESS) {
cportinfo->cport_state = SATA_PSTATE_FAILED;
mutex_exit(&cportinfo->cport_mutex);
SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_reprobe_port: "
"SATA port %d probing failed",
cportinfo->cport_addr.cport));
return (SATA_FAILURE);
}
/*
* update sata port state and set device type
*/
sata_update_port_info(sata_hba_inst, sata_device);
cportinfo->cport_state &= ~SATA_STATE_PROBING;
/*
* Sanity check - Port is active? Is the link active?
* Is there any device attached?
*/
if ((cportinfo->cport_state &
(SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) ||
(cportinfo->cport_scr.sstatus & SATA_PORT_DEVLINK_UP_MASK) !=
SATA_PORT_DEVLINK_UP) {
/*
* Port in non-usable state or no link active/no device.
* Free info structure if necessary (direct attached drive
* only, for now!
*/
sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
/* Add here differentiation for device attached or not */
cportinfo->cport_dev_type = SATA_DTYPE_NONE;
mutex_exit(&cportinfo->cport_mutex);
if (sdinfo != NULL)
kmem_free(sdinfo, sizeof (sata_drive_info_t));
return (SATA_SUCCESS);
}
cportinfo->cport_state |= SATA_STATE_READY;
cportinfo->cport_dev_type = sata_device->satadev_type;
sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
/*
* If we are re-probing the port, there may be
* sata_drive_info structure attached
* (or sata_pm_info, if PMult is supported).
*/
if (sata_device->satadev_type == SATA_DTYPE_NONE) {
/*
* There is no device, so remove device info structure,
* if necessary.
* Only direct attached drive is considered now, until
* port multiplier is supported. If the previously
* attached device was a port multiplier, we would need
* to take care of devices attached beyond the port
* multiplier.
*/
SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
cportinfo->cport_dev_type = SATA_DTYPE_NONE;
if (sdinfo != NULL) {
kmem_free(sdinfo, sizeof (sata_drive_info_t));
sata_log(sata_hba_inst, CE_WARN,
"SATA device detached "
"from port %d", cportinfo->cport_addr.cport);
}
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
if (sata_device->satadev_type != SATA_DTYPE_PMULT) {
if (sdinfo == NULL) {
/*
* There is some device attached, but there is
* no sata_drive_info structure - allocate one
*/
mutex_exit(&cportinfo->cport_mutex);
sdinfo = kmem_zalloc(
sizeof (sata_drive_info_t), KM_SLEEP);
mutex_enter(&cportinfo->cport_mutex);
/*
* Recheck, that the port state did not change when we
* released mutex.
*/
if (cportinfo->cport_state & SATA_STATE_READY) {
SATA_CPORTINFO_DRV_INFO(cportinfo) = sdinfo;
sdinfo->satadrv_addr = cportinfo->cport_addr;
sdinfo->satadrv_addr.qual = SATA_ADDR_DCPORT;
sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
sdinfo->satadrv_state = SATA_STATE_UNKNOWN;
} else {
/*
* Port is not in ready state, we
* cannot attach a device.
*/
mutex_exit(&cportinfo->cport_mutex);
kmem_free(sdinfo, sizeof (sata_drive_info_t));
return (SATA_SUCCESS);
}
/*
* Since we are adding device, presumably new one,
* indicate that it should be initalized,
* as well as some internal framework states).
*/
init_device = B_TRUE;
}
cportinfo->cport_dev_type = SATA_DTYPE_UNKNOWN;
sata_device->satadev_addr.qual = sdinfo->satadrv_addr.qual;
} else {
/*
* The device is a port multiplier - not handled now.
*/
cportinfo->cport_dev_type = SATA_DTYPE_UNKNOWN;
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
mutex_exit(&cportinfo->cport_mutex);
/*
* Figure out what kind of device we are really
* dealing with. Failure of identifying device does not fail this
* function.
*/
rval_probe = sata_probe_device(sata_hba_inst, sata_device);
rval_init = SATA_FAILURE;
mutex_enter(&cportinfo->cport_mutex);
if (rval_probe == SATA_SUCCESS) {
/*
* If we are dealing with the same type of a device as before,
* restore its settings flags.
*/
if (osdinfo != NULL &&
sata_device->satadev_type == prev_device_type)
sdinfo->satadrv_settings = prev_device_settings;
mutex_exit(&cportinfo->cport_mutex);
rval_init = SATA_SUCCESS;
/* Set initial device features, if necessary */
if (init_device == B_TRUE) {
rval_init = sata_initialize_device(sata_hba_inst,
sdinfo);
}
if (rval_init == SATA_SUCCESS)
return (rval_init);
/* else we will retry if retry was asked for */
} else {
/*
* If there was some device info before we probe the device,
* restore previous device setting, so we can retry from scratch
* later. Providing, of course, that device has not disapear
* during probing process.
*/
if (sata_device->satadev_type != SATA_DTYPE_NONE) {
if (osdinfo != NULL) {
cportinfo->cport_dev_type = prev_device_type;
sdinfo->satadrv_type = prev_device_type;
sdinfo->satadrv_state = prev_device_state;
}
} else {
/* device is gone */
kmem_free(sdinfo, sizeof (sata_drive_info_t));
cportinfo->cport_dev_type = SATA_DTYPE_NONE;
SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
mutex_exit(&cportinfo->cport_mutex);
return (SATA_SUCCESS);
}
mutex_exit(&cportinfo->cport_mutex);
}
if (retry) {
clock_t cur_time = ddi_get_lbolt();
/*
* A device was not successfully identified or initialized.
* Track retry time for device identification.
*/
if ((cur_time - start_time) <
drv_usectohz(SATA_DEV_REPROBE_TIMEOUT)) {
/* sleep for a while */
delay(drv_usectohz(SATA_DEV_RETRY_DLY));
goto retry_probe;
}
/* else no more retries */
mutex_enter(&cportinfo->cport_mutex);
if (SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
if (rval_init == SATA_RETRY) {
/*
* Setting drive features have failed, but
* because the drive is still accessible,
* keep it and emit a warning message.
*/
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d - desired "
"drive features could not be set. "
"Device may not operate as expected.",
cportinfo->cport_addr.cport);
} else {
SATA_CPORTINFO_DRV_INFO(cportinfo)->
satadrv_state = SATA_DSTATE_FAILED;
}
}
mutex_exit(&cportinfo->cport_mutex);
}
return (SATA_SUCCESS);
}
/*
* Initialize device
* Specified device is initialized to a default state.
*
* Returns SATA_SUCCESS if all device features are set successfully,
* SATA_RETRY if device is accessible but device features were not set
* successfully, and SATA_FAILURE otherwise.
*/
static int
sata_initialize_device(sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo)
{
int rval;
sata_save_drive_settings(sdinfo);
sdinfo->satadrv_settings |= SATA_DEV_READ_AHEAD;
sata_init_write_cache_mode(sdinfo);
rval = sata_set_drive_features(sata_hba_inst, sdinfo, 0);
/* Determine current data transfer mode */
if ((sdinfo->satadrv_id.ai_cap & SATA_DMA_SUPPORT) == 0) {
sdinfo->satadrv_settings &= ~SATA_DEV_DMA;
} else if ((sdinfo->satadrv_id.ai_validinfo &
SATA_VALIDINFO_88) != 0 &&
(sdinfo->satadrv_id.ai_ultradma & SATA_UDMA_SEL_MASK) != 0) {
sdinfo->satadrv_settings |= SATA_DEV_DMA;
} else if ((sdinfo->satadrv_id.ai_dworddma &
SATA_MDMA_SEL_MASK) != 0) {
sdinfo->satadrv_settings |= SATA_DEV_DMA;
} else
/* DMA supported, not no DMA transfer mode is selected !? */
sdinfo->satadrv_settings &= ~SATA_DEV_DMA;
return (rval);
}
/*
* Initialize write cache mode.
*
* The default write cache setting for SATA HDD is provided by sata_write_cache
* static variable. ATAPI CD/DVDs devices have write cache default is
* determined by sata_atapicdvd_write_cache static variable.
* ATAPI tape devices have write cache default is determined by
* sata_atapitape_write_cache static variable.
* ATAPI disk devices have write cache default is determined by
* sata_atapidisk_write_cache static variable.
* 1 - enable
* 0 - disable
* any other value - current drive setting
*
* Although there is not reason to disable write cache on CD/DVD devices,
* tape devices and ATAPI disk devices, the default setting control is provided
* for the maximun flexibility.
*
* In the future, it may be overridden by the
* disk-write-cache-enable property setting, if it is defined.
* Returns SATA_SUCCESS if all device features are set successfully,
* SATA_FAILURE otherwise.
*/
static void
sata_init_write_cache_mode(sata_drive_info_t *sdinfo)
{
switch (sdinfo->satadrv_type) {
case SATA_DTYPE_ATADISK:
if (sata_write_cache == 1)
sdinfo->satadrv_settings |= SATA_DEV_WRITE_CACHE;
else if (sata_write_cache == 0)
sdinfo->satadrv_settings &= ~SATA_DEV_WRITE_CACHE;
/*
* When sata_write_cache value is not 0 or 1,
* a current setting of the drive's write cache is used.
*/
break;
case SATA_DTYPE_ATAPICD:
if (sata_atapicdvd_write_cache == 1)
sdinfo->satadrv_settings |= SATA_DEV_WRITE_CACHE;
else if (sata_atapicdvd_write_cache == 0)
sdinfo->satadrv_settings &= ~SATA_DEV_WRITE_CACHE;
/*
* When sata_atapicdvd_write_cache value is not 0 or 1,
* a current setting of the drive's write cache is used.
*/
break;
case SATA_DTYPE_ATAPITAPE:
if (sata_atapitape_write_cache == 1)
sdinfo->satadrv_settings |= SATA_DEV_WRITE_CACHE;
else if (sata_atapitape_write_cache == 0)
sdinfo->satadrv_settings &= ~SATA_DEV_WRITE_CACHE;
/*
* When sata_atapitape_write_cache value is not 0 or 1,
* a current setting of the drive's write cache is used.
*/
break;
case SATA_DTYPE_ATAPIDISK:
if (sata_atapidisk_write_cache == 1)
sdinfo->satadrv_settings |= SATA_DEV_WRITE_CACHE;
else if (sata_atapidisk_write_cache == 0)
sdinfo->satadrv_settings &= ~SATA_DEV_WRITE_CACHE;
/*
* When sata_atapidisk_write_cache value is not 0 or 1,
* a current setting of the drive's write cache is used.
*/
break;
}
}
/*
* Validate sata address.
* Specified cport, pmport and qualifier has to match
* passed sata_scsi configuration info.
* The presence of an attached device is not verified.
*
* Returns 0 when address is valid, -1 otherwise.
*/
static int
sata_validate_sata_address(sata_hba_inst_t *sata_hba_inst, int cport,
int pmport, int qual)
{
if (qual == SATA_ADDR_DCPORT && pmport != 0)
goto invalid_address;
if (cport >= SATA_NUM_CPORTS(sata_hba_inst))
goto invalid_address;
if ((qual == SATA_ADDR_DPMPORT || qual == SATA_ADDR_PMPORT) &&
((SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) != SATA_DTYPE_PMULT) ||
(SATA_PMULT_INFO(sata_hba_inst, cport) == NULL) ||
(pmport >= SATA_NUM_PMPORTS(sata_hba_inst, cport))))
goto invalid_address;
return (0);
invalid_address:
return (-1);
}
/*
* Validate scsi address
* SCSI target address is translated into SATA cport/pmport and compared
* with a controller port/device configuration. LUN has to be 0.
* Returns 0 if a scsi target refers to an attached device,
* returns 1 if address is valid but device is not attached,
* returns -1 if bad address or device is of an unsupported type.
* Upon return sata_device argument is set.
*/
static int
sata_validate_scsi_address(sata_hba_inst_t *sata_hba_inst,
struct scsi_address *ap, sata_device_t *sata_device)
{
int cport, pmport, qual, rval;
rval = -1; /* Invalid address */
if (ap->a_lun != 0)
goto out;
qual = SCSI_TO_SATA_ADDR_QUAL(ap->a_target);
cport = SCSI_TO_SATA_CPORT(ap->a_target);
pmport = SCSI_TO_SATA_PMPORT(ap->a_target);
if (qual != SATA_ADDR_DCPORT && qual != SATA_ADDR_DPMPORT)
goto out;
if (sata_validate_sata_address(sata_hba_inst, cport, pmport, qual) ==
0) {
sata_cport_info_t *cportinfo;
sata_pmult_info_t *pmultinfo;
sata_drive_info_t *sdinfo = NULL;
rval = 1; /* Valid sata address */
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
if (qual == SATA_ADDR_DCPORT) {
if (cportinfo == NULL ||
cportinfo->cport_dev_type == SATA_DTYPE_NONE)
goto out;
if (cportinfo->cport_dev_type == SATA_DTYPE_PMULT ||
(cportinfo->cport_dev_type &
SATA_VALID_DEV_TYPE) == 0) {
rval = -1;
goto out;
}
sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
} else if (qual == SATA_ADDR_DPMPORT) {
pmultinfo = SATA_CPORTINFO_PMULT_INFO(cportinfo);
if (pmultinfo == NULL) {
rval = -1;
goto out;
}
if (SATA_PMPORT_INFO(sata_hba_inst, cport, pmport) ==
NULL ||
SATA_PMPORT_DEV_TYPE(sata_hba_inst, cport,
pmport) == SATA_DTYPE_NONE)
goto out;
sdinfo = SATA_PMPORT_DRV_INFO(sata_hba_inst, cport,
pmport);
} else {
rval = -1;
goto out;
}
if ((sdinfo == NULL) ||
(sdinfo->satadrv_type & SATA_VALID_DEV_TYPE) == 0)
goto out;
sata_device->satadev_type = sdinfo->satadrv_type;
sata_device->satadev_addr.qual = qual;
sata_device->satadev_addr.cport = cport;
sata_device->satadev_addr.pmport = pmport;
sata_device->satadev_rev = SATA_DEVICE_REV_1;
return (0);
}
out:
if (rval == 1) {
SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
"sata_validate_scsi_address: no valid target %x lun %x",
ap->a_target, ap->a_lun);
}
return (rval);
}
/*
* Find dip corresponding to passed device number
*
* Returns NULL if invalid device number is passed or device cannot be found,
* Returns dip is device is found.
*/
static dev_info_t *
sata_devt_to_devinfo(dev_t dev)
{
dev_info_t *dip;
#ifndef __lock_lint
struct devnames *dnp;
major_t major = getmajor(dev);
int instance = SATA_MINOR2INSTANCE(getminor(dev));
if (major >= devcnt)
return (NULL);
dnp = &devnamesp[major];
LOCK_DEV_OPS(&(dnp->dn_lock));
dip = dnp->dn_head;
while (dip && (ddi_get_instance(dip) != instance)) {
dip = ddi_get_next(dip);
}
UNLOCK_DEV_OPS(&(dnp->dn_lock));
#endif
return (dip);
}
/*
* Probe device.
* This function issues Identify Device command and initializes local
* sata_drive_info structure if the device can be identified.
* The device type is determined by examining Identify Device
* command response.
* If the sata_hba_inst has linked drive info structure for this
* device address, the Identify Device data is stored into sata_drive_info
* structure linked to the port info structure.
*
* sata_device has to refer to the valid sata port(s) for HBA described
* by sata_hba_inst structure.
*
* Returns:
* SATA_SUCCESS if device type was successfully probed and port-linked
* drive info structure was updated;
* SATA_FAILURE if there is no device, or device was not probed
* successully;
* SATA_RETRY if device probe can be retried later.
* If a device cannot be identified, sata_device's dev_state and dev_type
* fields are set to unknown.
* There are no retries in this function. Any retries should be managed by
* the caller.
*/
static int
sata_probe_device(sata_hba_inst_t *sata_hba_inst, sata_device_t *sata_device)
{
sata_drive_info_t *sdinfo;
sata_drive_info_t new_sdinfo; /* local drive info struct */
int rval;
ASSERT((SATA_CPORT_STATE(sata_hba_inst,
sata_device->satadev_addr.cport) &
(SATA_STATE_PROBED | SATA_STATE_READY)) != 0);
sata_device->satadev_type = SATA_DTYPE_NONE;
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
/* Get pointer to port-linked sata device info structure */
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
if (sdinfo != NULL) {
sdinfo->satadrv_state &=
~(SATA_STATE_PROBED | SATA_STATE_READY);
sdinfo->satadrv_state |= SATA_STATE_PROBING;
} else {
/* No device to probe */
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
sata_device->satadev_type = SATA_DTYPE_NONE;
sata_device->satadev_state = SATA_STATE_UNKNOWN;
return (SATA_FAILURE);
}
/*
* Need to issue both types of identify device command and
* determine device type by examining retreived data/status.
* First, ATA Identify Device.
*/
bzero(&new_sdinfo, sizeof (sata_drive_info_t));
new_sdinfo.satadrv_addr = sata_device->satadev_addr;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
new_sdinfo.satadrv_type = SATA_DTYPE_ATADISK;
rval = sata_identify_device(sata_hba_inst, &new_sdinfo);
if (rval == SATA_RETRY) {
/* We may try to check for ATAPI device */
if (SATA_FEATURES(sata_hba_inst) & SATA_CTLF_ATAPI) {
/*
* HBA supports ATAPI - try to issue Identify Packet
* Device command.
*/
new_sdinfo.satadrv_type = SATA_DTYPE_ATAPI;
rval = sata_identify_device(sata_hba_inst, &new_sdinfo);
}
}
if (rval == SATA_SUCCESS) {
/*
* Got something responding positively to ATA Identify Device
* or to Identify Packet Device cmd.
* Save last used device type.
*/
sata_device->satadev_type = new_sdinfo.satadrv_type;
/* save device info, if possible */
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
if (sdinfo == NULL) {
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
return (SATA_FAILURE);
}
/*
* Copy drive info into the port-linked drive info structure.
*/
*sdinfo = new_sdinfo;
sdinfo->satadrv_state &= ~SATA_STATE_PROBING;
sdinfo->satadrv_state |= SATA_STATE_PROBED;
if (sata_device->satadev_addr.qual == SATA_ADDR_DCPORT)
SATA_CPORT_DEV_TYPE(sata_hba_inst,
sata_device->satadev_addr.cport) =
sdinfo->satadrv_type;
else /* SATA_ADDR_DPMPORT */
SATA_PMPORT_DEV_TYPE(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport) =
sdinfo->satadrv_type;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
return (SATA_SUCCESS);
}
/*
* It may be SATA_RETRY or SATA_FAILURE return.
* Looks like we cannot determine the device type at this time.
*/
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
if (sdinfo != NULL) {
sata_device->satadev_type = SATA_DTYPE_UNKNOWN;
sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
sdinfo->satadrv_state &= ~SATA_STATE_PROBING;
sdinfo->satadrv_state |= SATA_STATE_PROBED;
if (sata_device->satadev_addr.qual == SATA_ADDR_DCPORT)
SATA_CPORT_DEV_TYPE(sata_hba_inst,
sata_device->satadev_addr.cport) =
SATA_DTYPE_UNKNOWN;
else {
/* SATA_ADDR_DPMPORT */
if ((SATA_PMULT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport) != NULL) &&
(SATA_PMPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport) != NULL))
SATA_PMPORT_DEV_TYPE(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport) =
SATA_DTYPE_UNKNOWN;
}
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
return (rval);
}
/*
* Get pointer to sata_drive_info structure.
*
* The sata_device has to contain address (cport, pmport and qualifier) for
* specified sata_scsi structure.
*
* Returns NULL if device address is not valid for this HBA configuration.
* Otherwise, returns a pointer to sata_drive_info structure.
*
* This function should be called with a port mutex held.
*/
static sata_drive_info_t *
sata_get_device_info(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
uint8_t cport = sata_device->satadev_addr.cport;
uint8_t pmport = sata_device->satadev_addr.pmport;
uint8_t qual = sata_device->satadev_addr.qual;
if (cport >= SATA_NUM_CPORTS(sata_hba_inst))
return (NULL);
if (!(SATA_CPORT_STATE(sata_hba_inst, cport) &
(SATA_STATE_PROBED | SATA_STATE_READY)))
/* Port not probed yet */
return (NULL);
if (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) == SATA_DTYPE_NONE)
return (NULL);
if (qual == SATA_ADDR_DCPORT) {
/* Request for a device on a controller port */
if (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) ==
SATA_DTYPE_PMULT)
/* Port multiplier attached */
return (NULL);
return (SATA_CPORT_DRV_INFO(sata_hba_inst, cport));
}
if (qual == SATA_ADDR_DPMPORT) {
if (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) !=
SATA_DTYPE_PMULT)
return (NULL);
if (pmport > SATA_NUM_PMPORTS(sata_hba_inst, cport))
return (NULL);
return (SATA_PMPORT_DRV_INFO(sata_hba_inst, cport, pmport));
}
/* we should not get here */
return (NULL);
}
/*
* sata_identify_device.
* Send Identify Device command to SATA HBA driver.
* If command executes successfully, update sata_drive_info structure pointed
* to by sdinfo argument, including Identify Device data.
* If command fails, invalidate data in sata_drive_info.
*
* Cannot be called from interrupt level.
*
* Returns:
* SATA_SUCCESS if the device was identified as a supported device,
* SATA_RETRY if the device was not identified but could be retried,
* SATA_FAILURE if the device was not identified and identify attempt
* should not be retried.
*/
static int
sata_identify_device(sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo)
{
uint16_t cfg_word;
int rval;
/* fetch device identify data */
if ((rval = sata_fetch_device_identify_data(sata_hba_inst,
sdinfo)) != SATA_SUCCESS)
goto fail_unknown;
cfg_word = sdinfo->satadrv_id.ai_config;
/* Set the correct device type */
if ((cfg_word & SATA_ATA_TYPE_MASK) == SATA_ATA_TYPE) {
sdinfo->satadrv_type = SATA_DTYPE_ATADISK;
} else if ((cfg_word & SATA_ATAPI_TYPE_MASK) == SATA_ATAPI_TYPE) {
switch (cfg_word & SATA_ATAPI_ID_DEV_TYPE) {
case SATA_ATAPI_CDROM_DEV:
sdinfo->satadrv_type = SATA_DTYPE_ATAPICD;
break;
case SATA_ATAPI_SQACC_DEV:
sdinfo->satadrv_type = SATA_DTYPE_ATAPITAPE;
break;
case SATA_ATAPI_DIRACC_DEV:
sdinfo->satadrv_type = SATA_DTYPE_ATAPIDISK;
break;
default:
sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
}
} else {
sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
}
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
if (sdinfo->satadrv_capacity == 0) {
/* Non-LBA disk. Too bad... */
sata_log(sata_hba_inst, CE_WARN,
"SATA disk device at port %d does not support LBA",
sdinfo->satadrv_addr.cport);
rval = SATA_FAILURE;
goto fail_unknown;
}
}
#if 0
/* Left for historical reason */
/*
* Some initial version of SATA spec indicated that at least
* UDMA mode 4 has to be supported. It is not metioned in
* SerialATA 2.6, so this restriction is removed.
*/
/* Check for Ultra DMA modes 6 through 0 being supported */
for (i = 6; i >= 0; --i) {
if (sdinfo->satadrv_id.ai_ultradma & (1 << i))
break;
}
/*
* At least UDMA 4 mode has to be supported. If mode 4 or
* higher are not supported by the device, fail this
* device.
*/
if (i < 4) {
/* No required Ultra DMA mode supported */
sata_log(sata_hba_inst, CE_WARN,
"SATA disk device at port %d does not support UDMA "
"mode 4 or higher", sdinfo->satadrv_addr.cport);
SATA_LOG_D((sata_hba_inst, CE_WARN,
"mode 4 or higher required, %d supported", i));
rval = SATA_FAILURE;
goto fail_unknown;
}
#endif
/*
* For Disk devices, if it doesn't support UDMA mode, we would
* like to return failure directly.
*/
if ((sdinfo->satadrv_type == SATA_DTYPE_ATADISK) &&
!((sdinfo->satadrv_id.ai_validinfo & SATA_VALIDINFO_88) != 0 &&
(sdinfo->satadrv_id.ai_ultradma & SATA_UDMA_SUP_MASK) != 0)) {
sata_log(sata_hba_inst, CE_WARN,
"SATA disk device at port %d does not support UDMA",
sdinfo->satadrv_addr.cport);
rval = SATA_FAILURE;
goto fail_unknown;
}
return (SATA_SUCCESS);
fail_unknown:
/* Invalidate sata_drive_info ? */
sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
sdinfo->satadrv_state = SATA_STATE_UNKNOWN;
return (rval);
}
/*
* Log/display device information
*/
static void
sata_show_drive_info(sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo)
{
int valid_version;
char msg_buf[MAXPATHLEN];
int i;
/* Show HBA path */
(void) ddi_pathname(SATA_DIP(sata_hba_inst), msg_buf);
cmn_err(CE_CONT, "?%s :\n", msg_buf);
switch (sdinfo->satadrv_type) {
case SATA_DTYPE_ATADISK:
(void) sprintf(msg_buf, "SATA disk device at");
break;
case SATA_DTYPE_ATAPICD:
(void) sprintf(msg_buf, "SATA CD/DVD (ATAPI) device at");
break;
case SATA_DTYPE_ATAPITAPE:
(void) sprintf(msg_buf, "SATA tape (ATAPI) device at");
break;
case SATA_DTYPE_ATAPIDISK:
(void) sprintf(msg_buf, "SATA disk (ATAPI) device at");
break;
case SATA_DTYPE_UNKNOWN:
(void) sprintf(msg_buf,
"Unsupported SATA device type (cfg 0x%x) at ",
sdinfo->satadrv_id.ai_config);
break;
}
if (sdinfo->satadrv_addr.qual == SATA_ADDR_DCPORT)
cmn_err(CE_CONT, "?\t%s port %d\n",
msg_buf, sdinfo->satadrv_addr.cport);
else
cmn_err(CE_CONT, "?\t%s port %d pmport %d\n",
msg_buf, sdinfo->satadrv_addr.cport,
sdinfo->satadrv_addr.pmport);
bcopy(&sdinfo->satadrv_id.ai_model, msg_buf,
sizeof (sdinfo->satadrv_id.ai_model));
swab(msg_buf, msg_buf, sizeof (sdinfo->satadrv_id.ai_model));
msg_buf[sizeof (sdinfo->satadrv_id.ai_model)] = '\0';
cmn_err(CE_CONT, "?\tmodel %s\n", msg_buf);
bcopy(&sdinfo->satadrv_id.ai_fw, msg_buf,
sizeof (sdinfo->satadrv_id.ai_fw));
swab(msg_buf, msg_buf, sizeof (sdinfo->satadrv_id.ai_fw));
msg_buf[sizeof (sdinfo->satadrv_id.ai_fw)] = '\0';
cmn_err(CE_CONT, "?\tfirmware %s\n", msg_buf);
bcopy(&sdinfo->satadrv_id.ai_drvser, msg_buf,
sizeof (sdinfo->satadrv_id.ai_drvser));
swab(msg_buf, msg_buf, sizeof (sdinfo->satadrv_id.ai_drvser));
msg_buf[sizeof (sdinfo->satadrv_id.ai_drvser)] = '\0';
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
cmn_err(CE_CONT, "?\tserial number %s\n", msg_buf);
} else {
/*
* Some drives do not implement serial number and may
* violate the spec by providing spaces rather than zeros
* in serial number field. Scan the buffer to detect it.
*/
for (i = 0; i < sizeof (sdinfo->satadrv_id.ai_drvser); i++) {
if (msg_buf[i] != '\0' && msg_buf[i] != ' ')
break;
}
if (i == sizeof (sdinfo->satadrv_id.ai_drvser)) {
cmn_err(CE_CONT, "?\tserial number - none\n");
} else {
cmn_err(CE_CONT, "?\tserial number %s\n", msg_buf);
}
}
#ifdef SATA_DEBUG
if (sdinfo->satadrv_id.ai_majorversion != 0 &&
sdinfo->satadrv_id.ai_majorversion != 0xffff) {
int i;
for (i = 14; i >= 2; i--) {
if (sdinfo->satadrv_id.ai_majorversion & (1 << i)) {
valid_version = i;
break;
}
}
cmn_err(CE_CONT,
"?\tATA/ATAPI-%d supported, majver 0x%x minver 0x%x\n",
valid_version,
sdinfo->satadrv_id.ai_majorversion,
sdinfo->satadrv_id.ai_minorversion);
}
#endif
/* Log some info */
cmn_err(CE_CONT, "?\tsupported features:\n");
msg_buf[0] = '\0';
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA48)
(void) strlcat(msg_buf, "48-bit LBA, ", MAXPATHLEN);
else if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA28)
(void) strlcat(msg_buf, "28-bit LBA, ", MAXPATHLEN);
}
if (sdinfo->satadrv_features_support & SATA_DEV_F_DMA)
(void) strlcat(msg_buf, "DMA", MAXPATHLEN);
if (sdinfo->satadrv_features_support & SATA_DEV_F_NCQ)
(void) strlcat(msg_buf, ", Native Command Queueing",
MAXPATHLEN);
if (sdinfo->satadrv_features_support & SATA_DEV_F_TCQ)
(void) strlcat(msg_buf, ", Legacy Tagged Queuing", MAXPATHLEN);
if ((sdinfo->satadrv_id.ai_cmdset82 & SATA_SMART_SUPPORTED) &&
(sdinfo->satadrv_id.ai_features85 & SATA_SMART_ENABLED))
(void) strlcat(msg_buf, ", SMART", MAXPATHLEN);
if ((sdinfo->satadrv_id.ai_cmdset84 & SATA_SMART_SELF_TEST_SUPPORTED) &&
(sdinfo->satadrv_id.ai_features87 & SATA_SMART_SELF_TEST_SUPPORTED))
(void) strlcat(msg_buf, ", SMART self-test", MAXPATHLEN);
cmn_err(CE_CONT, "?\t %s\n", msg_buf);
if (sdinfo->satadrv_features_support & SATA_DEV_F_SATA2)
cmn_err(CE_CONT, "?\tSATA Gen2 signaling speed (3.0Gbps)\n");
else if (sdinfo->satadrv_features_support & SATA_DEV_F_SATA1)
cmn_err(CE_CONT, "?\tSATA Gen1 signaling speed (1.5Gbps)\n");
if (sdinfo->satadrv_features_support &
(SATA_DEV_F_TCQ | SATA_DEV_F_NCQ)) {
msg_buf[0] = '\0';
(void) snprintf(msg_buf, MAXPATHLEN,
"Supported queue depth %d",
sdinfo->satadrv_queue_depth);
if (!(sata_func_enable &
(SATA_ENABLE_QUEUING | SATA_ENABLE_NCQ)))
(void) strlcat(msg_buf,
" - queueing disabled globally", MAXPATHLEN);
else if (sdinfo->satadrv_queue_depth >
sdinfo->satadrv_max_queue_depth) {
(void) snprintf(&msg_buf[strlen(msg_buf)],
MAXPATHLEN - strlen(msg_buf), ", limited to %d",
(int)sdinfo->satadrv_max_queue_depth);
}
cmn_err(CE_CONT, "?\t%s\n", msg_buf);
}
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
#ifdef __i386
(void) sprintf(msg_buf, "\tcapacity = %llu sectors\n",
sdinfo->satadrv_capacity);
#else
(void) sprintf(msg_buf, "\tcapacity = %lu sectors\n",
sdinfo->satadrv_capacity);
#endif
cmn_err(CE_CONT, "?%s", msg_buf);
}
}
/*
* sata_save_drive_settings extracts current setting of the device and stores
* it for future reference, in case the device setup would need to be restored
* after the device reset.
*
* For all devices read ahead and write cache settings are saved, if the
* device supports these features at all.
* For ATAPI devices the Removable Media Status Notification setting is saved.
*/
static void
sata_save_drive_settings(sata_drive_info_t *sdinfo)
{
if (SATA_READ_AHEAD_SUPPORTED(sdinfo->satadrv_id) ||
SATA_WRITE_CACHE_SUPPORTED(sdinfo->satadrv_id)) {
/* Current setting of Read Ahead (and Read Cache) */
if (SATA_READ_AHEAD_ENABLED(sdinfo->satadrv_id))
sdinfo->satadrv_settings |= SATA_DEV_READ_AHEAD;
else
sdinfo->satadrv_settings &= ~SATA_DEV_READ_AHEAD;
/* Current setting of Write Cache */
if (SATA_WRITE_CACHE_ENABLED(sdinfo->satadrv_id))
sdinfo->satadrv_settings |= SATA_DEV_WRITE_CACHE;
else
sdinfo->satadrv_settings &= ~SATA_DEV_WRITE_CACHE;
}
if (sdinfo->satadrv_type == SATA_DTYPE_ATAPICD) {
if (SATA_RM_NOTIFIC_SUPPORTED(sdinfo->satadrv_id))
sdinfo->satadrv_settings |= SATA_DEV_RMSN;
else
sdinfo->satadrv_settings &= ~SATA_DEV_RMSN;
}
}
/*
* sata_check_capacity function determines a disk capacity
* and addressing mode (LBA28/LBA48) by examining a disk identify device data.
*
* NOTE: CHS mode is not supported! If a device does not support LBA,
* this function is not called.
*
* Returns device capacity in number of blocks, i.e. largest addressable LBA+1
*/
static uint64_t
sata_check_capacity(sata_drive_info_t *sdinfo)
{
uint64_t capacity = 0;
int i;
if (sdinfo->satadrv_type != SATA_DTYPE_ATADISK ||
!sdinfo->satadrv_id.ai_cap & SATA_LBA_SUPPORT)
/* Capacity valid only for LBA-addressable disk devices */
return (0);
if ((sdinfo->satadrv_id.ai_validinfo & SATA_VALIDINFO_88) &&
(sdinfo->satadrv_id.ai_cmdset83 & SATA_EXT48) &&
(sdinfo->satadrv_id.ai_features86 & SATA_EXT48)) {
/* LBA48 mode supported and enabled */
sdinfo->satadrv_features_support |= SATA_DEV_F_LBA48 |
SATA_DEV_F_LBA28;
for (i = 3; i >= 0; --i) {
capacity <<= 16;
capacity += sdinfo->satadrv_id.ai_addrsecxt[i];
}
} else {
capacity = sdinfo->satadrv_id.ai_addrsec[1];
capacity <<= 16;
capacity += sdinfo->satadrv_id.ai_addrsec[0];
if (capacity >= 0x1000000)
/* LBA28 mode */
sdinfo->satadrv_features_support |= SATA_DEV_F_LBA28;
}
return (capacity);
}
/*
* Allocate consistent buffer for DMA transfer
*
* Cannot be called from interrupt level or with mutex held - it may sleep.
*
* Returns pointer to allocated buffer structure, or NULL if allocation failed.
*/
static struct buf *
sata_alloc_local_buffer(sata_pkt_txlate_t *spx, int len)
{
struct scsi_address ap;
struct buf *bp;
ddi_dma_attr_t cur_dma_attr;
ASSERT(spx->txlt_sata_pkt != NULL);
ap.a_hba_tran = spx->txlt_sata_hba_inst->satahba_scsi_tran;
ap.a_target = SATA_TO_SCSI_TARGET(
spx->txlt_sata_pkt->satapkt_device.satadev_addr.cport,
spx->txlt_sata_pkt->satapkt_device.satadev_addr.pmport,
spx->txlt_sata_pkt->satapkt_device.satadev_addr.qual);
ap.a_lun = 0;
bp = scsi_alloc_consistent_buf(&ap, NULL, len,
B_READ, SLEEP_FUNC, NULL);
if (bp != NULL) {
/* Allocate DMA resources for this buffer */
spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = bp;
/*
* We use a local version of the dma_attr, to account
* for a device addressing limitations.
* sata_adjust_dma_attr() will handle sdinfo == NULL which
* will cause dma attributes to be adjusted to a lowest
* acceptable level.
*/
sata_adjust_dma_attr(NULL,
SATA_DMA_ATTR(spx->txlt_sata_hba_inst), &cur_dma_attr);
if (sata_dma_buf_setup(spx, PKT_CONSISTENT,
SLEEP_FUNC, NULL, &cur_dma_attr) != DDI_SUCCESS) {
scsi_free_consistent_buf(bp);
spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = NULL;
bp = NULL;
}
}
return (bp);
}
/*
* Release local buffer (consistent buffer for DMA transfer) allocated
* via sata_alloc_local_buffer().
*/
static void
sata_free_local_buffer(sata_pkt_txlate_t *spx)
{
ASSERT(spx->txlt_sata_pkt != NULL);
ASSERT(spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp != NULL);
spx->txlt_sata_pkt->satapkt_cmd.satacmd_num_dma_cookies = 0;
spx->txlt_sata_pkt->satapkt_cmd.satacmd_dma_cookie_list = NULL;
sata_common_free_dma_rsrcs(spx);
/* Free buffer */
scsi_free_consistent_buf(spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp);
spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = NULL;
}
/*
* Allocate sata_pkt
* Pkt structure version and embedded strcutures version are initialized.
* sata_pkt and sata_pkt_txlate structures are cross-linked.
*
* Since this may be called in interrupt context by sata_scsi_init_pkt,
* callback argument determines if it can sleep or not.
* Hence, it should not be called from interrupt context.
*
* If successful, non-NULL pointer to a sata pkt is returned.
* Upon failure, NULL pointer is returned.
*/
static sata_pkt_t *
sata_pkt_alloc(sata_pkt_txlate_t *spx, int (*callback)(caddr_t))
{
sata_pkt_t *spkt;
int kmsflag;
kmsflag = (callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP;
spkt = kmem_zalloc(sizeof (sata_pkt_t), kmsflag);
if (spkt == NULL) {
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_pkt_alloc: failed"));
return (NULL);
}
spkt->satapkt_rev = SATA_PKT_REV;
spkt->satapkt_cmd.satacmd_rev = SATA_CMD_REV;
spkt->satapkt_device.satadev_rev = SATA_DEVICE_REV;
spkt->satapkt_framework_private = spx;
spx->txlt_sata_pkt = spkt;
return (spkt);
}
/*
* Free sata pkt allocated via sata_pkt_alloc()
*/
static void
sata_pkt_free(sata_pkt_txlate_t *spx)
{
ASSERT(spx->txlt_sata_pkt != NULL);
ASSERT(spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp == NULL);
kmem_free(spx->txlt_sata_pkt, sizeof (sata_pkt_t));
spx->txlt_sata_pkt = NULL;
}
/*
* Adjust DMA attributes.
* SCSI cmds block count is up to 24 bits, SATA cmd block count vary
* from 8 bits to 16 bits, depending on a command being used.
* Limiting max block count arbitrarily to 256 for all read/write
* commands may affects performance, so check both the device and
* controller capability before adjusting dma attributes.
*/
void
sata_adjust_dma_attr(sata_drive_info_t *sdinfo, ddi_dma_attr_t *dma_attr,
ddi_dma_attr_t *adj_dma_attr)
{
uint32_t count_max;
/* Copy original attributes */
*adj_dma_attr = *dma_attr;
/*
* Things to consider: device addressing capability,
* "excessive" controller DMA capabilities.
* If a device is being probed/initialized, there are
* no device info - use default limits then.
*/
if (sdinfo == NULL) {
count_max = dma_attr->dma_attr_granular * 0x100;
if (dma_attr->dma_attr_count_max > count_max)
adj_dma_attr->dma_attr_count_max = count_max;
if (dma_attr->dma_attr_maxxfer > count_max)
adj_dma_attr->dma_attr_maxxfer = count_max;
return;
}
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
if (sdinfo->satadrv_features_support & (SATA_DEV_F_LBA48)) {
/*
* 16-bit sector count may be used - we rely on
* the assumption that only read and write cmds
* will request more than 256 sectors worth of data
*/
count_max = adj_dma_attr->dma_attr_granular * 0x10000;
} else {
/*
* 8-bit sector count will be used - default limits
* for dma attributes
*/
count_max = adj_dma_attr->dma_attr_granular * 0x100;
}
/*
* Adjust controler dma attributes, if necessary
*/
if (dma_attr->dma_attr_count_max > count_max)
adj_dma_attr->dma_attr_count_max = count_max;
if (dma_attr->dma_attr_maxxfer > count_max)
adj_dma_attr->dma_attr_maxxfer = count_max;
}
}
/*
* Allocate DMA resources for the buffer
* This function handles initial DMA resource allocation as well as
* DMA window shift and may be called repeatedly for the same DMA window
* until all DMA cookies in the DMA window are processed.
* To guarantee that there is always a coherent set of cookies to process
* by SATA HBA driver (observing alignment, device granularity, etc.),
* the number of slots for DMA cookies is equal to lesser of a number of
* cookies in a DMA window and a max number of scatter/gather entries.
*
* Returns DDI_SUCCESS upon successful operation.
* Return failure code of a failing command or DDI_FAILURE when
* internal cleanup failed.
*/
static int
sata_dma_buf_setup(sata_pkt_txlate_t *spx, int flags,
int (*callback)(caddr_t), caddr_t arg,
ddi_dma_attr_t *cur_dma_attr)
{
int rval;
off_t offset;
size_t size;
int max_sg_len, req_len, i;
uint_t dma_flags;
struct buf *bp;
uint64_t cur_txfer_len;
ASSERT(spx->txlt_sata_pkt != NULL);
bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
ASSERT(bp != NULL);
if (spx->txlt_buf_dma_handle == NULL) {
/*
* No DMA resources allocated so far - this is a first call
* for this sata pkt.
*/
rval = ddi_dma_alloc_handle(SATA_DIP(spx->txlt_sata_hba_inst),
cur_dma_attr, callback, arg, &spx->txlt_buf_dma_handle);
if (rval != DDI_SUCCESS) {
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_dma_buf_setup: no buf DMA resources %x",
rval));
return (rval);
}
if (bp->b_flags & B_READ)
dma_flags = DDI_DMA_READ;
else
dma_flags = DDI_DMA_WRITE;
if (flags & PKT_CONSISTENT)
dma_flags |= DDI_DMA_CONSISTENT;
if (flags & PKT_DMA_PARTIAL)
dma_flags |= DDI_DMA_PARTIAL;
/*
* Check buffer alignment and size against dma attributes
* Consider dma_attr_align only. There may be requests
* with the size lower than device granularity, but they
* will not read/write from/to the device, so no adjustment
* is necessary. The dma_attr_minxfer theoretically should
* be considered, but no HBA driver is checking it.
*/
if (IS_P2ALIGNED(bp->b_un.b_addr,
cur_dma_attr->dma_attr_align)) {
rval = ddi_dma_buf_bind_handle(
spx->txlt_buf_dma_handle,
bp, dma_flags, callback, arg,
&spx->txlt_dma_cookie,
&spx->txlt_curwin_num_dma_cookies);
} else { /* Buffer is not aligned */
int (*ddicallback)(caddr_t);
size_t bufsz;
/* Check id sleeping is allowed */
ddicallback = (callback == NULL_FUNC) ?
DDI_DMA_DONTWAIT : DDI_DMA_SLEEP;
SATADBG2(SATA_DBG_DMA_SETUP, spx->txlt_sata_hba_inst,
"mis-aligned buffer: addr=0x%p, cnt=%lu",
(void *)bp->b_un.b_addr, bp->b_bcount);
if (bp->b_flags & (B_PAGEIO|B_PHYS))
/*
* CPU will need to access data in the buffer
* (for copying) so map it.
*/
bp_mapin(bp);
ASSERT(spx->txlt_tmp_buf == NULL);
/* Buffer may be padded by ddi_dma_mem_alloc()! */
rval = ddi_dma_mem_alloc(
spx->txlt_buf_dma_handle,
bp->b_bcount,
&sata_acc_attr,
DDI_DMA_STREAMING,
ddicallback, NULL,
&spx->txlt_tmp_buf,
&bufsz,
&spx->txlt_tmp_buf_handle);
if (rval != DDI_SUCCESS) {
/* DMA mapping failed */
(void) ddi_dma_free_handle(
&spx->txlt_buf_dma_handle);
spx->txlt_buf_dma_handle = NULL;
#ifdef SATA_DEBUG
mbuffail_count++;
#endif
SATADBG1(SATA_DBG_DMA_SETUP,
spx->txlt_sata_hba_inst,
"sata_dma_buf_setup: "
"buf dma mem alloc failed %x\n", rval);
return (rval);
}
ASSERT(IS_P2ALIGNED(spx->txlt_tmp_buf,
cur_dma_attr->dma_attr_align));
#ifdef SATA_DEBUG
mbuf_count++;
if (bp->b_bcount != bufsz)
/*
* This will require special handling, because
* DMA cookies will be based on the temporary
* buffer size, not the original buffer
* b_bcount, so the residue may have to
* be counted differently.
*/
SATADBG2(SATA_DBG_DMA_SETUP,
spx->txlt_sata_hba_inst,
"sata_dma_buf_setup: bp size %x != "
"bufsz %x\n", bp->b_bcount, bufsz);
#endif
if (dma_flags & DDI_DMA_WRITE) {
/*
* Write operation - copy data into
* an aligned temporary buffer. Buffer will be
* synced for device by ddi_dma_addr_bind_handle
*/
bcopy(bp->b_un.b_addr, spx->txlt_tmp_buf,
bp->b_bcount);
}
rval = ddi_dma_addr_bind_handle(
spx->txlt_buf_dma_handle,
NULL,
spx->txlt_tmp_buf,
bufsz, dma_flags, ddicallback, 0,
&spx->txlt_dma_cookie,
&spx->txlt_curwin_num_dma_cookies);
}
switch (rval) {
case DDI_DMA_PARTIAL_MAP:
SATADBG1(SATA_DBG_DMA_SETUP, spx->txlt_sata_hba_inst,
"sata_dma_buf_setup: DMA Partial Map\n", NULL);
/*
* Partial DMA mapping.
* Retrieve number of DMA windows for this request.
*/
if (ddi_dma_numwin(spx->txlt_buf_dma_handle,
&spx->txlt_num_dma_win) != DDI_SUCCESS) {
if (spx->txlt_tmp_buf != NULL) {
ddi_dma_mem_free(
&spx->txlt_tmp_buf_handle);
spx->txlt_tmp_buf = NULL;
}
(void) ddi_dma_unbind_handle(
spx->txlt_buf_dma_handle);
(void) ddi_dma_free_handle(
&spx->txlt_buf_dma_handle);
spx->txlt_buf_dma_handle = NULL;
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_dma_buf_setup: numwin failed\n"));
return (DDI_FAILURE);
}
SATADBG2(SATA_DBG_DMA_SETUP,
spx->txlt_sata_hba_inst,
"sata_dma_buf_setup: windows: %d, cookies: %d\n",
spx->txlt_num_dma_win,
spx->txlt_curwin_num_dma_cookies);
spx->txlt_cur_dma_win = 0;
break;
case DDI_DMA_MAPPED:
/* DMA fully mapped */
spx->txlt_num_dma_win = 1;
spx->txlt_cur_dma_win = 0;
SATADBG1(SATA_DBG_DMA_SETUP,
spx->txlt_sata_hba_inst,
"sata_dma_buf_setup: windows: 1 "
"cookies: %d\n", spx->txlt_curwin_num_dma_cookies);
break;
default:
/* DMA mapping failed */
if (spx->txlt_tmp_buf != NULL) {
ddi_dma_mem_free(
&spx->txlt_tmp_buf_handle);
spx->txlt_tmp_buf = NULL;
}
(void) ddi_dma_free_handle(&spx->txlt_buf_dma_handle);
spx->txlt_buf_dma_handle = NULL;
SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
"sata_dma_buf_setup: buf dma handle binding "
"failed %x\n", rval));
return (rval);
}
spx->txlt_curwin_processed_dma_cookies = 0;
spx->txlt_dma_cookie_list = NULL;
} else {
/*
* DMA setup is reused. Check if we need to process more
* cookies in current window, or to get next window, if any.
*/
ASSERT(spx->txlt_curwin_processed_dma_cookies <=
spx->txlt_curwin_num_dma_cookies);
if (spx->txlt_curwin_processed_dma_cookies ==
spx->txlt_curwin_num_dma_cookies) {
/*
* All cookies from current DMA window were processed.
* Get next DMA window.
*/
spx->txlt_cur_dma_win++;
if (spx->txlt_cur_dma_win < spx->txlt_num_dma_win) {
(void) ddi_dma_getwin(spx->txlt_buf_dma_handle,
spx->txlt_cur_dma_win, &offset, &size,
&spx->txlt_dma_cookie,
&spx->txlt_curwin_num_dma_cookies);
spx->txlt_curwin_processed_dma_cookies = 0;
} else {
/* No more windows! End of request! */
/* What to do? - panic for now */
ASSERT(spx->txlt_cur_dma_win >=
spx->txlt_num_dma_win);
spx->txlt_curwin_num_dma_cookies = 0;
spx->txlt_curwin_processed_dma_cookies = 0;
spx->txlt_sata_pkt->
satapkt_cmd.satacmd_num_dma_cookies = 0;
return (DDI_SUCCESS);
}
}
}
/* There better be at least one DMA cookie outstanding */
ASSERT((spx->txlt_curwin_num_dma_cookies -
spx->txlt_curwin_processed_dma_cookies) > 0);
if (spx->txlt_dma_cookie_list == &spx->txlt_dma_cookie) {
/* The default cookie slot was used in previous run */
ASSERT(spx->txlt_curwin_processed_dma_cookies == 0);
spx->txlt_dma_cookie_list = NULL;
spx->txlt_dma_cookie_list_len = 0;
}
if (spx->txlt_curwin_processed_dma_cookies == 0) {
/*
* Processing a new DMA window - set-up dma cookies list.
* We may reuse previously allocated cookie array if it is
* possible.
*/
if (spx->txlt_dma_cookie_list != NULL &&
spx->txlt_dma_cookie_list_len <
spx->txlt_curwin_num_dma_cookies) {
/*
* New DMA window contains more cookies than
* the previous one. We need larger cookie list - free
* the old one.
*/
(void) kmem_free(spx->txlt_dma_cookie_list,
spx->txlt_dma_cookie_list_len *
sizeof (ddi_dma_cookie_t));
spx->txlt_dma_cookie_list = NULL;
spx->txlt_dma_cookie_list_len = 0;
}
if (spx->txlt_dma_cookie_list == NULL) {
/*
* Calculate lesser of number of cookies in this
* DMA window and number of s/g entries.
*/
max_sg_len = cur_dma_attr->dma_attr_sgllen;
req_len = MIN(max_sg_len,
spx->txlt_curwin_num_dma_cookies);
/* Allocate new dma cookie array if necessary */
if (req_len == 1) {
/* Only one cookie - no need for a list */
spx->txlt_dma_cookie_list =
&spx->txlt_dma_cookie;
spx->txlt_dma_cookie_list_len = 1;
} else {
/*
* More than one cookie - try to allocate space.
*/
spx->txlt_dma_cookie_list = kmem_zalloc(
sizeof (ddi_dma_cookie_t) * req_len,
callback == NULL_FUNC ? KM_NOSLEEP :
KM_SLEEP);
if (spx->txlt_dma_cookie_list == NULL) {
SATADBG1(SATA_DBG_DMA_SETUP,
spx->txlt_sata_hba_inst,
"sata_dma_buf_setup: cookie list "
"allocation failed\n", NULL);
/*
* We could not allocate space for
* neccessary number of dma cookies in
* this window, so we fail this request.
* Next invocation would try again to
* allocate space for cookie list.
* Note:Packet residue was not modified.
*/
return (DDI_DMA_NORESOURCES);
} else {
spx->txlt_dma_cookie_list_len = req_len;
}
}
}
/*
* Fetch DMA cookies into cookie list in sata_pkt_txlate.
* First cookie was already fetched.
*/
*(&spx->txlt_dma_cookie_list[0]) = spx->txlt_dma_cookie;
cur_txfer_len =
(uint64_t)spx->txlt_dma_cookie_list[0].dmac_size;
spx->txlt_sata_pkt->satapkt_cmd.satacmd_num_dma_cookies = 1;
spx->txlt_curwin_processed_dma_cookies++;
for (i = 1; (i < spx->txlt_dma_cookie_list_len) &&
(i < spx->txlt_curwin_num_dma_cookies); i++) {
ddi_dma_nextcookie(spx->txlt_buf_dma_handle,
&spx->txlt_dma_cookie_list[i]);
cur_txfer_len +=
(uint64_t)spx->txlt_dma_cookie_list[i].dmac_size;
spx->txlt_curwin_processed_dma_cookies++;
spx->txlt_sata_pkt->
satapkt_cmd.satacmd_num_dma_cookies += 1;
}
} else {
SATADBG2(SATA_DBG_DMA_SETUP, spx->txlt_sata_hba_inst,
"sata_dma_buf_setup: sliding within DMA window, "
"cur cookie %d, total cookies %d\n",
spx->txlt_curwin_processed_dma_cookies,
spx->txlt_curwin_num_dma_cookies);
/*
* Not all cookies from the current dma window were used because
* of s/g limitation.
* There is no need to re-size the list - it was set at
* optimal size, or only default entry is used (s/g = 1).
*/
if (spx->txlt_dma_cookie_list == NULL) {
spx->txlt_dma_cookie_list = &spx->txlt_dma_cookie;
spx->txlt_dma_cookie_list_len = 1;
}
/*
* Since we are processing remaining cookies in a DMA window,
* there may be less of them than the number of entries in the
* current dma cookie list.
*/
req_len = MIN(spx->txlt_dma_cookie_list_len,
(spx->txlt_curwin_num_dma_cookies -
spx->txlt_curwin_processed_dma_cookies));
/* Fetch the next batch of cookies */
for (i = 0, cur_txfer_len = 0; i < req_len; i++) {
ddi_dma_nextcookie(spx->txlt_buf_dma_handle,
&spx->txlt_dma_cookie_list[i]);
cur_txfer_len +=
(uint64_t)spx->txlt_dma_cookie_list[i].dmac_size;
spx->txlt_sata_pkt->
satapkt_cmd.satacmd_num_dma_cookies++;
spx->txlt_curwin_processed_dma_cookies++;
}
}
ASSERT(spx->txlt_sata_pkt->satapkt_cmd.satacmd_num_dma_cookies > 0);
/* Point sata_cmd to the cookie list */
spx->txlt_sata_pkt->satapkt_cmd.satacmd_dma_cookie_list =
&spx->txlt_dma_cookie_list[0];
/* Remember number of DMA cookies passed in sata packet */
spx->txlt_num_dma_cookies =
spx->txlt_sata_pkt->satapkt_cmd.satacmd_num_dma_cookies;
ASSERT(cur_txfer_len != 0);
if (cur_txfer_len <= bp->b_bcount)
spx->txlt_total_residue -= cur_txfer_len;
else {
/*
* Temporary DMA buffer has been padded by
* ddi_dma_mem_alloc()!
* This requires special handling, because DMA cookies are
* based on the temporary buffer size, not the b_bcount,
* and we have extra bytes to transfer - but the packet
* residue has to stay correct because we will copy only
* the requested number of bytes.
*/
spx->txlt_total_residue -= bp->b_bcount;
}
return (DDI_SUCCESS);
}
/*
* Common routine for releasing DMA resources
*/
static void
sata_common_free_dma_rsrcs(sata_pkt_txlate_t *spx)
{
if (spx->txlt_buf_dma_handle != NULL) {
if (spx->txlt_tmp_buf != NULL) {
/*
* Intermediate DMA buffer was allocated.
* Free allocated buffer and associated access handle.
*/
ddi_dma_mem_free(&spx->txlt_tmp_buf_handle);
spx->txlt_tmp_buf = NULL;
}
/*
* Free DMA resources - cookies and handles
*/
/* ASSERT(spx->txlt_dma_cookie_list != NULL); */
if (spx->txlt_dma_cookie_list != NULL) {
if (spx->txlt_dma_cookie_list !=
&spx->txlt_dma_cookie) {
(void) kmem_free(spx->txlt_dma_cookie_list,
spx->txlt_dma_cookie_list_len *
sizeof (ddi_dma_cookie_t));
spx->txlt_dma_cookie_list = NULL;
}
}
(void) ddi_dma_unbind_handle(spx->txlt_buf_dma_handle);
(void) ddi_dma_free_handle(&spx->txlt_buf_dma_handle);
spx->txlt_buf_dma_handle = NULL;
}
}
/*
* Free DMA resources
* Used by the HBA driver to release DMA resources that it does not use.
*
* Returns Void
*/
void
sata_free_dma_resources(sata_pkt_t *sata_pkt)
{
sata_pkt_txlate_t *spx;
if (sata_pkt == NULL)
return;
spx = (sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
sata_common_free_dma_rsrcs(spx);
}
/*
* Fetch Device Identify data.
* Send DEVICE IDENTIFY or IDENTIFY PACKET DEVICE (depending on a device type)
* command to a device and get the device identify data.
* The device_info structure has to be set to device type (for selecting proper
* device identify command).
*
* Returns:
* SATA_SUCCESS if cmd succeeded
* SATA_RETRY if cmd was rejected and could be retried,
* SATA_FAILURE if cmd failed and should not be retried (port error)
*
* Cannot be called in an interrupt context.
*/
static int
sata_fetch_device_identify_data(sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo)
{
struct buf *bp;
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int rval;
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (SATA_RETRY); /* may retry later */
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/*
* Allocate buffer for Identify Data return data
*/
bp = sata_alloc_local_buffer(spx, sizeof (sata_id_t));
if (bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_fetch_device_identify_data: "
"cannot allocate buffer for ID"));
return (SATA_RETRY); /* may retry later */
}
/* Fill sata_pkt */
sdinfo->satadrv_state = SATA_STATE_PROBING;
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback */
spkt->satapkt_comp = NULL;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_bp = bp;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
/* Build Identify Device cmd in the sata_pkt */
scmd->satacmd_addr_type = 0; /* N/A */
scmd->satacmd_sec_count_lsb = 0; /* N/A */
scmd->satacmd_lba_low_lsb = 0; /* N/A */
scmd->satacmd_lba_mid_lsb = 0; /* N/A */
scmd->satacmd_lba_high_lsb = 0; /* N/A */
scmd->satacmd_features_reg = 0; /* N/A */
scmd->satacmd_device_reg = 0; /* Always device 0 */
if (sdinfo->satadrv_type & SATA_DTYPE_ATAPI) {
/* Identify Packet Device cmd */
scmd->satacmd_cmd_reg = SATAC_ID_PACKET_DEVICE;
} else {
/* Identify Device cmd - mandatory for all other devices */
scmd->satacmd_cmd_reg = SATAC_ID_DEVICE;
}
/* Send pkt to SATA HBA driver */
rval = (*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt);
#ifdef SATA_INJECT_FAULTS
sata_inject_pkt_fault(spkt, &rval, sata_fault_type);
#endif
if (rval == SATA_TRAN_ACCEPTED &&
spkt->satapkt_reason == SATA_PKT_COMPLETED) {
if (spx->txlt_buf_dma_handle != NULL) {
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORKERNEL);
ASSERT(rval == DDI_SUCCESS);
}
if ((((sata_id_t *)(bp->b_un.b_addr))->ai_config &
SATA_INCOMPLETE_DATA) == SATA_INCOMPLETE_DATA) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"SATA disk device at port %d - "
"partial Identify Data",
sdinfo->satadrv_addr.cport));
rval = SATA_RETRY; /* may retry later */
goto fail;
}
/* Update sata_drive_info */
bcopy(bp->b_un.b_addr, &sdinfo->satadrv_id,
sizeof (sata_id_t));
sdinfo->satadrv_features_support = 0;
if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
/*
* Retrieve capacity (disks only) and addressing mode
*/
sdinfo->satadrv_capacity = sata_check_capacity(sdinfo);
} else {
/*
* For ATAPI devices one would have to issue
* Get Capacity cmd for media capacity. Not here.
*/
sdinfo->satadrv_capacity = 0;
/*
* Check what cdb length is supported
*/
if ((sdinfo->satadrv_id.ai_config &
SATA_ATAPI_ID_PKT_SZ) == SATA_ATAPI_ID_PKT_16B)
sdinfo->satadrv_atapi_cdb_len = 16;
else
sdinfo->satadrv_atapi_cdb_len = 12;
}
/* Setup supported features flags */
if (sdinfo->satadrv_id.ai_cap & SATA_DMA_SUPPORT)
sdinfo->satadrv_features_support |= SATA_DEV_F_DMA;
/* Check for SATA GEN and NCQ support */
if (sdinfo->satadrv_id.ai_satacap != 0 &&
sdinfo->satadrv_id.ai_satacap != 0xffff) {
/* SATA compliance */
if (sdinfo->satadrv_id.ai_satacap & SATA_NCQ)
sdinfo->satadrv_features_support |=
SATA_DEV_F_NCQ;
if (sdinfo->satadrv_id.ai_satacap &
(SATA_1_SPEED | SATA_2_SPEED)) {
if (sdinfo->satadrv_id.ai_satacap &
SATA_2_SPEED)
sdinfo->satadrv_features_support |=
SATA_DEV_F_SATA2;
if (sdinfo->satadrv_id.ai_satacap &
SATA_1_SPEED)
sdinfo->satadrv_features_support |=
SATA_DEV_F_SATA1;
} else {
sdinfo->satadrv_features_support |=
SATA_DEV_F_SATA1;
}
}
if ((sdinfo->satadrv_id.ai_cmdset83 & SATA_RW_DMA_QUEUED_CMD) &&
(sdinfo->satadrv_id.ai_features86 & SATA_RW_DMA_QUEUED_CMD))
sdinfo->satadrv_features_support |= SATA_DEV_F_TCQ;
sdinfo->satadrv_queue_depth = sdinfo->satadrv_id.ai_qdepth;
if ((sdinfo->satadrv_features_support & SATA_DEV_F_NCQ) ||
(sdinfo->satadrv_features_support & SATA_DEV_F_TCQ)) {
++sdinfo->satadrv_queue_depth;
/* Adjust according to controller capabilities */
sdinfo->satadrv_max_queue_depth = MIN(
sdinfo->satadrv_queue_depth,
SATA_QDEPTH(sata_hba_inst));
/* Adjust according to global queue depth limit */
sdinfo->satadrv_max_queue_depth = MIN(
sdinfo->satadrv_max_queue_depth,
sata_current_max_qdepth);
if (sdinfo->satadrv_max_queue_depth == 0)
sdinfo->satadrv_max_queue_depth = 1;
} else
sdinfo->satadrv_max_queue_depth = 1;
rval = SATA_SUCCESS;
} else {
/*
* Woops, no Identify Data.
*/
if (rval == SATA_TRAN_BUSY || rval == SATA_TRAN_QUEUE_FULL) {
rval = SATA_RETRY; /* may retry later */
} else if (rval == SATA_TRAN_ACCEPTED) {
if (spkt->satapkt_reason == SATA_PKT_DEV_ERROR ||
spkt->satapkt_reason == SATA_PKT_ABORTED ||
spkt->satapkt_reason == SATA_PKT_TIMEOUT ||
spkt->satapkt_reason == SATA_PKT_RESET)
rval = SATA_RETRY; /* may retry later */
else
rval = SATA_FAILURE;
} else {
rval = SATA_FAILURE;
}
}
fail:
/* Free allocated resources */
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Some devices may not come-up with default DMA mode (UDMA or MWDMA).
* UDMA mode is checked first, followed by MWDMA mode.
* set correctly, so this function is setting it to the highest supported level.
* Older SATA spec required that the device supports at least DMA 4 mode and
* UDMA mode is selected. It is not mentioned in SerialATA 2.6, so this
* restriction has been removed.
*
* Returns SATA_SUCCESS if proper DMA mode is selected or no DMA is supported.
* Returns SATA_FAILURE if proper DMA mode could not be selected.
*
* NOTE: This function should be called only if DMA mode is supported.
*/
static int
sata_set_dma_mode(sata_hba_inst_t *sata_hba_inst, sata_drive_info_t *sdinfo)
{
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int mode;
uint8_t subcmd;
int rval = SATA_SUCCESS;
ASSERT(sdinfo != NULL);
ASSERT(sata_hba_inst != NULL);
if ((sdinfo->satadrv_id.ai_validinfo & SATA_VALIDINFO_88) != 0 &&
(sdinfo->satadrv_id.ai_ultradma & SATA_UDMA_SUP_MASK) != 0) {
/* Find highest Ultra DMA mode supported */
for (mode = 6; mode >= 0; --mode) {
if (sdinfo->satadrv_id.ai_ultradma & (1 << mode))
break;
}
#if 0
/* Left for historical reasons */
/*
* Some initial version of SATA spec indicated that at least
* UDMA mode 4 has to be supported. It is not mentioned in
* SerialATA 2.6, so this restriction is removed.
*/
if (mode < 4)
return (SATA_FAILURE);
#endif
/*
* We're still going to set DMA mode whatever is selected
* by default
*
* We saw an old maxtor sata drive will select Ultra DMA and
* Multi-Word DMA simultaneouly by default, which is going
* to cause DMA command timed out, so we need to select DMA
* mode even when it's already done by default
*/
subcmd = SATAC_TRANSFER_MODE_ULTRA_DMA;
} else if ((sdinfo->satadrv_id.ai_dworddma & SATA_MDMA_SUP_MASK) != 0) {
/* Find highest MultiWord DMA mode supported */
for (mode = 2; mode >= 0; --mode) {
if (sdinfo->satadrv_id.ai_dworddma & (1 << mode))
break;
}
/*
* We're still going to set DMA mode whatever is selected
* by default
*
* We saw an old maxtor sata drive will select Ultra DMA and
* Multi-Word DMA simultaneouly by default, which is going
* to cause DMA command timed out, so we need to select DMA
* mode even when it's already done by default
*/
subcmd = SATAC_TRANSFER_MODE_MULTI_WORD_DMA;
} else
return (SATA_SUCCESS);
/*
* Set DMA mode via SET FEATURES COMMAND.
* Prepare packet for SET FEATURES COMMAND.
*/
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_set_dma_mode: could not set DMA mode %", mode));
rval = SATA_FAILURE;
goto done;
}
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
/* Synchronous mode, no callback, interrupts */
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
spkt->satapkt_comp = NULL;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_NODATA_XFER;
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
scmd->satacmd_addr_type = 0;
scmd->satacmd_device_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
scmd->satacmd_features_reg = SATAC_SF_TRANSFER_MODE;
scmd->satacmd_sec_count_lsb = subcmd | mode;
/* Transfer command to HBA */
if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst),
spkt) != SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
/* Pkt execution failed */
rval = SATA_FAILURE;
}
done:
/* Free allocated resources */
if (spkt != NULL)
sata_pkt_free(spx);
(void) kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Set device caching mode.
* One of the following operations should be specified:
* SATAC_SF_ENABLE_READ_AHEAD
* SATAC_SF_DISABLE_READ_AHEAD
* SATAC_SF_ENABLE_WRITE_CACHE
* SATAC_SF_DISABLE_WRITE_CACHE
*
* If operation fails, system log messgage is emitted.
* Returns SATA_SUCCESS when the operation succeeds, SATA_RETRY if
* command was sent but did not succeed, and SATA_FAILURE otherwise.
*/
static int
sata_set_cache_mode(sata_hba_inst_t *sata_hba_inst, sata_drive_info_t *sdinfo,
int cache_op)
{
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int rval = SATA_SUCCESS;
int hba_rval;
char *infop;
ASSERT(sdinfo != NULL);
ASSERT(sata_hba_inst != NULL);
ASSERT(cache_op == SATAC_SF_ENABLE_READ_AHEAD ||
cache_op == SATAC_SF_DISABLE_READ_AHEAD ||
cache_op == SATAC_SF_ENABLE_WRITE_CACHE ||
cache_op == SATAC_SF_DISABLE_WRITE_CACHE);
/* Prepare packet for SET FEATURES COMMAND */
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
rval = SATA_FAILURE;
goto failure;
}
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
/* Synchronous mode, no callback, interrupts */
spkt->satapkt_op_mode =
SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
spkt->satapkt_comp = NULL;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_NODATA_XFER;
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
scmd->satacmd_addr_type = 0;
scmd->satacmd_device_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
scmd->satacmd_features_reg = cache_op;
/* Transfer command to HBA */
hba_rval = (*SATA_START_FUNC(sata_hba_inst))(
SATA_DIP(sata_hba_inst), spkt);
#ifdef SATA_INJECT_FAULTS
sata_inject_pkt_fault(spkt, &rval, sata_fault_type);
#endif
if ((hba_rval != SATA_TRAN_ACCEPTED) ||
(spkt->satapkt_reason != SATA_PKT_COMPLETED)) {
/* Pkt execution failed */
switch (cache_op) {
case SATAC_SF_ENABLE_READ_AHEAD:
infop = "enabling read ahead failed";
break;
case SATAC_SF_DISABLE_READ_AHEAD:
infop = "disabling read ahead failed";
break;
case SATAC_SF_ENABLE_WRITE_CACHE:
infop = "enabling write cache failed";
break;
case SATAC_SF_DISABLE_WRITE_CACHE:
infop = "disabling write cache failed";
break;
}
SATA_LOG_D((sata_hba_inst, CE_WARN, "%s", infop));
rval = SATA_RETRY;
}
failure:
/* Free allocated resources */
if (spkt != NULL)
sata_pkt_free(spx);
(void) kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Set Removable Media Status Notification (enable/disable)
* state == 0 , disable
* state != 0 , enable
*
* If operation fails, system log messgage is emitted.
* Returns SATA_SUCCESS when the operation succeeds, SATA_FAILURE otherwise.
*/
static int
sata_set_rmsn(sata_hba_inst_t *sata_hba_inst, sata_drive_info_t *sdinfo,
int state)
{
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int rval = SATA_SUCCESS;
char *infop;
ASSERT(sdinfo != NULL);
ASSERT(sata_hba_inst != NULL);
/* Prepare packet for SET FEATURES COMMAND */
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
rval = SATA_FAILURE;
goto failure;
}
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
/* Synchronous mode, no callback, interrupts */
spkt->satapkt_op_mode =
SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
spkt->satapkt_comp = NULL;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_NODATA_XFER;
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
scmd->satacmd_addr_type = 0;
scmd->satacmd_device_reg = 0;
scmd->satacmd_status_reg = 0;
scmd->satacmd_error_reg = 0;
scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
if (state == 0)
scmd->satacmd_features_reg = SATAC_SF_DISABLE_RMSN;
else
scmd->satacmd_features_reg = SATAC_SF_ENABLE_RMSN;
/* Transfer command to HBA */
if (((*SATA_START_FUNC(sata_hba_inst))(
SATA_DIP(sata_hba_inst), spkt) != SATA_TRAN_ACCEPTED) ||
(spkt->satapkt_reason != SATA_PKT_COMPLETED)) {
/* Pkt execution failed */
if (state == 0)
infop = "disabling Removable Media Status "
"Notification failed";
else
infop = "enabling Removable Media Status "
"Notification failed";
SATA_LOG_D((sata_hba_inst, CE_WARN, "%s", infop));
rval = SATA_FAILURE;
}
failure:
/* Free allocated resources */
if (spkt != NULL)
sata_pkt_free(spx);
(void) kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Update port SCR block
*/
static void
sata_update_port_scr(sata_port_scr_t *port_scr, sata_device_t *device)
{
port_scr->sstatus = device->satadev_scr.sstatus;
port_scr->serror = device->satadev_scr.serror;
port_scr->scontrol = device->satadev_scr.scontrol;
port_scr->sactive = device->satadev_scr.sactive;
port_scr->snotific = device->satadev_scr.snotific;
}
/*
* Update state and copy port ss* values from passed sata_device structure.
* sata_address is validated - if not valid, nothing is changed in sata_scsi
* configuration struct.
*
* SATA_PSTATE_SHUTDOWN in port state is not reset to 0 by this function
* regardless of the state in device argument.
*
* Port mutex should be held while calling this function.
*/
static void
sata_update_port_info(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
ASSERT(mutex_owned(&SATA_CPORT_MUTEX(sata_hba_inst,
sata_device->satadev_addr.cport)));
if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT ||
sata_device->satadev_addr.qual == SATA_ADDR_DCPORT) {
sata_cport_info_t *cportinfo;
if (SATA_NUM_CPORTS(sata_hba_inst) <=
sata_device->satadev_addr.cport)
return;
cportinfo = SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport);
sata_update_port_scr(&cportinfo->cport_scr, sata_device);
/* Preserve SATA_PSTATE_SHUTDOWN flag */
cportinfo->cport_state &= ~(SATA_PSTATE_PWRON |
SATA_PSTATE_PWROFF | SATA_PSTATE_FAILED);
cportinfo->cport_state |=
sata_device->satadev_state & SATA_PSTATE_VALID;
} else {
sata_pmport_info_t *pmportinfo;
if ((sata_device->satadev_addr.qual != SATA_ADDR_PMPORT) ||
(sata_device->satadev_addr.qual != SATA_ADDR_DPMPORT) ||
SATA_NUM_PMPORTS(sata_hba_inst,
sata_device->satadev_addr.cport) <
sata_device->satadev_addr.pmport)
return;
pmportinfo = SATA_PMPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport);
sata_update_port_scr(&pmportinfo->pmport_scr, sata_device);
/* Preserve SATA_PSTATE_SHUTDOWN flag */
pmportinfo->pmport_state &=
~(SATA_PSTATE_PWRON | SATA_PSTATE_PWROFF |
SATA_PSTATE_FAILED);
pmportinfo->pmport_state |=
sata_device->satadev_state & SATA_PSTATE_VALID;
}
}
/*
* Extract SATA port specification from an IOCTL argument.
*
* This function return the port the user land send us as is, unless it
* cannot retrieve port spec, then -1 is returned.
*
* Note: Only cport - no port multiplier port.
*/
static int32_t
sata_get_port_num(sata_hba_inst_t *sata_hba_inst, struct devctl_iocdata *dcp)
{
int32_t port;
/* Extract port number from nvpair in dca structure */
if (nvlist_lookup_int32(ndi_dc_get_ap_data(dcp), "port", &port) != 0) {
SATA_LOG_D((sata_hba_inst, CE_NOTE,
"sata_get_port_num: invalid port spec 0x%x in ioctl",
port));
port = -1;
}
return (port);
}
/*
* Get dev_info_t pointer to the device node pointed to by port argument.
* NOTE: target argument is a value used in ioctls to identify
* the AP - it is not a sata_address.
* It is a combination of cport, pmport and address qualifier, encodded same
* way as a scsi target number.
* At this moment it carries only cport number.
*
* No PMult hotplug support.
*
* Returns dev_info_t pointer if target device was found, NULL otherwise.
*/
static dev_info_t *
sata_get_target_dip(dev_info_t *dip, int32_t port)
{
dev_info_t *cdip = NULL;
int target, tgt;
int ncport;
int circ;
ncport = port & SATA_CFGA_CPORT_MASK;
target = SATA_TO_SCSI_TARGET(ncport, 0, SATA_ADDR_DCPORT);
ndi_devi_enter(dip, &circ);
for (cdip = ddi_get_child(dip); cdip != NULL; ) {
dev_info_t *next = ddi_get_next_sibling(cdip);
tgt = ddi_prop_get_int(DDI_DEV_T_ANY, cdip,
DDI_PROP_DONTPASS, "target", -1);
if (tgt == -1) {
/*
* This is actually an error condition, but not
* a fatal one. Just continue the search.
*/
cdip = next;
continue;
}
if (tgt == target)
break;
cdip = next;
}
ndi_devi_exit(dip, circ);
return (cdip);
}
/*
* Get dev_info_t pointer to the device node pointed to by port argument.
* NOTE: target argument is a value used in ioctls to identify
* the AP - it is not a sata_address.
* It is a combination of cport, pmport and address qualifier, encoded same
* way as a scsi target number.
* At this moment it carries only cport number.
*
* No PMult hotplug support.
*
* Returns dev_info_t pointer if target device was found, NULL otherwise.
*/
static dev_info_t *
sata_get_scsi_target_dip(dev_info_t *dip, sata_address_t *saddr)
{
dev_info_t *cdip = NULL;
int target, tgt;
int circ;
target = SATA_TO_SCSI_TARGET(saddr->cport, saddr->pmport, saddr->qual);
ndi_devi_enter(dip, &circ);
for (cdip = ddi_get_child(dip); cdip != NULL; ) {
dev_info_t *next = ddi_get_next_sibling(cdip);
tgt = ddi_prop_get_int(DDI_DEV_T_ANY, cdip,
DDI_PROP_DONTPASS, "target", -1);
if (tgt == -1) {
/*
* This is actually an error condition, but not
* a fatal one. Just continue the search.
*/
cdip = next;
continue;
}
if (tgt == target)
break;
cdip = next;
}
ndi_devi_exit(dip, circ);
return (cdip);
}
/*
* Process sata port disconnect request.
* Normally, cfgadm sata plugin will try to offline (unconfigure) the device
* before this request. Nevertheless, if a device is still configured,
* we need to attempt to offline and unconfigure device.
* Regardless of the unconfigure operation results the port is marked as
* deactivated and no access to the attached device is possible.
* If the target node remains because unconfigure operation failed, its state
* will be set to DEVICE_REMOVED, preventing it to be used again when a device
* is inserted/re-inserted. The event daemon will repeatedly try to unconfigure
* the device and remove old target node.
*
* This function invokes sata_hba_inst->satahba_tran->
* sata_tran_hotplug_ops->sata_tran_port_deactivate().
* If successful, the device structure (if any) attached to the specified port
* is removed and state of the port marked appropriately.
* Failure of the port_deactivate may keep port in the physically active state,
* or may fail the port.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_disconnect(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
sata_drive_info_t *sdinfo = NULL;
sata_cport_info_t *cportinfo = NULL;
sata_pmport_info_t *pmportinfo = NULL;
sata_pmult_info_t *pmultinfo = NULL;
dev_info_t *tdip;
int cport, pmport, qual;
int rval = SATA_SUCCESS;
int rv = 0;
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
qual = sata_device->satadev_addr.qual;
ASSERT(qual == SATA_ADDR_CPORT || qual == SATA_ADDR_PMPORT);
/*
* DEVCTL_AP_DISCONNECT invokes sata_hba_inst->satahba_tran->
* sata_tran_hotplug_ops->sata_tran_port_deactivate().
* Do the sanity check.
*/
if (SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst) == NULL) {
/* No physical port deactivation supported. */
return (EINVAL);
}
/* Check the current state of the port */
rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (rval != SATA_SUCCESS ||
(sata_device->satadev_state & SATA_PSTATE_FAILED) != 0) {
/* Device port status is unknown or it is in failed state */
if (qual == SATA_ADDR_PMPORT) {
SATA_PMPORT_STATE(sata_hba_inst, cport, pmport) =
SATA_PSTATE_FAILED;
SATADBG1(SATA_DBG_IOCTL_IF, sata_hba_inst,
"sata_hba_ioctl: connect: failed to deactivate "
"SATA port %d", cport);
} else {
SATA_CPORT_STATE(sata_hba_inst, cport) =
SATA_PSTATE_FAILED;
SATADBG2(SATA_DBG_IOCTL_IF, sata_hba_inst,
"sata_hba_ioctl: connect: failed to deactivate "
"SATA port %d:%d", cport, pmport);
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
return (EIO);
}
/*
* Set port's dev_state to not ready - this will disable
* an access to a potentially attached device.
*/
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
if (qual == SATA_ADDR_PMPORT) {
pmportinfo = SATA_PMPORT_INFO(sata_hba_inst, cport, pmport);
if (pmportinfo->pmport_dev_type != SATA_DTYPE_NONE) {
sdinfo = pmportinfo->pmport_sata_drive;
ASSERT(sdinfo != NULL);
}
pmportinfo->pmport_state &= ~SATA_STATE_READY;
} else {
/* Assuming cport */
if (cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
if (cportinfo->cport_dev_type == SATA_DTYPE_PMULT) {
pmultinfo =
cportinfo->cport_devp.cport_sata_pmult;
ASSERT(pmultinfo != NULL);
} else {
sdinfo = cportinfo->cport_devp.cport_sata_drive;
}
}
cportinfo->cport_state &= ~SATA_STATE_READY;
}
if (sdinfo != NULL) {
if ((sdinfo->satadrv_type & (SATA_VALID_DEV_TYPE)) != 0) {
/*
* If a target node exists, try to offline
* a device and remove target node.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
/* We are addressing attached device, not a port */
sata_device->satadev_addr.qual =
sdinfo->satadrv_addr.qual;
tdip = sata_get_scsi_target_dip(SATA_DIP(sata_hba_inst),
&sata_device->satadev_addr);
if (tdip != NULL && ndi_devi_offline(tdip,
NDI_DEVI_REMOVE) != NDI_SUCCESS) {
/*
* Problem
* The target node remained attached.
* This happens when the device file was open
* or a node was waiting for resources.
* Cannot do anything about it.
*/
if (qual == SATA_ADDR_CPORT) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: disconnect: could "
"not unconfigure device before "
"disconnecting the SATA port %d",
cport));
} else {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: disconnect: could "
"not unconfigure device before "
"disconnecting the SATA port %d:%d",
cport, pmport));
}
/*
* Set DEVICE REMOVED state in the target
* node. It will prevent access to the device
* even when a new device is attached, until
* the old target node is released, removed and
* recreated for a new device.
*/
sata_set_device_removed(tdip);
/*
* Instruct event daemon to try the target
* node cleanup later.
*/
sata_set_target_node_cleanup(
sata_hba_inst, &sata_device->satadev_addr);
}
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
}
/* Remove and release sata_drive info structure. */
if (pmportinfo != NULL) {
SATA_PMPORT_DRV_INFO(sata_hba_inst, cport, pmport) =
NULL;
pmportinfo->pmport_dev_type = SATA_DTYPE_NONE;
} else {
SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
cportinfo->cport_dev_type = SATA_DTYPE_NONE;
}
(void) kmem_free((void *)sdinfo, sizeof (sata_drive_info_t));
}
#if 0
else if (pmultinfo != NULL) {
/*
* Port Multiplier itself needs special handling.
* All device ports need to be processed here!
*/
}
#endif
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
/* Just ask HBA driver to deactivate port */
/* sata_device->satadev_addr.qual = SATA_ADDR_DCPORT; */
rval = (*SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device);
/*
* Generate sysevent - EC_DR / ESC_DR_AP_STATE_CHANGE
* without the hint (to force listener to investivate the state).
*/
sata_gen_sysevent(sata_hba_inst, &sata_device->satadev_addr,
SE_NO_HINT);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (rval != SATA_SUCCESS) {
/*
* Port deactivation failure - do not
* change port state unless the state
* returned by HBA indicates a port failure.
* NOTE: device structures were released, so devices now are
* invisible! Port reset is needed to re-enumerate devices.
*/
if (sata_device->satadev_state & SATA_PSTATE_FAILED) {
if (pmportinfo != NULL)
pmportinfo->pmport_state = SATA_PSTATE_FAILED;
else
cportinfo->cport_state = SATA_PSTATE_FAILED;
rv = EIO;
}
} else {
/*
* Deactivation succeded. From now on the sata framework
* will not care what is happening to the device, until
* the port is activated again.
*/
cportinfo->cport_state |= SATA_PSTATE_SHUTDOWN;
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
return (rv);
}
/*
* Process sata port connect request
* The sata cfgadm pluging will invoke this operation only if port was found
* in the disconnect state (failed state is also treated as the disconnected
* state).
* DEVCTL_AP_CONNECT would invoke sata_hba_inst->satahba_tran->
* sata_tran_hotplug_ops->sata_tran_port_activate().
* If successful and a device is found attached to the port,
* the initialization sequence is executed to attach a device structure to
* a port structure. The state of the port and a device would be set
* appropriately.
* The device is not set in configured state (system-wise) by this operation.
*
* Note, that activating the port may generate link events,
* so it is important that following processing and the
* event processing does not interfere with each other!
*
* This operation may remove port failed state and will
* try to make port active and in good standing.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_connect(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
int cport, pmport, qual;
int rv = 0;
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
qual = sata_device->satadev_addr.qual;
ASSERT(qual == SATA_ADDR_CPORT || qual == SATA_ADDR_PMPORT);
/*
* DEVCTL_AP_CONNECT would invoke sata_hba_inst->
* satahba_tran->sata_tran_hotplug_ops->sata_tran_port_activate().
* Perform sanity check now.
*/
if (SATA_PORT_ACTIVATE_FUNC(sata_hba_inst) == NULL) {
/* No physical port activation supported. */
return (EINVAL);
}
/* Just ask HBA driver to activate port */
if ((*SATA_PORT_ACTIVATE_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device) != SATA_SUCCESS) {
/*
* Port activation failure.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (sata_device->satadev_state & SATA_PSTATE_FAILED) {
if (qual == SATA_ADDR_DCPORT) {
SATA_CPORT_STATE(sata_hba_inst, cport) =
SATA_PSTATE_FAILED;
SATADBG1(SATA_DBG_IOCTL_IF, sata_hba_inst,
"sata_hba_ioctl: connect: failed to "
"activate SATA port %d", cport);
} else { /* port multiplier device port */
SATA_PMPORT_STATE(sata_hba_inst, cport,
pmport) = SATA_PSTATE_FAILED;
SATADBG2(SATA_DBG_IOCTL_IF, sata_hba_inst,
"sata_hba_ioctl: connect: failed to "
"activate SATA port %d:%d", cport, pmport);
}
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
SATADBG2(SATA_DBG_IOCTL_IF, sata_hba_inst,
"sata_hba_ioctl: connect: failed to activate SATA "
"port %d:%d", cport, pmport);
return (EIO);
}
/* Virgin port state - will be updated by the port re-probe. */
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if (qual == SATA_ADDR_CPORT)
SATA_CPORT_STATE(sata_hba_inst, cport) = 0;
else /* port multiplier device port */
SATA_PMPORT_STATE(sata_hba_inst, cport, pmport) = 0;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
/*
* Probe the port to find its state and attached device.
*/
if (sata_reprobe_port(sata_hba_inst, sata_device,
SATA_DEV_IDENTIFY_RETRY) == SATA_FAILURE)
rv = EIO;
/*
* Generate sysevent - EC_DR / ESC_DR_AP_STATE_CHANGE
* without the hint
*/
sata_gen_sysevent(sata_hba_inst, &sata_device->satadev_addr,
SE_NO_HINT);
/*
* If there is a device attached to the port, emit
* a message.
*/
if (sata_device->satadev_type != SATA_DTYPE_NONE) {
if (qual == SATA_ADDR_CPORT) {
sata_log(sata_hba_inst, CE_WARN,
"SATA device detected at port %d", cport);
if (sata_device->satadev_type == SATA_DTYPE_UNKNOWN) {
/*
* A device was not successfully identified
*/
sata_log(sata_hba_inst, CE_WARN,
"Could not identify SATA "
"device at port %d", cport);
}
} else { /* port multiplier device port */
sata_log(sata_hba_inst, CE_WARN,
"SATA device detected at port %d:%d",
cport, pmport);
if (sata_device->satadev_type == SATA_DTYPE_UNKNOWN) {
/*
* A device was not successfully identified
*/
sata_log(sata_hba_inst, CE_WARN,
"Could not identify SATA "
"device at port %d:%d", cport, pmport);
}
}
}
return (rv);
}
/*
* Process sata device unconfigure request.
* The unconfigure operation uses generic nexus operation to
* offline a device. It leaves a target device node attached.
* and obviously sata_drive_info attached as well, because
* from the hardware point of view nothing has changed.
*/
static int
sata_ioctl_unconfigure(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
int rv = 0;
dev_info_t *tdip;
/* We are addressing attached device, not a port */
if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT)
sata_device->satadev_addr.qual = SATA_ADDR_DCPORT;
else if (sata_device->satadev_addr.qual == SATA_ADDR_PMPORT)
sata_device->satadev_addr.qual = SATA_ADDR_DPMPORT;
if ((tdip = sata_get_scsi_target_dip(SATA_DIP(sata_hba_inst),
&sata_device->satadev_addr)) != NULL) {
if (ndi_devi_offline(tdip, NDI_UNCONFIG) != NDI_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: unconfigure: "
"failed to unconfigure device at SATA port %d:%d",
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport));
rv = EIO;
}
/*
* The target node devi_state should be marked with
* DEVI_DEVICE_OFFLINE by ndi_devi_offline().
* This would be the indication for cfgadm that
* the AP node occupant state is 'unconfigured'.
*/
} else {
/*
* This would indicate a failure on the part of cfgadm
* to detect correct state of the node prior to this
* call - one cannot unconfigure non-existing device.
*/
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: unconfigure: "
"attempt to unconfigure non-existing device "
"at SATA port %d:%d",
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport));
rv = ENXIO;
}
return (rv);
}
/*
* Process sata device configure request
* If port is in a failed state, operation is aborted - one has to use
* an explicit connect or port activate request to try to get a port into
* non-failed mode. Port reset wil also work in such situation.
* If the port is in disconnected (shutdown) state, the connect operation is
* attempted prior to any other action.
* When port is in the active state, there is a device attached and the target
* node exists, a device was most likely offlined.
* If target node does not exist, a new target node is created. In both cases
* an attempt is made to online (configure) the device.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_configure(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
int cport, pmport, qual;
int rval;
boolean_t target = TRUE;
sata_cport_info_t *cportinfo;
sata_pmport_info_t *pmportinfo = NULL;
dev_info_t *tdip;
sata_drive_info_t *sdinfo;
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
qual = sata_device->satadev_addr.qual;
/* Get current port state */
rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (rval != SATA_SUCCESS ||
(sata_device->satadev_state & SATA_PSTATE_FAILED) != 0) {
/*
* Obviously, device on a failed port is not visible
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
return (ENXIO);
}
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
if (qual == SATA_ADDR_PMPORT)
pmportinfo = SATA_PMPORT_INFO(sata_hba_inst, cport, pmport);
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if ((sata_device->satadev_state & SATA_PSTATE_SHUTDOWN) != 0) {
/* need to activate port */
target = FALSE;
/* Sanity check */
if (SATA_PORT_ACTIVATE_FUNC(sata_hba_inst) == NULL)
return (ENXIO);
/* Just let HBA driver to activate port */
if ((*SATA_PORT_ACTIVATE_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device) != SATA_SUCCESS) {
/*
* Port activation failure - do not change port state
* unless the state returned by HBA indicates a port
* failure.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (sata_device->satadev_state & SATA_PSTATE_FAILED) {
if (qual == SATA_ADDR_PMPORT)
pmportinfo->pmport_state =
SATA_PSTATE_FAILED;
else
cportinfo->cport_state =
SATA_PSTATE_FAILED;
}
mutex_exit(&SATA_CPORT_INFO(
sata_hba_inst, cport)->cport_mutex);
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: configure: "
"failed to activate SATA port %d:%d",
cport, pmport));
return (EIO);
}
/*
* Generate sysevent - EC_DR / ESC_DR_AP_STATE_CHANGE
* without the hint.
*/
sata_gen_sysevent(sata_hba_inst,
&sata_device->satadev_addr, SE_NO_HINT);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
/* Virgin port state */
if (qual == SATA_ADDR_PMPORT)
pmportinfo->pmport_state = 0;
else
cportinfo->cport_state = 0;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
}
/*
* Always reprobe port, to get current device info.
*/
if (sata_reprobe_port(sata_hba_inst, sata_device,
SATA_DEV_IDENTIFY_RETRY) != SATA_SUCCESS)
return (EIO);
if (sata_device->satadev_type != SATA_DTYPE_NONE && target == FALSE) {
if (qual == SATA_ADDR_PMPORT) {
/*
* That's the transition from "inactive" port
* to active one with device attached.
*/
sata_log(sata_hba_inst, CE_WARN,
"SATA device detected at port %d:%d",
cport, pmport);
} else {
/*
* When PM is attached to the cport and cport is
* activated, every PM device port needs to be reprobed.
* We need to emit message for all devices detected
* at port multiplier's device ports.
* Add such code here.
* For now, just inform about device attached to
* cport.
*/
sata_log(sata_hba_inst, CE_WARN,
"SATA device detected at port %d", cport);
}
}
/*
* This is where real configuration operation starts.
*
* When PM is attached to the cport and cport is activated,
* devices attached PM device ports may have to be configured
* explicitly. This may change when port multiplier is supported.
* For now, configure only disks and other valid target devices.
*/
if (!(sata_device->satadev_type & SATA_VALID_DEV_TYPE)) {
if (qual == SATA_ADDR_CPORT) {
if (sata_device->satadev_type == SATA_DTYPE_UNKNOWN) {
/*
* A device was not successfully identified
*/
sata_log(sata_hba_inst, CE_WARN,
"Could not identify SATA "
"device at port %d", cport);
}
} else { /* port multiplier device port */
if (sata_device->satadev_type == SATA_DTYPE_UNKNOWN) {
/*
* A device was not successfully identified
*/
sata_log(sata_hba_inst, CE_WARN,
"Could not identify SATA "
"device at port %d:%d", cport, pmport);
}
}
return (ENXIO); /* No device to configure */
}
/*
* Here we may have a device in reset condition,
* but because we are just configuring it, there is
* no need to process the reset other than just
* to clear device reset condition in the HBA driver.
* Setting the flag SATA_EVNT_CLEAR_DEVICE_RESET will
* cause a first command sent the HBA driver with the request
* to clear device reset condition.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if (qual == SATA_ADDR_PMPORT)
sata_device->satadev_addr.qual = SATA_ADDR_DPMPORT;
else
sata_device->satadev_addr.qual = SATA_ADDR_DCPORT;
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
if (sdinfo == NULL) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
return (ENXIO);
}
if (sdinfo->satadrv_event_flags &
(SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET)) {
sdinfo->satadrv_event_flags = 0;
}
sdinfo->satadrv_event_flags |= SATA_EVNT_CLEAR_DEVICE_RESET;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if ((tdip = sata_get_scsi_target_dip(SATA_DIP(sata_hba_inst),
&sata_device->satadev_addr)) != NULL) {
/*
* Target node exists. Verify, that it belongs
* to existing, attached device and not to
* a removed device.
*/
if (sata_check_device_removed(tdip) == B_TRUE) {
if (qual == SATA_ADDR_DPMPORT)
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d cannot be "
"configured. "
"Application(s) accessing "
"previously attached device "
"have to release it before newly "
"inserted device can be made accessible.",
cport);
else
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d:%d cannot be"
"configured. "
"Application(s) accessing "
"previously attached device "
"have to release it before newly "
"inserted device can be made accessible.",
cport, pmport);
return (EIO);
}
/*
* Device was not removed and re-inserted.
* Try to online it.
*/
if (ndi_devi_online(tdip, 0) != NDI_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: configure: "
"onlining device at SATA port "
"%d:%d failed", cport, pmport));
return (EIO);
}
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
if (qual == SATA_ADDR_DPMPORT)
pmportinfo->pmport_tgtnode_clean = B_TRUE;
else
cportinfo-> cport_tgtnode_clean = B_TRUE;
mutex_exit(&SATA_CPORT_INFO(
sata_hba_inst, cport)->cport_mutex);
} else {
/*
* No target node - need to create a new target node.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
if (qual == SATA_ADDR_DPMPORT)
pmportinfo->pmport_tgtnode_clean = B_TRUE;
else
cportinfo-> cport_tgtnode_clean = B_TRUE;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
tdip = sata_create_target_node(SATA_DIP(sata_hba_inst),
sata_hba_inst, &sata_device->satadev_addr);
if (tdip == NULL) {
/* Configure operation failed */
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: configure: "
"configuring SATA device at port %d:%d "
"failed", cport, pmport));
return (EIO);
}
}
return (0);
}
/*
* Process ioctl deactivate port request.
* Arbitrarily unconfigure attached device, if any.
* Even if the unconfigure fails, proceed with the
* port deactivation.
*
* NOTE: Port Multiplier code is not completed and tested.
*/
static int
sata_ioctl_deactivate(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
int cport, pmport, qual;
int rval, rv = 0;
sata_cport_info_t *cportinfo;
sata_pmport_info_t *pmportinfo = NULL;
dev_info_t *tdip;
sata_drive_info_t *sdinfo = NULL;
/* Sanity check */
if (SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst) == NULL)
return (ENOTSUP);
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
qual = sata_device->satadev_addr.qual;
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
if (qual == SATA_ADDR_CPORT) {
sata_device->satadev_addr.qual = SATA_ADDR_DCPORT;
if (cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
/*
* For now, assume that port multiplier is not
* supported, i.e. deal only with valid devices
*/
if ((cportinfo->cport_dev_type &
SATA_VALID_DEV_TYPE) != 0)
sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
/*
* If attached device is a port multiplier, we will
* have to unconfigure all devices attached to the
* port multiplier. Add this code here.
*/
}
cportinfo->cport_state &= ~SATA_STATE_READY;
} else {
/* Port multiplier device port */
pmportinfo = SATA_PMPORT_INFO(sata_hba_inst, cport, pmport);
sata_device->satadev_addr.qual = SATA_ADDR_DPMPORT;
if (pmportinfo->pmport_dev_type != SATA_DTYPE_NONE &&
(pmportinfo->pmport_dev_type & SATA_VALID_DEV_TYPE) != 0)
sdinfo = SATA_PMPORTINFO_DRV_INFO(pmportinfo);
pmportinfo->pmport_state &= ~SATA_STATE_READY;
}
if (sdinfo != NULL) {
/*
* If a target node exists, try to offline a device and
* to remove a target node.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
tdip = sata_get_scsi_target_dip(SATA_DIP(sata_hba_inst),
&sata_device->satadev_addr);
if (tdip != NULL) {
/* target node exist */
SATADBG1(SATA_DBG_IOCTL_IF, sata_hba_inst,
"sata_hba_ioctl: port deactivate: "
"target node exists.", NULL);
if (ndi_devi_offline(tdip, NDI_DEVI_REMOVE) !=
NDI_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: port deactivate: "
"failed to unconfigure device at port "
"%d:%d before deactivating the port",
cport, pmport));
/*
* Set DEVICE REMOVED state in the target
* node. It will prevent an access to
* the device even when a new device is
* attached, until the old target node is
* released, removed and recreated for a new
* device.
*/
sata_set_device_removed(tdip);
/*
* Instruct the event daemon to try the
* target node cleanup later.
*/
sata_set_target_node_cleanup(sata_hba_inst,
&sata_device->satadev_addr);
}
}
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
/*
* In any case, remove and release sata_drive_info
* structure.
*/
if (qual == SATA_ADDR_CPORT) {
SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
cportinfo->cport_dev_type = SATA_DTYPE_NONE;
} else { /* port multiplier device port */
SATA_PMPORTINFO_DRV_INFO(pmportinfo) = NULL;
pmportinfo->pmport_dev_type = SATA_DTYPE_NONE;
}
(void) kmem_free((void *)sdinfo, sizeof (sata_drive_info_t));
}
if (qual == SATA_ADDR_CPORT) {
cportinfo->cport_state &= ~(SATA_STATE_PROBED |
SATA_STATE_PROBING);
} else { /* port multiplier device port */
pmportinfo->pmport_state &= ~(SATA_STATE_PROBED |
SATA_STATE_PROBING);
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
/* Just let HBA driver to deactivate port */
sata_device->satadev_addr.qual = qual;
rval = (*SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device);
/*
* Generate sysevent - EC_DR / ESC_DR_AP_STATE_CHANGE
* without the hint
*/
sata_gen_sysevent(sata_hba_inst, &sata_device->satadev_addr,
SE_NO_HINT);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (qual == SATA_ADDR_CPORT) {
if (rval != SATA_SUCCESS) {
/*
* Port deactivation failure - do not change port state
* unless the state returned by HBA indicates a port
* failure.
*/
if (sata_device->satadev_state & SATA_PSTATE_FAILED) {
SATA_CPORT_STATE(sata_hba_inst, cport) =
SATA_PSTATE_FAILED;
}
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: port deactivate: "
"cannot deactivate SATA port %d", cport));
rv = EIO;
} else {
cportinfo->cport_state |= SATA_PSTATE_SHUTDOWN;
}
} else {
if (rval != SATA_SUCCESS) {
if (sata_device->satadev_state & SATA_PSTATE_FAILED) {
SATA_PMPORT_STATE(sata_hba_inst, cport,
pmport) = SATA_PSTATE_FAILED;
}
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: port deactivate: "
"cannot deactivate SATA port %d:%d",
cport, pmport));
rv = EIO;
} else {
pmportinfo->pmport_state |= SATA_PSTATE_SHUTDOWN;
}
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
return (rv);
}
/*
* Process ioctl port activate request.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_activate(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
int cport, pmport, qual;
sata_cport_info_t *cportinfo;
sata_pmport_info_t *pmportinfo = NULL;
boolean_t dev_existed = TRUE;
/* Sanity check */
if (SATA_PORT_ACTIVATE_FUNC(sata_hba_inst) == NULL)
return (ENOTSUP);
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
qual = sata_device->satadev_addr.qual;
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
if (qual == SATA_ADDR_PMPORT) {
pmportinfo = SATA_PMPORT_INFO(sata_hba_inst, cport, pmport);
if (pmportinfo->pmport_state & SATA_PSTATE_SHUTDOWN ||
pmportinfo->pmport_dev_type == SATA_DTYPE_NONE)
dev_existed = FALSE;
} else { /* cport */
if (cportinfo->cport_state & SATA_PSTATE_SHUTDOWN ||
cportinfo->cport_dev_type == SATA_DTYPE_NONE)
dev_existed = FALSE;
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
/* Just let HBA driver to activate port, if necessary */
if ((*SATA_PORT_ACTIVATE_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device) != SATA_SUCCESS) {
/*
* Port activation failure - do not change port state unless
* the state returned by HBA indicates a port failure.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (sata_device->satadev_state & SATA_PSTATE_FAILED) {
if (qual == SATA_ADDR_PMPORT)
pmportinfo->pmport_state = SATA_PSTATE_FAILED;
else
cportinfo->cport_state = SATA_PSTATE_FAILED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
cport)->cport_mutex);
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: port activate: cannot activate "
"SATA port %d:%d", cport, pmport));
return (EIO);
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
}
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if (qual == SATA_ADDR_PMPORT)
pmportinfo->pmport_state &= ~SATA_PSTATE_SHUTDOWN;
else
cportinfo->cport_state &= ~SATA_PSTATE_SHUTDOWN;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
/*
* Re-probe port to find its current state and possibly attached device.
* Port re-probing may change the cportinfo device type if device is
* found attached.
* If port probing failed, the device type would be set to
* SATA_DTYPE_NONE.
*/
(void) sata_reprobe_port(sata_hba_inst, sata_device,
SATA_DEV_IDENTIFY_RETRY);
/*
* Generate sysevent - EC_DR / ESC_DR_AP_STATE_CHANGE
* without the hint.
*/
sata_gen_sysevent(sata_hba_inst, &sata_device->satadev_addr,
SE_NO_HINT);
if (dev_existed == FALSE) {
if (qual == SATA_ADDR_PMPORT &&
pmportinfo->pmport_dev_type != SATA_DTYPE_NONE) {
/*
* That's the transition from the "inactive" port state
* or the active port without a device attached to the
* active port state with a device attached.
*/
sata_log(sata_hba_inst, CE_WARN,
"SATA device detected at port %d:%d",
cport, pmport);
} else if (qual == SATA_ADDR_CPORT &&
cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
/*
* That's the transition from the "inactive" port state
* or the active port without a device attached to the
* active port state with a device attached.
*/
if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
sata_log(sata_hba_inst, CE_WARN,
"SATA device detected at port %d", cport);
} else {
sata_log(sata_hba_inst, CE_WARN,
"SATA port multiplier detected at port %d",
cport);
/*
* Because the detected device is a port
* multiplier, we need to reprobe every device
* port on the port multiplier and show every
* device found attached.
* Add this code here.
*/
}
}
}
return (0);
}
/*
* Process ioctl reset port request.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_reset_port(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
int cport, pmport, qual;
int rv = 0;
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
qual = sata_device->satadev_addr.qual;
/* Sanity check */
if (SATA_RESET_DPORT_FUNC(sata_hba_inst) == NULL) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: sata_hba_tran missing required "
"function sata_tran_reset_dport"));
return (ENOTSUP);
}
/* Ask HBA to reset port */
if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst),
sata_device) != SATA_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: reset port: failed %d:%d",
cport, pmport));
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (qual == SATA_ADDR_CPORT)
SATA_CPORT_STATE(sata_hba_inst, cport) =
SATA_PSTATE_FAILED;
else
SATA_PMPORT_STATE(sata_hba_inst, cport, pmport) =
SATA_PSTATE_FAILED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
rv = EIO;
}
/*
* Beacuse the port was reset, it should be probed and
* attached device reinitialized. At this point the
* port state is unknown - it's state is HBA-specific.
* Re-probe port to get its state.
*/
if (sata_reprobe_port(sata_hba_inst, sata_device,
SATA_DEV_IDENTIFY_RETRY) != SATA_SUCCESS) {
rv = EIO;
}
return (rv);
}
/*
* Process ioctl reset device request.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_reset_device(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
sata_drive_info_t *sdinfo;
int cport, pmport;
int rv = 0;
/* Sanity check */
if (SATA_RESET_DPORT_FUNC(sata_hba_inst) == NULL) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: sata_hba_tran missing required "
"function sata_tran_reset_dport"));
return (ENOTSUP);
}
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT) {
sata_device->satadev_addr.qual = SATA_ADDR_DCPORT;
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport);
} else { /* port multiplier */
sata_device->satadev_addr.qual = SATA_ADDR_DPMPORT;
sdinfo = SATA_PMPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport);
}
if (sdinfo == NULL) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
return (EINVAL);
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
/* Ask HBA to reset device */
if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device) != SATA_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: reset device: failed at port %d:%d",
cport, pmport));
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
/*
* Device info structure remains attached. Another device reset
* or port disconnect/connect and re-probing is
* needed to change it's state
*/
sdinfo->satadrv_state &= ~SATA_STATE_READY;
sdinfo->satadrv_state |= SATA_DSTATE_FAILED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
rv = EIO;
}
/*
* If attached device was a port multiplier, some extra processing
* may be needed, to bring it back (if port re-probing did not handle
* it). Add such code here.
*/
return (rv);
}
/*
* Process ioctl reset all request.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_reset_all(sata_hba_inst_t *sata_hba_inst)
{
sata_device_t sata_device;
int rv = 0;
int tcport;
int tpmport = 0;
sata_device.satadev_rev = SATA_DEVICE_REV;
/*
* There is no protection here for configured devices.
*/
/* Sanity check */
if (SATA_RESET_DPORT_FUNC(sata_hba_inst) == NULL) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: sata_hba_tran missing required "
"function sata_tran_reset_dport"));
return (ENOTSUP);
}
/*
* Need to lock all ports, not just one.
* If any port is locked by event processing, fail the whole operation.
* One port is already locked, but for simplicity lock it again.
*/
for (tcport = 0; tcport < SATA_NUM_CPORTS(sata_hba_inst); tcport++) {
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_mutex);
if (((SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_event_flags) & SATA_EVNT_LOCK_PORT_BUSY) != 0) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_mutex);
rv = EBUSY;
break;
} else {
SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_event_flags |= SATA_APCTL_LOCK_PORT_BUSY;
/*
* If there is a port multiplier attached, we may need
* to lock its port as well. If so, add such code here.
*/
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_mutex);
}
if (rv == 0) {
/*
* All cports were successfully locked.
* Reset main SATA controller only for now - no PMult.
* Set the device address to port 0, to have a valid device
* address.
*/
sata_device.satadev_addr.qual = SATA_ADDR_CNTRL;
sata_device.satadev_addr.cport = 0;
sata_device.satadev_addr.pmport = 0;
if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device) != SATA_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: reset controller failed"));
return (EIO);
}
/*
* Because ports were reset, port states are unknown.
* They should be re-probed to get their state and
* attached devices should be reinitialized.
* Add code here to re-probe port multiplier device ports.
*/
for (tcport = 0; tcport < SATA_NUM_CPORTS(sata_hba_inst);
tcport++) {
sata_device.satadev_addr.cport = tcport;
sata_device.satadev_addr.pmport = tpmport;
sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
if (sata_reprobe_port(sata_hba_inst, &sata_device,
SATA_DEV_IDENTIFY_RETRY) != SATA_SUCCESS)
rv = EIO;
}
}
/*
* Unlock all ports
*/
for (tcport = 0; tcport < SATA_NUM_CPORTS(sata_hba_inst); tcport++) {
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_mutex);
SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_event_flags &= ~SATA_APCTL_LOCK_PORT_BUSY;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, tcport)->
cport_mutex);
}
/*
* This operation returns EFAULT if either reset
* controller failed or a re-probing of any port failed.
*/
return (rv);
}
/*
* Process ioctl port self test request.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_port_self_test(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device)
{
int cport, pmport, qual;
int rv = 0;
/* Sanity check */
if (SATA_SELFTEST_FUNC(sata_hba_inst) == NULL)
return (ENOTSUP);
cport = sata_device->satadev_addr.cport;
pmport = sata_device->satadev_addr.pmport;
qual = sata_device->satadev_addr.qual;
/*
* There is no protection here for a configured
* device attached to this port.
*/
if ((*SATA_SELFTEST_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), sata_device) != SATA_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_ioctl: port selftest: "
"failed port %d:%d", cport, pmport));
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
sata_update_port_info(sata_hba_inst, sata_device);
if (qual == SATA_ADDR_CPORT)
SATA_CPORT_STATE(sata_hba_inst, cport) =
SATA_PSTATE_FAILED;
else /* port ultiplier device port */
SATA_PMPORT_STATE(sata_hba_inst, cport, pmport) =
SATA_PSTATE_FAILED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
cport_mutex);
return (EIO);
}
/*
* Beacuse the port was reset in the course of testing, it should be
* re-probed and attached device state should be restored. At this
* point the port state is unknown - it's state is HBA-specific.
* Force port re-probing to get it into a known state.
*/
if (sata_reprobe_port(sata_hba_inst, sata_device,
SATA_DEV_IDENTIFY_RETRY) != SATA_SUCCESS)
rv = EIO;
return (rv);
}
/*
* sata_cfgadm_state:
* Use the sata port state and state of the target node to figure out
* the cfgadm_state.
*
* The port argument is a value with encoded cport,
* pmport and address qualifier, in the same manner as a scsi target number.
* SCSI_TO_SATA_CPORT macro extracts cport number,
* SCSI_TO_SATA_PMPORT extracts pmport number and
* SCSI_TO_SATA_ADDR_QUAL extracts port mulitplier qualifier flag.
*
* For now, support is for cports only - no port multiplier device ports.
*/
static void
sata_cfgadm_state(sata_hba_inst_t *sata_hba_inst, int32_t port,
devctl_ap_state_t *ap_state)
{
uint16_t cport;
int port_state;
sata_drive_info_t *sdinfo;
/* Cport only */
cport = SCSI_TO_SATA_CPORT(port);
port_state = SATA_CPORT_STATE(sata_hba_inst, cport);
if (port_state & SATA_PSTATE_SHUTDOWN ||
port_state & SATA_PSTATE_FAILED) {
ap_state->ap_rstate = AP_RSTATE_DISCONNECTED;
ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
if (port_state & SATA_PSTATE_FAILED)
ap_state->ap_condition = AP_COND_FAILED;
else
ap_state->ap_condition = AP_COND_UNKNOWN;
return;
}
/* Need to check pmult device port here as well, when supported */
/* Port is enabled and ready */
switch (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport)) {
case SATA_DTYPE_NONE:
{
ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
ap_state->ap_condition = AP_COND_OK;
/* No device attached */
ap_state->ap_rstate = AP_RSTATE_EMPTY;
break;
}
case SATA_DTYPE_UNKNOWN:
case SATA_DTYPE_PMULT: /* Until PMult is supported */
case SATA_DTYPE_ATADISK:
case SATA_DTYPE_ATAPICD:
case SATA_DTYPE_ATAPITAPE:
case SATA_DTYPE_ATAPIDISK:
{
dev_info_t *tdip = NULL;
dev_info_t *dip = NULL;
int circ;
dip = SATA_DIP(sata_hba_inst);
tdip = sata_get_target_dip(dip, port);
ap_state->ap_rstate = AP_RSTATE_CONNECTED;
if (tdip != NULL) {
ndi_devi_enter(dip, &circ);
mutex_enter(&(DEVI(tdip)->devi_lock));
if (DEVI_IS_DEVICE_REMOVED(tdip)) {
/*
* There could be the case where previously
* configured and opened device was removed
* and unknown device was plugged.
* In such case we want to show a device, and
* its configured or unconfigured state but
* indicate unusable condition untill the
* old target node is released and removed.
*/
ap_state->ap_condition = AP_COND_UNUSABLE;
} else {
mutex_enter(&SATA_CPORT_MUTEX(sata_hba_inst,
cport));
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst,
cport);
if (sdinfo != NULL) {
if ((sdinfo->satadrv_state &
SATA_DSTATE_FAILED) != 0)
ap_state->ap_condition =
AP_COND_FAILED;
else
ap_state->ap_condition =
AP_COND_OK;
} else {
ap_state->ap_condition =
AP_COND_UNKNOWN;
}
mutex_exit(&SATA_CPORT_MUTEX(sata_hba_inst,
cport));
}
if ((DEVI_IS_DEVICE_OFFLINE(tdip)) ||
(DEVI_IS_DEVICE_DOWN(tdip))) {
ap_state->ap_ostate =
AP_OSTATE_UNCONFIGURED;
} else {
ap_state->ap_ostate =
AP_OSTATE_CONFIGURED;
}
mutex_exit(&(DEVI(tdip)->devi_lock));
ndi_devi_exit(dip, circ);
} else {
ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
ap_state->ap_condition = AP_COND_UNKNOWN;
}
break;
}
default:
ap_state->ap_rstate = AP_RSTATE_CONNECTED;
ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
ap_state->ap_condition = AP_COND_UNKNOWN;
/*
* This is actually internal error condition (non fatal),
* because we have already checked all defined device types.
*/
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_cfgadm_state: Internal error: "
"unknown device type"));
break;
}
}
/*
* Process ioctl get device path request.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_get_device_path(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device, sata_ioctl_data_t *ioc, int mode)
{
char path[MAXPATHLEN];
uint32_t size;
dev_info_t *tdip;
(void) strcpy(path, "/devices");
if ((tdip = sata_get_scsi_target_dip(SATA_DIP(sata_hba_inst),
&sata_device->satadev_addr)) == NULL) {
/*
* No such device. If this is a request for a size, do not
* return EINVAL for non-existing target, because cfgadm
* will then indicate a meaningless ioctl failure.
* If this is a request for a path, indicate invalid
* argument.
*/
if (ioc->get_size == 0)
return (EINVAL);
} else {
(void) ddi_pathname(tdip, path + strlen(path));
}
size = strlen(path) + 1;
if (ioc->get_size != 0) {
if (ddi_copyout((void *)&size, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
} else {
if (ioc->bufsiz != size)
return (EINVAL);
else if (ddi_copyout((void *)&path, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
}
return (0);
}
/*
* Process ioctl get attachment point type request.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_get_ap_type(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device, sata_ioctl_data_t *ioc, int mode)
{
uint32_t type_len;
const char *ap_type;
int dev_type;
if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT)
dev_type = SATA_CPORT_DEV_TYPE(sata_hba_inst,
sata_device->satadev_addr.cport);
else /* pmport */
dev_type = SATA_PMPORT_DEV_TYPE(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport);
switch (dev_type) {
case SATA_DTYPE_NONE:
ap_type = "port";
break;
case SATA_DTYPE_ATADISK:
case SATA_DTYPE_ATAPIDISK:
ap_type = "disk";
break;
case SATA_DTYPE_ATAPICD:
ap_type = "cd/dvd";
break;
case SATA_DTYPE_ATAPITAPE:
ap_type = "tape";
break;
case SATA_DTYPE_PMULT:
ap_type = "pmult";
break;
case SATA_DTYPE_UNKNOWN:
ap_type = "unknown";
break;
default:
ap_type = "unsupported";
break;
} /* end of dev_type switch */
type_len = strlen(ap_type) + 1;
if (ioc->get_size) {
if (ddi_copyout((void *)&type_len, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
} else {
if (ioc->bufsiz != type_len)
return (EINVAL);
if (ddi_copyout((void *)ap_type, ioc->buf,
ioc->bufsiz, mode) != 0)
return (EFAULT);
}
return (0);
}
/*
* Process ioctl get device model info request.
* This operation should return to cfgadm the device model
* information string
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_get_model_info(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device, sata_ioctl_data_t *ioc, int mode)
{
sata_drive_info_t *sdinfo;
uint32_t info_len;
char ap_info[SATA_ID_MODEL_LEN + 1];
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT)
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport);
else /* port multiplier */
sdinfo = SATA_PMPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport);
if (sdinfo == NULL) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
return (EINVAL);
}
#ifdef _LITTLE_ENDIAN
swab(sdinfo->satadrv_id.ai_model, ap_info, SATA_ID_MODEL_LEN);
#else /* _LITTLE_ENDIAN */
bcopy(sdinfo->satadrv_id.ai_model, ap_info, SATA_ID_MODEL_LEN);
#endif /* _LITTLE_ENDIAN */
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
ap_info[SATA_ID_MODEL_LEN] = '\0';
info_len = strlen(ap_info) + 1;
if (ioc->get_size) {
if (ddi_copyout((void *)&info_len, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
} else {
if (ioc->bufsiz < info_len)
return (EINVAL);
if (ddi_copyout((void *)ap_info, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
}
return (0);
}
/*
* Process ioctl get device firmware revision info request.
* This operation should return to cfgadm the device firmware revision
* information string
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_get_revfirmware_info(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device, sata_ioctl_data_t *ioc, int mode)
{
sata_drive_info_t *sdinfo;
uint32_t info_len;
char ap_info[SATA_ID_FW_LEN + 1];
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT)
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport);
else /* port multiplier */
sdinfo = SATA_PMPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport);
if (sdinfo == NULL) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
return (EINVAL);
}
#ifdef _LITTLE_ENDIAN
swab(sdinfo->satadrv_id.ai_fw, ap_info, SATA_ID_FW_LEN);
#else /* _LITTLE_ENDIAN */
bcopy(sdinfo->satadrv_id.ai_fw, ap_info, SATA_ID_FW_LEN);
#endif /* _LITTLE_ENDIAN */
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
ap_info[SATA_ID_FW_LEN] = '\0';
info_len = strlen(ap_info) + 1;
if (ioc->get_size) {
if (ddi_copyout((void *)&info_len, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
} else {
if (ioc->bufsiz < info_len)
return (EINVAL);
if (ddi_copyout((void *)ap_info, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
}
return (0);
}
/*
* Process ioctl get device serial number info request.
* This operation should return to cfgadm the device serial number string.
*
* NOTE: Port multiplier code is not completed nor tested.
*/
static int
sata_ioctl_get_serialnumber_info(sata_hba_inst_t *sata_hba_inst,
sata_device_t *sata_device, sata_ioctl_data_t *ioc, int mode)
{
sata_drive_info_t *sdinfo;
uint32_t info_len;
char ap_info[SATA_ID_SERIAL_LEN + 1];
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT)
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport);
else /* port multiplier */
sdinfo = SATA_PMPORT_DRV_INFO(sata_hba_inst,
sata_device->satadev_addr.cport,
sata_device->satadev_addr.pmport);
if (sdinfo == NULL) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
return (EINVAL);
}
#ifdef _LITTLE_ENDIAN
swab(sdinfo->satadrv_id.ai_drvser, ap_info, SATA_ID_SERIAL_LEN);
#else /* _LITTLE_ENDIAN */
bcopy(sdinfo->satadrv_id.ai_drvser, ap_info, SATA_ID_SERIAL_LEN);
#endif /* _LITTLE_ENDIAN */
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
sata_device->satadev_addr.cport)->cport_mutex);
ap_info[SATA_ID_SERIAL_LEN] = '\0';
info_len = strlen(ap_info) + 1;
if (ioc->get_size) {
if (ddi_copyout((void *)&info_len, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
} else {
if (ioc->bufsiz < info_len)
return (EINVAL);
if (ddi_copyout((void *)ap_info, ioc->buf, ioc->bufsiz,
mode) != 0)
return (EFAULT);
}
return (0);
}
/*
* Preset scsi extended sense data (to NO SENSE)
* First 18 bytes of the sense data are preset to current valid sense
* with a key NO SENSE data.
*
* Returns void
*/
static void
sata_fixed_sense_data_preset(struct scsi_extended_sense *sense)
{
sense->es_valid = 1; /* Valid sense */
sense->es_class = CLASS_EXTENDED_SENSE; /* 0x70 - current err */
sense->es_key = KEY_NO_SENSE;
sense->es_info_1 = 0;
sense->es_info_2 = 0;
sense->es_info_3 = 0;
sense->es_info_4 = 0;
sense->es_add_len = 10; /* Additional length - replace with a def */
sense->es_cmd_info[0] = 0;
sense->es_cmd_info[1] = 0;
sense->es_cmd_info[2] = 0;
sense->es_cmd_info[3] = 0;
sense->es_add_code = 0;
sense->es_qual_code = 0;
}
/*
* Register a legacy cmdk-style devid for the target (disk) device.
*
* Note: This function is called only when the HBA devinfo node has the
* property "use-cmdk-devid-format" set. This property indicates that
* devid compatible with old cmdk (target) driver is to be generated
* for any target device attached to this controller. This will take
* precedence over the devid generated by sd (target) driver.
* This function is derived from cmdk_devid_setup() function in cmdk.c.
*/
static void
sata_target_devid_register(dev_info_t *dip, sata_drive_info_t *sdinfo)
{
char *hwid;
int modlen;
int serlen;
int rval;
ddi_devid_t devid;
/*
* device ID is a concatanation of model number, "=", serial number.
*/
hwid = kmem_zalloc(LEGACY_HWID_LEN, KM_SLEEP);
bcopy(&sdinfo->satadrv_id.ai_model, hwid,
sizeof (sdinfo->satadrv_id.ai_model));
swab(hwid, hwid, sizeof (sdinfo->satadrv_id.ai_model));
modlen = sata_check_modser(hwid, sizeof (sdinfo->satadrv_id.ai_model));
if (modlen == 0)
goto err;
hwid[modlen++] = '=';
bcopy(&sdinfo->satadrv_id.ai_drvser, &hwid[modlen],
sizeof (sdinfo->satadrv_id.ai_drvser));
swab(&hwid[modlen], &hwid[modlen],
sizeof (sdinfo->satadrv_id.ai_drvser));
serlen = sata_check_modser(&hwid[modlen],
sizeof (sdinfo->satadrv_id.ai_drvser));
if (serlen == 0)
goto err;
hwid[modlen + serlen] = 0; /* terminate the hwid string */
/* initialize/register devid */
if ((rval = ddi_devid_init(dip, DEVID_ATA_SERIAL,
(ushort_t)(modlen + serlen), hwid, &devid)) == DDI_SUCCESS) {
rval = ddi_devid_register(dip, devid);
/*
* Free up the allocated devid buffer.
* NOTE: This doesn't mean unregistering devid.
*/
ddi_devid_free(devid);
}
if (rval != DDI_SUCCESS)
cmn_err(CE_WARN, "sata: failed to create devid for the disk"
" on port %d", sdinfo->satadrv_addr.cport);
err:
kmem_free(hwid, LEGACY_HWID_LEN);
}
/*
* valid model/serial string must contain a non-zero non-space characters.
* trim trailing spaces/NULLs.
*/
static int
sata_check_modser(char *buf, int buf_len)
{
boolean_t ret;
char *s;
int i;
int tb;
char ch;
ret = B_FALSE;
s = buf;
for (i = 0; i < buf_len; i++) {
ch = *s++;
if (ch != ' ' && ch != '\0')
tb = i + 1;
if (ch != ' ' && ch != '\0' && ch != '0')
ret = B_TRUE;
}
if (ret == B_FALSE)
return (0); /* invalid string */
return (tb); /* return length */
}
/*
* sata_set_drive_features function compares current device features setting
* with the saved device features settings and, if there is a difference,
* it restores device features setting to the previously saved state.
* It also arbitrarily tries to select the highest supported DMA mode.
* Device Identify or Identify Packet Device data has to be current.
* At the moment read ahead and write cache are considered for all devices.
* For atapi devices, Removable Media Status Notification is set in addition
* to common features.
*
* This function cannot be called in the interrupt context (it may sleep).
*
* The input argument sdinfo should point to the drive info structure
* to be updated after features are set. Note, that only
* device (packet) identify data is updated, not the flags indicating the
* supported features.
*
* Returns SATA_SUCCESS if successful or there was nothing to do.
* Device Identify data in the drive info structure pointed to by the sdinfo
* arguments is updated even when no features were set or changed.
*
* Returns SATA_FAILURE if device features could not be set or DMA mode
* for a disk cannot be set and device identify data cannot be fetched.
*
* Returns SATA_RETRY if device features could not be set (other than disk
* DMA mode) but the device identify data was fetched successfully.
*
* Note: This function may fail the port, making it inaccessible.
* In such case the explicit port disconnect/connect or physical device
* detach/attach is required to re-evaluate port state again.
*/
static int
sata_set_drive_features(sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo, int restore)
{
int rval = SATA_SUCCESS;
int rval_set;
sata_drive_info_t new_sdinfo;
char *finfo = "sata_set_drive_features: cannot";
char *finfox;
int cache_op;
bzero(&new_sdinfo, sizeof (sata_drive_info_t));
new_sdinfo.satadrv_addr = sdinfo->satadrv_addr;
new_sdinfo.satadrv_type = sdinfo->satadrv_type;
if (sata_fetch_device_identify_data(sata_hba_inst, &new_sdinfo) != 0) {
/*
* Cannot get device identification - caller may retry later
*/
SATADBG1(SATA_DBG_DEV_SETTINGS, sata_hba_inst,
"%s fetch device identify data\n", finfo);
return (SATA_FAILURE);
}
finfox = (restore != 0) ? " restore device features" :
" initialize device features\n";
switch (sdinfo->satadrv_type) {
case SATA_DTYPE_ATADISK:
/* Arbitrarily set UDMA mode */
if (sata_set_dma_mode(sata_hba_inst, &new_sdinfo) !=
SATA_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"%s set UDMA mode\n", finfo));
return (SATA_FAILURE);
}
break;
case SATA_DTYPE_ATAPICD:
case SATA_DTYPE_ATAPITAPE:
case SATA_DTYPE_ATAPIDISK:
/* Set Removable Media Status Notification, if necessary */
if (SATA_RM_NOTIFIC_SUPPORTED(new_sdinfo.satadrv_id) &&
restore != 0) {
if (((sdinfo->satadrv_settings & SATA_DEV_RMSN) &&
(!SATA_RM_NOTIFIC_ENABLED(new_sdinfo.satadrv_id)))||
((!(sdinfo->satadrv_settings & SATA_DEV_RMSN)) &&
SATA_RM_NOTIFIC_ENABLED(new_sdinfo.satadrv_id))) {
/* Current setting does not match saved one */
if (sata_set_rmsn(sata_hba_inst, sdinfo,
sdinfo->satadrv_settings &
SATA_DEV_RMSN) != SATA_SUCCESS)
rval = SATA_FAILURE;
}
}
/*
* We have to set Multiword DMA or UDMA, if it is supported, as
* we want to use DMA transfer mode whenever possible.
* Some devices require explicit setting of the DMA mode.
*/
if (new_sdinfo.satadrv_id.ai_cap & SATA_DMA_SUPPORT) {
/* Set highest supported DMA mode */
if (sata_set_dma_mode(sata_hba_inst, &new_sdinfo) !=
SATA_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"%s set UDMA mode\n", finfo));
rval = SATA_FAILURE;
}
}
break;
}
if (!SATA_READ_AHEAD_SUPPORTED(new_sdinfo.satadrv_id) &&
!SATA_WRITE_CACHE_SUPPORTED(new_sdinfo.satadrv_id)) {
/*
* neither READ AHEAD nor WRITE CACHE is supported
* - do nothing
*/
SATADBG1(SATA_DBG_DEV_SETTINGS, sata_hba_inst,
"settable features not supported\n", NULL);
goto update_sdinfo;
}
if ((SATA_READ_AHEAD_ENABLED(new_sdinfo.satadrv_id) &&
(sdinfo->satadrv_settings & SATA_DEV_READ_AHEAD)) &&
(SATA_WRITE_CACHE_ENABLED(new_sdinfo.satadrv_id) &&
(sdinfo->satadrv_settings & SATA_DEV_WRITE_CACHE))) {
/*
* both READ AHEAD and WRITE CACHE are enabled
* - Nothing to do
*/
SATADBG1(SATA_DBG_DEV_SETTINGS, sata_hba_inst,
"no device features to set\n", NULL);
goto update_sdinfo;
}
cache_op = 0;
if (SATA_READ_AHEAD_SUPPORTED(new_sdinfo.satadrv_id)) {
if ((sdinfo->satadrv_settings & SATA_DEV_READ_AHEAD) &&
!SATA_READ_AHEAD_ENABLED(new_sdinfo.satadrv_id)) {
/* Enable read ahead / read cache */
cache_op = SATAC_SF_ENABLE_READ_AHEAD;
SATADBG1(SATA_DBG_DEV_SETTINGS, sata_hba_inst,
"enabling read cache\n", NULL);
} else if (!(sdinfo->satadrv_settings & SATA_DEV_READ_AHEAD) &&
SATA_READ_AHEAD_ENABLED(new_sdinfo.satadrv_id)) {
/* Disable read ahead / read cache */
cache_op = SATAC_SF_DISABLE_READ_AHEAD;
SATADBG1(SATA_DBG_DEV_SETTINGS, sata_hba_inst,
"disabling read cache\n", NULL);
}
if (cache_op != 0) {
/* Try to set read cache mode */
rval_set = sata_set_cache_mode(sata_hba_inst,
&new_sdinfo, cache_op);
if (rval != SATA_FAILURE && rval_set != SATA_SUCCESS)
rval = rval_set;
}
}
cache_op = 0;
if (SATA_WRITE_CACHE_SUPPORTED(new_sdinfo.satadrv_id)) {
if ((sdinfo->satadrv_settings & SATA_DEV_WRITE_CACHE) &&
!SATA_WRITE_CACHE_ENABLED(new_sdinfo.satadrv_id)) {
/* Enable write cache */
cache_op = SATAC_SF_ENABLE_WRITE_CACHE;
SATADBG1(SATA_DBG_DEV_SETTINGS, sata_hba_inst,
"enabling write cache\n", NULL);
} else if (!(sdinfo->satadrv_settings & SATA_DEV_WRITE_CACHE) &&
SATA_WRITE_CACHE_ENABLED(new_sdinfo.satadrv_id)) {
/* Disable write cache */
cache_op = SATAC_SF_DISABLE_WRITE_CACHE;
SATADBG1(SATA_DBG_DEV_SETTINGS, sata_hba_inst,
"disabling write cache\n", NULL);
}
if (cache_op != 0) {
/* Try to set write cache mode */
rval_set = sata_set_cache_mode(sata_hba_inst,
&new_sdinfo, cache_op);
if (rval != SATA_FAILURE && rval_set != SATA_SUCCESS)
rval = rval_set;
}
}
if (rval != SATA_SUCCESS)
SATA_LOG_D((sata_hba_inst, CE_WARN,
"%s %s", finfo, finfox));
update_sdinfo:
/*
* We need to fetch Device Identify data again
*/
if (sata_fetch_device_identify_data(sata_hba_inst, &new_sdinfo) != 0) {
/*
* Cannot get device identification - retry later
*/
SATA_LOG_D((sata_hba_inst, CE_WARN,
"%s re-fetch device identify data\n", finfo));
rval = SATA_FAILURE;
}
/* Copy device sata info. */
sdinfo->satadrv_id = new_sdinfo.satadrv_id;
return (rval);
}
/*
*
* Returns 1 if threshold exceeded, 0 if threshold not exceeded, -1 if
* unable to determine.
*
* Cannot be called in an interrupt context.
*
* Called by sata_build_lsense_page_2f()
*/
static int
sata_fetch_smart_return_status(sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo)
{
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int rval;
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (-1);
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback */
spkt->satapkt_comp = NULL;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_special_regs = B_TRUE;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_NODATA_XFER;
/* Set up which registers need to be returned */
scmd->satacmd_flags.sata_copy_out_lba_mid_lsb = B_TRUE;
scmd->satacmd_flags.sata_copy_out_lba_high_lsb = B_TRUE;
/* Build SMART_RETURN_STATUS cmd in the sata_pkt */
scmd->satacmd_addr_type = 0; /* N/A */
scmd->satacmd_sec_count_lsb = 0; /* N/A */
scmd->satacmd_lba_low_lsb = 0; /* N/A */
scmd->satacmd_lba_mid_lsb = SMART_MAGIC_VAL_1;
scmd->satacmd_lba_high_lsb = SMART_MAGIC_VAL_2;
scmd->satacmd_features_reg = SATA_SMART_RETURN_STATUS;
scmd->satacmd_device_reg = 0; /* Always device 0 */
scmd->satacmd_cmd_reg = SATAC_SMART;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/* Send pkt to SATA HBA driver */
if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt) !=
SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/*
* Whoops, no SMART RETURN STATUS
*/
rval = -1;
} else {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
if (scmd->satacmd_error_reg & SATA_ERROR_ABORT) {
rval = -1;
goto fail;
}
if (scmd->satacmd_status_reg & SATA_STATUS_ERR) {
rval = -1;
goto fail;
}
if ((scmd->satacmd_lba_mid_lsb == SMART_MAGIC_VAL_1) &&
(scmd->satacmd_lba_high_lsb == SMART_MAGIC_VAL_2))
rval = 0;
else if ((scmd->satacmd_lba_mid_lsb == SMART_MAGIC_VAL_3) &&
(scmd->satacmd_lba_high_lsb == SMART_MAGIC_VAL_4))
rval = 1;
else {
rval = -1;
goto fail;
}
}
fail:
/* Free allocated resources */
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
*
* Returns 0 if succeeded, -1 otherwise
*
* Cannot be called in an interrupt context.
*
*/
static int
sata_fetch_smart_data(
sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo,
struct smart_data *smart_data)
{
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int rval;
#if ! defined(lint)
ASSERT(sizeof (struct smart_data) == 512);
#endif
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (-1);
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback */
spkt->satapkt_comp = NULL;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
/*
* Allocate buffer for SMART data
*/
scmd->satacmd_bp = sata_alloc_local_buffer(spx,
sizeof (struct smart_data));
if (scmd->satacmd_bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_fetch_smart_data: "
"cannot allocate buffer"));
return (-1);
}
/* Build SMART_READ_DATA cmd in the sata_pkt */
scmd->satacmd_addr_type = 0; /* N/A */
scmd->satacmd_sec_count_lsb = 0; /* N/A */
scmd->satacmd_lba_low_lsb = 0; /* N/A */
scmd->satacmd_lba_mid_lsb = SMART_MAGIC_VAL_1;
scmd->satacmd_lba_high_lsb = SMART_MAGIC_VAL_2;
scmd->satacmd_features_reg = SATA_SMART_READ_DATA;
scmd->satacmd_device_reg = 0; /* Always device 0 */
scmd->satacmd_cmd_reg = SATAC_SMART;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/* Send pkt to SATA HBA driver */
if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt) !=
SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/*
* Whoops, no SMART DATA available
*/
rval = -1;
goto fail;
} else {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
if (spx->txlt_buf_dma_handle != NULL) {
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORKERNEL);
ASSERT(rval == DDI_SUCCESS);
}
bcopy(scmd->satacmd_bp->b_un.b_addr, (uint8_t *)smart_data,
sizeof (struct smart_data));
}
fail:
/* Free allocated resources */
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Used by LOG SENSE page 0x10
* Reads (in synchronous mode) the self test log data using Read Log Ext cmd.
* Note: cannot be called in the interrupt context.
*
* return 0 for success, -1 otherwise
*
*/
static int
sata_ext_smart_selftest_read_log(
sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo,
struct smart_ext_selftest_log *ext_selftest_log,
uint16_t block_num)
{
sata_pkt_txlate_t *spx;
sata_pkt_t *spkt;
sata_cmd_t *scmd;
int rval;
#if ! defined(lint)
ASSERT(sizeof (struct smart_ext_selftest_log) == 512);
#endif
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (-1);
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback */
spkt->satapkt_comp = NULL;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
/*
* Allocate buffer for SMART extended self-test log
*/
scmd->satacmd_bp = sata_alloc_local_buffer(spx,
sizeof (struct smart_ext_selftest_log));
if (scmd->satacmd_bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_ext_smart_selftest_log: "
"cannot allocate buffer"));
return (-1);
}
/* Build READ LOG EXT w/ extended self-test log cmd in the sata_pkt */
scmd->satacmd_addr_type = ATA_ADDR_LBA48;
scmd->satacmd_sec_count_lsb = 1; /* One sector of selftest log */
scmd->satacmd_sec_count_msb = 0; /* One sector of selftest log */
scmd->satacmd_lba_low_lsb = EXT_SMART_SELFTEST_LOG_PAGE;
scmd->satacmd_lba_low_msb = 0;
scmd->satacmd_lba_mid_lsb = block_num & 0xff;
scmd->satacmd_lba_mid_msb = block_num >> 8;
scmd->satacmd_device_reg = 0; /* Always device 0 */
scmd->satacmd_cmd_reg = SATAC_READ_LOG_EXT;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/* Send pkt to SATA HBA driver */
if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt) !=
SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/*
* Whoops, no SMART selftest log info available
*/
rval = -1;
goto fail;
} else {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
if (spx->txlt_buf_dma_handle != NULL) {
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORKERNEL);
ASSERT(rval == DDI_SUCCESS);
}
bcopy(scmd->satacmd_bp->b_un.b_addr,
(uint8_t *)ext_selftest_log,
sizeof (struct smart_ext_selftest_log));
rval = 0;
}
fail:
/* Free allocated resources */
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Returns 0 for success, -1 otherwise
*
* SMART self-test log data is returned in buffer pointed to by selftest_log
*/
static int
sata_smart_selftest_log(
sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo,
struct smart_selftest_log *selftest_log)
{
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int rval;
#if ! defined(lint)
ASSERT(sizeof (struct smart_selftest_log) == 512);
#endif
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (-1);
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback */
spkt->satapkt_comp = NULL;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
/*
* Allocate buffer for SMART SELFTEST LOG
*/
scmd->satacmd_bp = sata_alloc_local_buffer(spx,
sizeof (struct smart_selftest_log));
if (scmd->satacmd_bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_smart_selftest_log: "
"cannot allocate buffer"));
return (-1);
}
/* Build SMART_READ_LOG cmd in the sata_pkt */
scmd->satacmd_addr_type = 0; /* N/A */
scmd->satacmd_sec_count_lsb = 1; /* One sector of SMART log */
scmd->satacmd_lba_low_lsb = SMART_SELFTEST_LOG_PAGE;
scmd->satacmd_lba_mid_lsb = SMART_MAGIC_VAL_1;
scmd->satacmd_lba_high_lsb = SMART_MAGIC_VAL_2;
scmd->satacmd_features_reg = SATA_SMART_READ_LOG;
scmd->satacmd_device_reg = 0; /* Always device 0 */
scmd->satacmd_cmd_reg = SATAC_SMART;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/* Send pkt to SATA HBA driver */
if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt) !=
SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/*
* Whoops, no SMART DATA available
*/
rval = -1;
goto fail;
} else {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
if (spx->txlt_buf_dma_handle != NULL) {
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORKERNEL);
ASSERT(rval == DDI_SUCCESS);
}
bcopy(scmd->satacmd_bp->b_un.b_addr, (uint8_t *)selftest_log,
sizeof (struct smart_selftest_log));
rval = 0;
}
fail:
/* Free allocated resources */
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Returns 0 for success, -1 otherwise
*
* SMART READ LOG data is returned in buffer pointed to by smart_log
*/
static int
sata_smart_read_log(
sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo,
uint8_t *smart_log, /* where the data should be returned */
uint8_t which_log, /* which log should be returned */
uint8_t log_size) /* # of 512 bytes in log */
{
sata_pkt_t *spkt;
sata_cmd_t *scmd;
sata_pkt_txlate_t *spx;
int rval;
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (-1);
}
/* address is needed now */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback */
spkt->satapkt_comp = NULL;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
/*
* Allocate buffer for SMART READ LOG
*/
scmd->satacmd_bp = sata_alloc_local_buffer(spx, log_size * 512);
if (scmd->satacmd_bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_smart_read_log: " "cannot allocate buffer"));
return (-1);
}
/* Build SMART_READ_LOG cmd in the sata_pkt */
scmd->satacmd_addr_type = 0; /* N/A */
scmd->satacmd_sec_count_lsb = log_size; /* what the caller asked for */
scmd->satacmd_lba_low_lsb = which_log; /* which log page */
scmd->satacmd_lba_mid_lsb = SMART_MAGIC_VAL_1;
scmd->satacmd_lba_high_lsb = SMART_MAGIC_VAL_2;
scmd->satacmd_features_reg = SATA_SMART_READ_LOG;
scmd->satacmd_device_reg = 0; /* Always device 0 */
scmd->satacmd_cmd_reg = SATAC_SMART;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/* Send pkt to SATA HBA driver */
if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt) !=
SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/*
* Whoops, no SMART DATA available
*/
rval = -1;
goto fail;
} else {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
if (spx->txlt_buf_dma_handle != NULL) {
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORKERNEL);
ASSERT(rval == DDI_SUCCESS);
}
bcopy(scmd->satacmd_bp->b_un.b_addr, smart_log, log_size * 512);
rval = 0;
}
fail:
/* Free allocated resources */
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Used by LOG SENSE page 0x10
*
* return 0 for success, -1 otherwise
*
*/
static int
sata_read_log_ext_directory(
sata_hba_inst_t *sata_hba_inst,
sata_drive_info_t *sdinfo,
struct read_log_ext_directory *logdir)
{
sata_pkt_txlate_t *spx;
sata_pkt_t *spkt;
sata_cmd_t *scmd;
int rval;
#if ! defined(lint)
ASSERT(sizeof (struct read_log_ext_directory) == 512);
#endif
spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
spx->txlt_sata_hba_inst = sata_hba_inst;
spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
if (spkt == NULL) {
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (-1);
}
/* Fill sata_pkt */
spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback */
spkt->satapkt_comp = NULL;
/* Timeout 30s */
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
scmd->satacmd_flags.sata_data_direction = SATA_DIR_READ;
/*
* Allocate buffer for SMART READ LOG EXTENDED command
*/
scmd->satacmd_bp = sata_alloc_local_buffer(spx,
sizeof (struct read_log_ext_directory));
if (scmd->satacmd_bp == NULL) {
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_read_log_ext_directory: "
"cannot allocate buffer"));
return (-1);
}
/* Build READ LOG EXT w/ log directory cmd in the sata_pkt */
scmd->satacmd_addr_type = ATA_ADDR_LBA48;
scmd->satacmd_sec_count_lsb = 1; /* One sector of directory */
scmd->satacmd_sec_count_msb = 0; /* One sector of directory */
scmd->satacmd_lba_low_lsb = READ_LOG_EXT_LOG_DIRECTORY;
scmd->satacmd_lba_low_msb = 0;
scmd->satacmd_lba_mid_lsb = 0;
scmd->satacmd_lba_mid_msb = 0;
scmd->satacmd_device_reg = 0; /* Always device 0 */
scmd->satacmd_cmd_reg = SATAC_READ_LOG_EXT;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/* Send pkt to SATA HBA driver */
if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt) !=
SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
/*
* Whoops, no SMART selftest log info available
*/
rval = -1;
goto fail;
} else {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
sdinfo->satadrv_addr.cport)));
if (spx->txlt_buf_dma_handle != NULL) {
rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
DDI_DMA_SYNC_FORKERNEL);
ASSERT(rval == DDI_SUCCESS);
}
bcopy(scmd->satacmd_bp->b_un.b_addr, (uint8_t *)logdir,
sizeof (struct read_log_ext_directory));
rval = 0;
}
fail:
/* Free allocated resources */
sata_free_local_buffer(spx);
sata_pkt_free(spx);
kmem_free(spx, sizeof (sata_pkt_txlate_t));
return (rval);
}
/*
* Set up error retrieval sata command for NCQ command error data
* recovery.
*
* Returns SATA_SUCCESS when data buffer is allocated and packet set-up,
* returns SATA_FAILURE otherwise.
*/
static int
sata_ncq_err_ret_cmd_setup(sata_pkt_txlate_t *spx, sata_drive_info_t *sdinfo)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(sdinfo))
#endif
sata_pkt_t *spkt = spx->txlt_sata_pkt;
sata_cmd_t *scmd;
struct buf *bp;
/* Operation modes are up to the caller */
spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
/* Synchronous mode, no callback - may be changed by the caller */
spkt->satapkt_comp = NULL;
spkt->satapkt_time = sata_default_pkt_time;
scmd = &spkt->satapkt_cmd;
bcopy(&sata_rle_cmd, scmd, sizeof (sata_cmd_t));
scmd->satacmd_flags.sata_ignore_dev_reset = B_TRUE;
/*
* Allocate dma_able buffer error data.
* Buffer allocation will take care of buffer alignment and other DMA
* attributes.
*/
bp = sata_alloc_local_buffer(spx,
sizeof (struct sata_ncq_error_recovery_page));
if (bp == NULL)
return (SATA_FAILURE);
bp_mapin(bp); /* make data buffer accessible */
scmd->satacmd_bp = bp;
/*
* Set-up pointer to the buffer handle, so HBA can sync buffer
* before accessing it. Handle is in usual place in translate struct.
*/
scmd->satacmd_err_ret_buf_handle = &spx->txlt_buf_dma_handle;
ASSERT(scmd->satacmd_num_dma_cookies != 0);
ASSERT(scmd->satacmd_dma_cookie_list != NULL);
return (SATA_SUCCESS);
}
/*
* sata_xlate_errors() is used to translate (S)ATA error
* information to SCSI information returned in the SCSI
* packet.
*/
static void
sata_xlate_errors(sata_pkt_txlate_t *spx)
{
struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
struct scsi_extended_sense *sense;
scsipkt->pkt_reason = CMD_INCOMPLETE;
*scsipkt->pkt_scbp = STATUS_CHECK;
sense = sata_arq_sense(spx);
switch (spx->txlt_sata_pkt->satapkt_reason) {
case SATA_PKT_PORT_ERROR:
/*
* We have no device data. Assume no data transfered.
*/
sense->es_key = KEY_HARDWARE_ERROR;
break;
case SATA_PKT_DEV_ERROR:
if (spx->txlt_sata_pkt->satapkt_cmd.satacmd_status_reg &
SATA_STATUS_ERR) {
/*
* determine dev error reason from error
* reg content
*/
sata_decode_device_error(spx, sense);
break;
}
/* No extended sense key - no info available */
break;
case SATA_PKT_TIMEOUT:
scsipkt->pkt_reason = CMD_TIMEOUT;
scsipkt->pkt_statistics |= STAT_TIMEOUT | STAT_DEV_RESET;
/* No extended sense key */
break;
case SATA_PKT_ABORTED:
scsipkt->pkt_reason = CMD_ABORTED;
scsipkt->pkt_statistics |= STAT_ABORTED;
/* No extended sense key */
break;
case SATA_PKT_RESET:
/*
* pkt aborted either by an explicit reset request from
* a host, or due to error recovery
*/
scsipkt->pkt_reason = CMD_RESET;
scsipkt->pkt_statistics |= STAT_DEV_RESET;
break;
default:
scsipkt->pkt_reason = CMD_TRAN_ERR;
break;
}
}
/*
* Log sata message
* dev pathname msg line preceeds the logged message.
*/
static void
sata_log(sata_hba_inst_t *sata_hba_inst, uint_t level, char *fmt, ...)
{
char pathname[128];
dev_info_t *dip;
va_list ap;
mutex_enter(&sata_log_mutex);
va_start(ap, fmt);
(void) vsprintf(sata_log_buf, fmt, ap);
va_end(ap);
if (sata_hba_inst != NULL) {
dip = SATA_DIP(sata_hba_inst);
(void) ddi_pathname(dip, pathname);
} else {
pathname[0] = 0;
}
if (level == CE_CONT) {
if (sata_debug_flags == 0)
cmn_err(level, "?%s:\n %s\n", pathname, sata_log_buf);
else
cmn_err(level, "%s:\n %s\n", pathname, sata_log_buf);
} else {
if (level != CE_NOTE) {
cmn_err(level, "%s:\n %s", pathname, sata_log_buf);
} else if (sata_msg) {
cmn_err(level, "%s:\n %s", pathname,
sata_log_buf);
}
}
mutex_exit(&sata_log_mutex);
}
/* ******** Asynchronous HBA events handling & hotplugging support ******** */
/*
* Start or terminate the thread, depending on flag arg and current state
*/
static void
sata_event_thread_control(int startstop)
{
static int sata_event_thread_terminating = 0;
static int sata_event_thread_starting = 0;
int i;
mutex_enter(&sata_event_mutex);
if (startstop == 0 && (sata_event_thread_starting == 1 ||
sata_event_thread_terminating == 1)) {
mutex_exit(&sata_event_mutex);
return;
}
if (startstop == 1 && sata_event_thread_starting == 1) {
mutex_exit(&sata_event_mutex);
return;
}
if (startstop == 1 && sata_event_thread_terminating == 1) {
sata_event_thread_starting = 1;
/* wait til terminate operation completes */
i = SATA_EVNT_DAEMON_TERM_WAIT/SATA_EVNT_DAEMON_TERM_TIMEOUT;
while (sata_event_thread_terminating == 1) {
if (i-- <= 0) {
sata_event_thread_starting = 0;
mutex_exit(&sata_event_mutex);
#ifdef SATA_DEBUG
cmn_err(CE_WARN, "sata_event_thread_control: "
"timeout waiting for thread to terminate");
#endif
return;
}
mutex_exit(&sata_event_mutex);
delay(drv_usectohz(SATA_EVNT_DAEMON_TERM_TIMEOUT));
mutex_enter(&sata_event_mutex);
}
}
if (startstop == 1) {
if (sata_event_thread == NULL) {
sata_event_thread = thread_create(NULL, 0,
(void (*)())sata_event_daemon,
&sata_hba_list, 0, &p0, TS_RUN, minclsyspri);
}
sata_event_thread_starting = 0;
mutex_exit(&sata_event_mutex);
return;
}
/*
* If we got here, thread may need to be terminated
*/
if (sata_event_thread != NULL) {
int i;
/* Signal event thread to go away */
sata_event_thread_terminating = 1;
sata_event_thread_terminate = 1;
cv_signal(&sata_event_cv);
/*
* Wait til daemon terminates.
*/
i = SATA_EVNT_DAEMON_TERM_WAIT/SATA_EVNT_DAEMON_TERM_TIMEOUT;
while (sata_event_thread_terminate == 1) {
mutex_exit(&sata_event_mutex);
if (i-- <= 0) {
/* Daemon did not go away !!! */
#ifdef SATA_DEBUG
cmn_err(CE_WARN, "sata_event_thread_control: "
"cannot terminate event daemon thread");
#endif
mutex_enter(&sata_event_mutex);
break;
}
delay(drv_usectohz(SATA_EVNT_DAEMON_TERM_TIMEOUT));
mutex_enter(&sata_event_mutex);
}
sata_event_thread_terminating = 0;
}
ASSERT(sata_event_thread_terminating == 0);
ASSERT(sata_event_thread_starting == 0);
mutex_exit(&sata_event_mutex);
}
/*
* SATA HBA event notification function.
* Events reported by SATA HBA drivers per HBA instance relate to a change in
* a port and/or device state or a controller itself.
* Events for different addresses/addr types cannot be combined.
* A warning message is generated for each event type.
* Events are not processed by this function, so only the
* event flag(s)is set for an affected entity and the event thread is
* waken up. Event daemon thread processes all events.
*
* NOTE: Since more than one event may be reported at the same time, one
* cannot determine a sequence of events when opposite event are reported, eg.
* LINK_LOST and LINK_ESTABLISHED. Actual port status during event processing
* is taking precedence over reported events, i.e. may cause ignoring some
* events.
*/
#define SATA_EVENT_MAX_MSG_LENGTH 79
void
sata_hba_event_notify(dev_info_t *dip, sata_device_t *sata_device, int event)
{
sata_hba_inst_t *sata_hba_inst = NULL;
sata_address_t *saddr;
sata_drive_info_t *sdinfo;
sata_port_stats_t *pstats;
sata_cport_info_t *cportinfo;
sata_pmport_info_t *pmportinfo;
int cport, pmport;
char buf1[SATA_EVENT_MAX_MSG_LENGTH + 1];
char buf2[SATA_EVENT_MAX_MSG_LENGTH + 1];
char *lcp;
static char *err_msg_evnt_1 =
"sata_hba_event_notify: invalid port event 0x%x ";
static char *err_msg_evnt_2 =
"sata_hba_event_notify: invalid device event 0x%x ";
int linkevent;
/*
* There is a possibility that an event will be generated on HBA
* that has not completed attachment or is detaching. We still want
* to process events until HBA is detached.
*/
mutex_enter(&sata_mutex);
for (sata_hba_inst = sata_hba_list; sata_hba_inst != NULL;
sata_hba_inst = sata_hba_inst->satahba_next) {
if (SATA_DIP(sata_hba_inst) == dip)
if (sata_hba_inst->satahba_attached == 1)
break;
}
mutex_exit(&sata_mutex);
if (sata_hba_inst == NULL)
/* HBA not attached */
return;
ASSERT(sata_device != NULL);
/*
* Validate address before - do not proceed with invalid address.
*/
saddr = &sata_device->satadev_addr;
if (saddr->cport >= SATA_NUM_CPORTS(sata_hba_inst))
return;
if (saddr->qual == SATA_ADDR_PMPORT ||
saddr->qual == SATA_ADDR_DPMPORT)
/* Port Multiplier not supported yet */
return;
cport = saddr->cport;
pmport = saddr->pmport;
buf1[0] = buf2[0] = '\0';
/*
* If event relates to port or device, check port state.
* Port has to be initialized, or we cannot accept an event.
*/
if ((saddr->qual & (SATA_ADDR_CPORT | SATA_ADDR_PMPORT |
SATA_ADDR_DCPORT | SATA_ADDR_DPMPORT)) != 0) {
if ((saddr->qual & (SATA_ADDR_CPORT | SATA_ADDR_DCPORT)) != 0) {
mutex_enter(&sata_hba_inst->satahba_mutex);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
mutex_exit(&sata_hba_inst->satahba_mutex);
if (cportinfo == NULL || cportinfo->cport_state == 0)
return;
} else {
mutex_enter(&sata_hba_inst->satahba_mutex);
pmportinfo = SATA_PMPORT_INFO(sata_hba_inst,
cport, pmport);
mutex_exit(&sata_hba_inst->satahba_mutex);
if (pmportinfo == NULL || pmportinfo->pmport_state == 0)
return;
}
}
/*
* Events refer to devices, ports and controllers - each has
* unique address. Events for different addresses cannot be combined.
*/
if (saddr->qual & (SATA_ADDR_CPORT | SATA_ADDR_PMPORT)) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
/* qualify this event(s) */
if ((event & SATA_EVNT_PORT_EVENTS) == 0) {
/* Invalid event for the device port */
(void) sprintf(buf2, err_msg_evnt_1,
event & SATA_EVNT_PORT_EVENTS);
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
goto event_info;
}
if (saddr->qual == SATA_ADDR_CPORT) {
/* Controller's device port event */
(SATA_CPORT_INFO(sata_hba_inst, cport))->
cport_event_flags |=
event & SATA_EVNT_PORT_EVENTS;
pstats =
&(SATA_CPORT_INFO(sata_hba_inst, cport))->
cport_stats;
} else {
/* Port multiplier's device port event */
(SATA_PMPORT_INFO(sata_hba_inst, cport, pmport))->
pmport_event_flags |=
event & SATA_EVNT_PORT_EVENTS;
pstats =
&(SATA_PMPORT_INFO(sata_hba_inst, cport, pmport))->
pmport_stats;
}
/*
* Add to statistics and log the message. We have to do it
* here rather than in the event daemon, because there may be
* multiple events occuring before they are processed.
*/
linkevent = event &
(SATA_EVNT_LINK_LOST | SATA_EVNT_LINK_ESTABLISHED);
if (linkevent) {
if (linkevent == (SATA_EVNT_LINK_LOST |
SATA_EVNT_LINK_ESTABLISHED)) {
/* This is likely event combination */
(void) strlcat(buf1, "link lost/established, ",
SATA_EVENT_MAX_MSG_LENGTH);
if (pstats->link_lost < 0xffffffffffffffffULL)
pstats->link_lost++;
if (pstats->link_established <
0xffffffffffffffffULL)
pstats->link_established++;
linkevent = 0;
} else if (linkevent & SATA_EVNT_LINK_LOST) {
(void) strlcat(buf1, "link lost, ",
SATA_EVENT_MAX_MSG_LENGTH);
if (pstats->link_lost < 0xffffffffffffffffULL)
pstats->link_lost++;
} else {
(void) strlcat(buf1, "link established, ",
SATA_EVENT_MAX_MSG_LENGTH);
if (pstats->link_established <
0xffffffffffffffffULL)
pstats->link_established++;
}
}
if (event & SATA_EVNT_DEVICE_ATTACHED) {
(void) strlcat(buf1, "device attached, ",
SATA_EVENT_MAX_MSG_LENGTH);
if (pstats->device_attached < 0xffffffffffffffffULL)
pstats->device_attached++;
}
if (event & SATA_EVNT_DEVICE_DETACHED) {
(void) strlcat(buf1, "device detached, ",
SATA_EVENT_MAX_MSG_LENGTH);
if (pstats->device_detached < 0xffffffffffffffffULL)
pstats->device_detached++;
}
if (event & SATA_EVNT_PWR_LEVEL_CHANGED) {
SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
"port %d power level changed", cport);
if (pstats->port_pwr_changed < 0xffffffffffffffffULL)
pstats->port_pwr_changed++;
}
if ((event & ~SATA_EVNT_PORT_EVENTS) != 0) {
/* There should be no other events for this address */
(void) sprintf(buf2, err_msg_evnt_1,
event & ~SATA_EVNT_PORT_EVENTS);
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
} else if (saddr->qual & (SATA_ADDR_DCPORT | SATA_ADDR_DPMPORT)) {
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
/* qualify this event */
if ((event & SATA_EVNT_DEVICE_RESET) == 0) {
/* Invalid event for a device */
(void) sprintf(buf2, err_msg_evnt_2,
event & SATA_EVNT_DEVICE_RESET);
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
goto event_info;
}
/* drive event */
sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
if (sdinfo != NULL) {
if (event & SATA_EVNT_DEVICE_RESET) {
(void) strlcat(buf1, "device reset, ",
SATA_EVENT_MAX_MSG_LENGTH);
if (sdinfo->satadrv_stats.drive_reset <
0xffffffffffffffffULL)
sdinfo->satadrv_stats.drive_reset++;
sdinfo->satadrv_event_flags |=
SATA_EVNT_DEVICE_RESET;
}
}
if ((event & ~SATA_EVNT_DEVICE_RESET) != 0) {
/* Invalid event for a device */
(void) sprintf(buf2, err_msg_evnt_2,
event & ~SATA_EVNT_DRIVE_EVENTS);
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
} else {
if (saddr->qual != SATA_ADDR_NULL) {
/* Wrong address qualifier */
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_event_notify: invalid address 0x%x",
*(uint32_t *)saddr));
return;
}
if ((event & SATA_EVNT_CONTROLLER_EVENTS) == 0 ||
(event & ~SATA_EVNT_CONTROLLER_EVENTS) != 0) {
/* Invalid event for the controller */
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_hba_event_notify: invalid event 0x%x for "
"controller",
event & SATA_EVNT_CONTROLLER_EVENTS));
return;
}
buf1[0] = '\0';
/* This may be a frequent and not interesting event */
SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
"controller power level changed\n", NULL);
mutex_enter(&sata_hba_inst->satahba_mutex);
if (sata_hba_inst->satahba_stats.ctrl_pwr_change <
0xffffffffffffffffULL)
sata_hba_inst->satahba_stats.ctrl_pwr_change++;
sata_hba_inst->satahba_event_flags |=
SATA_EVNT_PWR_LEVEL_CHANGED;
mutex_exit(&sata_hba_inst->satahba_mutex);
}
/*
* If we got here, there is something to do with this HBA
* instance.
*/
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN; /* global event indicator */
mutex_exit(&sata_mutex);
/* Tickle event thread */
mutex_enter(&sata_event_mutex);
if (sata_event_thread_active == 0)
cv_signal(&sata_event_cv);
mutex_exit(&sata_event_mutex);
event_info:
if (buf1[0] != '\0') {
lcp = strrchr(buf1, ',');
if (lcp != NULL)
*lcp = '\0';
}
if (saddr->qual == SATA_ADDR_CPORT ||
saddr->qual == SATA_ADDR_DCPORT) {
if (buf1[0] != '\0') {
sata_log(sata_hba_inst, CE_NOTE, "port %d: %s\n",
cport, buf1);
}
if (buf2[0] != '\0') {
sata_log(sata_hba_inst, CE_NOTE, "port %d: %s\n",
cport, buf2);
}
} else if (saddr->qual == SATA_ADDR_PMPORT ||
saddr->qual == SATA_ADDR_DPMPORT) {
if (buf1[0] != '\0') {
sata_log(sata_hba_inst, CE_NOTE,
"port %d pmport %d: %s\n", cport, pmport, buf1);
}
if (buf2[0] != '\0') {
sata_log(sata_hba_inst, CE_NOTE,
"port %d pmport %d: %s\n", cport, pmport, buf2);
}
}
}
/*
* Event processing thread.
* Arg is a pointer to the sata_hba_list pointer.
* It is not really needed, because sata_hba_list is global and static
*/
static void
sata_event_daemon(void *arg)
{
#ifndef __lock_lint
_NOTE(ARGUNUSED(arg))
#endif
sata_hba_inst_t *sata_hba_inst;
clock_t lbolt;
SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
"SATA event daemon started\n", NULL);
loop:
/*
* Process events here. Walk through all registered HBAs
*/
mutex_enter(&sata_mutex);
for (sata_hba_inst = sata_hba_list; sata_hba_inst != NULL;
sata_hba_inst = sata_hba_inst->satahba_next) {
ASSERT(sata_hba_inst != NULL);
mutex_enter(&sata_hba_inst->satahba_mutex);
if (sata_hba_inst->satahba_attached == 0 ||
(sata_hba_inst->satahba_event_flags &
SATA_EVNT_SKIP) != 0) {
mutex_exit(&sata_hba_inst->satahba_mutex);
continue;
}
if (sata_hba_inst->satahba_event_flags & SATA_EVNT_MAIN) {
sata_hba_inst->satahba_event_flags |= SATA_EVNT_SKIP;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_exit(&sata_mutex);
/* Got the controller with pending event */
sata_process_controller_events(sata_hba_inst);
/*
* Since global mutex was released, there is a
* possibility that HBA list has changed, so start
* over from the top. Just processed controller
* will be passed-over because of the SKIP flag.
*/
goto loop;
}
mutex_exit(&sata_hba_inst->satahba_mutex);
}
/* Clear SKIP flag in all controllers */
for (sata_hba_inst = sata_hba_list; sata_hba_inst != NULL;
sata_hba_inst = sata_hba_inst->satahba_next) {
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags &= ~SATA_EVNT_SKIP;
mutex_exit(&sata_hba_inst->satahba_mutex);
}
mutex_exit(&sata_mutex);
SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
"SATA EVENT DAEMON suspending itself", NULL);
#ifdef SATA_DEBUG
if ((sata_func_enable & SATA_ENABLE_PROCESS_EVENTS) == 0) {
sata_log(sata_hba_inst, CE_WARN,
"SATA EVENTS PROCESSING DISABLED\n");
thread_exit(); /* Daemon will not run again */
}
#endif
mutex_enter(&sata_event_mutex);
sata_event_thread_active = 0;
mutex_exit(&sata_event_mutex);
/*
* Go to sleep/suspend itself and wake up either because new event or
* wait timeout. Exit if there is a termination request (driver
* unload).
*/
do {
lbolt = ddi_get_lbolt();
lbolt += drv_usectohz(SATA_EVNT_DAEMON_SLEEP_TIME);
mutex_enter(&sata_event_mutex);
(void) cv_timedwait(&sata_event_cv, &sata_event_mutex, lbolt);
if (sata_event_thread_active != 0) {
mutex_exit(&sata_event_mutex);
continue;
}
/* Check if it is time to go away */
if (sata_event_thread_terminate == 1) {
/*
* It is up to the thread setting above flag to make
* sure that this thread is not killed prematurely.
*/
sata_event_thread_terminate = 0;
sata_event_thread = NULL;
mutex_exit(&sata_event_mutex);
SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
"SATA_EVENT_DAEMON_TERMINATING", NULL);
thread_exit(); { _NOTE(NOT_REACHED) }
}
mutex_exit(&sata_event_mutex);
} while (!(sata_event_pending & SATA_EVNT_MAIN));
mutex_enter(&sata_event_mutex);
sata_event_thread_active = 1;
mutex_exit(&sata_event_mutex);
mutex_enter(&sata_mutex);
sata_event_pending &= ~SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
"SATA EVENT DAEMON READY TO PROCESS EVENT", NULL);
goto loop;
}
/*
* Specific HBA instance event processing.
*
* NOTE: At the moment, device event processing is limited to hard disks
* only.
* cports only are supported - no pmports.
*/
static void
sata_process_controller_events(sata_hba_inst_t *sata_hba_inst)
{
int ncport;
uint32_t event_flags;
sata_address_t *saddr;
sata_cport_info_t *cportinfo;
SATADBG1(SATA_DBG_EVENTS_CNTRL, sata_hba_inst,
"Processing controller %d event(s)",
ddi_get_instance(SATA_DIP(sata_hba_inst)));
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags &= ~SATA_EVNT_MAIN;
event_flags = sata_hba_inst->satahba_event_flags;
mutex_exit(&sata_hba_inst->satahba_mutex);
/*
* Process controller power change first
* HERE
*/
if (event_flags & SATA_EVNT_PWR_LEVEL_CHANGED)
sata_process_cntrl_pwr_level_change(sata_hba_inst);
/*
* Search through ports/devices to identify affected port/device.
* We may have to process events for more than one port/device.
*/
for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst); ncport++) {
/*
* Not all ports may be processed in attach by the time we
* get an event. Check if port info is initialized.
*/
mutex_enter(&sata_hba_inst->satahba_mutex);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, ncport);
mutex_exit(&sata_hba_inst->satahba_mutex);
if (cportinfo == NULL || cportinfo->cport_state == NULL)
continue;
/* We have initialized controller port info */
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
event_flags = (SATA_CPORT_INFO(sata_hba_inst, ncport))->
cport_event_flags;
/* Check if port was locked by IOCTL processing */
if (event_flags & SATA_APCTL_LOCK_PORT_BUSY) {
/*
* We ignore port events because port is busy
* with AP control processing. Set again
* controller and main event flag, so that
* events may be processed by the next daemon
* run.
*/
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
SATADBG1(SATA_DBG_EVENTS_PROCPST, sata_hba_inst,
"Event processing postponed until "
"AP control processing completes",
NULL);
/* Check other ports */
continue;
} else {
/*
* Set BSY flag so that AP control would not
* interfere with events processing for
* this port.
*/
(SATA_CPORT_INFO(sata_hba_inst, ncport))->
cport_event_flags |= SATA_EVNT_LOCK_PORT_BUSY;
}
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
saddr = &(SATA_CPORT_INFO(sata_hba_inst, ncport))->cport_addr;
if ((event_flags &
(SATA_EVNT_PORT_EVENTS | SATA_EVNT_DRIVE_EVENTS)) != 0) {
/*
* Got port event.
* We need some hierarchy of event processing as they
* are affecting each other:
* 1. port failed
* 2. device detached/attached
* 3. link events - link events may trigger device
* detached or device attached events in some
* circumstances.
* 4. port power level changed
*/
if (event_flags & SATA_EVNT_PORT_FAILED) {
sata_process_port_failed_event(sata_hba_inst,
saddr);
}
if (event_flags & SATA_EVNT_DEVICE_DETACHED) {
sata_process_device_detached(sata_hba_inst,
saddr);
}
if (event_flags & SATA_EVNT_DEVICE_ATTACHED) {
sata_process_device_attached(sata_hba_inst,
saddr);
}
if (event_flags &
(SATA_EVNT_LINK_ESTABLISHED |
SATA_EVNT_LINK_LOST)) {
sata_process_port_link_events(sata_hba_inst,
saddr);
}
if (event_flags & SATA_EVNT_PWR_LEVEL_CHANGED) {
sata_process_port_pwr_change(sata_hba_inst,
saddr);
}
if (event_flags & SATA_EVNT_TARGET_NODE_CLEANUP) {
sata_process_target_node_cleanup(
sata_hba_inst, saddr);
}
if (event_flags & SATA_EVNT_AUTOONLINE_DEVICE) {
sata_process_device_autoonline(
sata_hba_inst, saddr);
}
}
mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
if ((SATA_CPORT_DEV_TYPE(sata_hba_inst, ncport) !=
SATA_DTYPE_NONE) &&
(SATA_CPORT_DRV_INFO(sata_hba_inst, ncport) != NULL)) {
if (SATA_CPORT_DRV_INFO(sata_hba_inst, ncport)->
satadrv_event_flags &
(SATA_EVNT_DEVICE_RESET |
SATA_EVNT_INPROC_DEVICE_RESET)) {
/* Have device event */
sata_process_device_reset(sata_hba_inst,
saddr);
}
}
/* Release PORT_BUSY flag */
(SATA_CPORT_INFO(sata_hba_inst, ncport))->
cport_event_flags &= ~SATA_EVNT_LOCK_PORT_BUSY;
mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
} /* End of loop through the controller SATA ports */
}
/*
* Process HBA power level change reported by HBA driver.
* Not implemented at this time - event is ignored.
*/
static void
sata_process_cntrl_pwr_level_change(sata_hba_inst_t *sata_hba_inst)
{
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing controller power level change", NULL);
/* Ignoring it for now */
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags &= ~SATA_EVNT_PWR_LEVEL_CHANGED;
mutex_exit(&sata_hba_inst->satahba_mutex);
}
/*
* Process port power level change reported by HBA driver.
* Not implemented at this time - event is ignored.
*/
static void
sata_process_port_pwr_change(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_cport_info_t *cportinfo;
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port power level change", NULL);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
/* Reset event flag */
cportinfo->cport_event_flags &= ~SATA_EVNT_PWR_LEVEL_CHANGED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
}
/*
* Process port failure reported by HBA driver.
* cports support only - no pmports.
*/
static void
sata_process_port_failed_event(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_cport_info_t *cportinfo;
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
/* Reset event flag first */
cportinfo->cport_event_flags &= ~SATA_EVNT_PORT_FAILED;
/* If the port is in SHUTDOWN or FAILED state, ignore this event. */
if ((cportinfo->cport_state &
(SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) == 0) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
return;
}
/* Fail the port */
cportinfo->cport_state = SATA_PSTATE_FAILED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
sata_log(sata_hba_inst, CE_WARN, "SATA port %d failed", saddr->cport);
}
/*
* Device Reset Event processing.
* The seqeunce is managed by 3 stage flags:
* - reset event reported,
* - reset event being processed,
* - request to clear device reset state.
*
* NOTE: This function has to be entered with cport mutex held. It exits with
* mutex held as well, but can release mutex during the processing.
*/
static void
sata_process_device_reset(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_drive_info_t old_sdinfo; /* local copy of the drive info */
sata_drive_info_t *sdinfo;
sata_cport_info_t *cportinfo;
sata_device_t sata_device;
int rval_probe, rval_set;
/* We only care about host sata cport for now */
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst, saddr->cport);
/*
* If the port is in SHUTDOWN or FAILED state, or device is in FAILED
* state, ignore reset event.
*/
if (((cportinfo->cport_state &
(SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) ||
(sdinfo->satadrv_state & SATA_DSTATE_FAILED) != 0) {
sdinfo->satadrv_event_flags &=
~(SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET);
return;
}
if ((SATA_CPORT_DEV_TYPE(sata_hba_inst, saddr->cport) &
SATA_VALID_DEV_TYPE) == 0) {
/*
* This should not happen - coding error.
* But we can recover, so do not panic, just clean up
* and if in debug mode, log the message.
*/
#ifdef SATA_DEBUG
sata_log(sata_hba_inst, CE_WARN,
"sata_process_device_reset: "
"Invalid device type with sdinfo!", NULL);
#endif
sdinfo->satadrv_event_flags = 0;
return;
}
#ifdef SATA_DEBUG
if ((sdinfo->satadrv_event_flags &
(SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET)) == 0) {
/* Nothing to do */
/* Something is weird - why we are processing dev reset? */
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"No device reset event!!!!", NULL);
return;
}
if ((sdinfo->satadrv_event_flags &
(SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET)) ==
(SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET)) {
/* Something is weird - new device reset event */
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Overlapping device reset events!", NULL);
}
#endif
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d device reset", saddr->cport);
/* Clear event flag */
sdinfo->satadrv_event_flags &= ~SATA_EVNT_DEVICE_RESET;
/* It seems that we always need to check the port state first */
sata_device.satadev_rev = SATA_DEVICE_REV;
sata_device.satadev_addr = *saddr;
/*
* We have to exit mutex, because the HBA probe port function may
* block on its own mutex.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
rval_probe = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, &sata_device);
if (rval_probe != SATA_SUCCESS) {
/* Something went wrong? Fail the port */
cportinfo->cport_state = SATA_PSTATE_FAILED;
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst, saddr->cport);
if (sdinfo != NULL)
sdinfo->satadrv_event_flags = 0;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
SATA_LOG_D((sata_hba_inst, CE_WARN,
"SATA port %d probing failed",
saddr->cport));
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
return;
}
if ((sata_device.satadev_scr.sstatus &
SATA_PORT_DEVLINK_UP_MASK) !=
SATA_PORT_DEVLINK_UP ||
sata_device.satadev_type == SATA_DTYPE_NONE) {
/*
* No device to process, anymore. Some other event processing
* would or have already performed port info cleanup.
* To be safe (HBA may need it), request clearing device
* reset condition.
*/
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst, saddr->cport);
if (sdinfo != NULL) {
sdinfo->satadrv_event_flags &=
~SATA_EVNT_INPROC_DEVICE_RESET;
sdinfo->satadrv_event_flags |=
SATA_EVNT_CLEAR_DEVICE_RESET;
}
return;
}
sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst, saddr->cport);
if (sdinfo == NULL) {
return;
}
if ((sdinfo->satadrv_event_flags &
SATA_EVNT_INPROC_DEVICE_RESET) == 0) {
/*
* Start tracking time for device feature restoration and
* identification. Save current time (lbolt value).
*/
sdinfo->satadrv_reset_time = ddi_get_lbolt();
}
/* Mark device reset processing as active */
sdinfo->satadrv_event_flags |= SATA_EVNT_INPROC_DEVICE_RESET;
old_sdinfo = *sdinfo; /* local copy of the drive info */
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
rval_set = sata_set_drive_features(sata_hba_inst, &old_sdinfo, 1);
if (rval_set != SATA_SUCCESS) {
/*
* Restoring drive setting failed.
* Probe the port first, to check if the port state has changed
*/
sata_device.satadev_rev = SATA_DEVICE_REV;
sata_device.satadev_addr = *saddr;
sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
/* probe port */
rval_probe = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
if (rval_probe == SATA_SUCCESS &&
(sata_device.satadev_state &
(SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) == 0 &&
(sata_device.satadev_scr.sstatus &
SATA_PORT_DEVLINK_UP_MASK) == SATA_PORT_DEVLINK_UP &&
sata_device.satadev_type != SATA_DTYPE_NONE) {
/*
* We may retry this a bit later - in-process reset
* condition should be already set.
* Track retry time for device identification.
*/
if ((cportinfo->cport_dev_type &
SATA_VALID_DEV_TYPE) != 0 &&
SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL &&
sdinfo->satadrv_reset_time != 0) {
clock_t cur_time = ddi_get_lbolt();
/*
* If the retry time limit was not
* exceeded, retry.
*/
if ((cur_time - sdinfo->satadrv_reset_time) <
drv_usectohz(SATA_DEV_REPROBE_TIMEOUT)) {
mutex_enter(
&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |=
SATA_EVNT_MAIN;
mutex_exit(
&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
return;
}
if (rval_set == SATA_RETRY) {
/*
* Setting drive features failed, but
* the drive is still accessible,
* so emit a warning message before
* return.
*/
mutex_exit(&SATA_CPORT_INFO(
sata_hba_inst,
saddr->cport)->cport_mutex);
goto done;
}
}
/* Fail the drive */
sdinfo->satadrv_state = SATA_DSTATE_FAILED;
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d - device failed",
saddr->cport);
}
/*
* No point of retrying - device failed or some other event
* processing or already did or will do port info cleanup.
* To be safe (HBA may need it),
* request clearing device reset condition.
*/
sdinfo->satadrv_event_flags |= SATA_EVNT_CLEAR_DEVICE_RESET;
sdinfo->satadrv_event_flags &= ~SATA_EVNT_INPROC_DEVICE_RESET;
sdinfo->satadrv_reset_time = 0;
return;
}
done:
/*
* If setting of drive features failed, but the drive is still
* accessible, emit a warning message.
*/
if (rval_set == SATA_RETRY) {
sata_log(sata_hba_inst, CE_WARN,
"SATA device at port %d - desired setting could not be "
"restored after reset. Device may not operate as expected.",
saddr->cport);
}
/*
* Raise the flag indicating that the next sata command could
* be sent with SATA_CLEAR_DEV_RESET_STATE flag, if no new device
* reset is reported.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
if (SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
sdinfo->satadrv_reset_time = 0;
if ((cportinfo->cport_dev_type & SATA_VALID_DEV_TYPE) != 0) {
sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
sdinfo->satadrv_event_flags &=
~SATA_EVNT_INPROC_DEVICE_RESET;
sdinfo->satadrv_event_flags |=
SATA_EVNT_CLEAR_DEVICE_RESET;
}
}
}
/*
* Port Link Events processing.
* Every link established event may involve device reset (due to
* COMRESET signal, equivalent of the hard reset) so arbitrarily
* set device reset event for an attached device (if any).
* If the port is in SHUTDOWN or FAILED state, ignore link events.
*
* The link established event processing varies, depending on the state
* of the target node, HBA hotplugging capabilities, state of the port.
* If the link is not active, the link established event is ignored.
* If HBA cannot detect device attachment and there is no target node,
* the link established event triggers device attach event processing.
* Else, link established event triggers device reset event processing.
*
* The link lost event processing varies, depending on a HBA hotplugging
* capability and the state of the port (link active or not active).
* If the link is active, the lost link event is ignored.
* If HBA cannot detect device removal, the lost link event triggers
* device detached event processing after link lost timeout.
* Else, the event is ignored.
*
* NOTE: Only cports are processed for now, i.e. no port multiplier ports
*/
static void
sata_process_port_link_events(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_device_t sata_device;
sata_cport_info_t *cportinfo;
sata_drive_info_t *sdinfo;
uint32_t event_flags;
int rval;
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d link event(s)", saddr->cport);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
event_flags = cportinfo->cport_event_flags;
/* Reset event flags first */
cportinfo->cport_event_flags &=
~(SATA_EVNT_LINK_ESTABLISHED | SATA_EVNT_LINK_LOST);
/* If the port is in SHUTDOWN or FAILED state, ignore link events. */
if ((cportinfo->cport_state &
(SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
return;
}
/*
* For the sanity sake get current port state.
* Set device address only. Other sata_device fields should be
* set by HBA driver.
*/
sata_device.satadev_rev = SATA_DEVICE_REV;
sata_device.satadev_addr = *saddr;
/*
* We have to exit mutex, because the HBA probe port function may
* block on its own mutex.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, &sata_device);
if (rval != SATA_SUCCESS) {
/* Something went wrong? Fail the port */
cportinfo->cport_state = SATA_PSTATE_FAILED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
SATA_LOG_D((sata_hba_inst, CE_WARN,
"SATA port %d probing failed",
saddr->cport));
/*
* We may want to release device info structure, but
* it is not necessary.
*/
return;
} else {
/* port probed successfully */
cportinfo->cport_state |= SATA_STATE_PROBED | SATA_STATE_READY;
}
if (event_flags & SATA_EVNT_LINK_ESTABLISHED) {
if ((sata_device.satadev_scr.sstatus &
SATA_PORT_DEVLINK_UP_MASK) != SATA_PORT_DEVLINK_UP) {
/* Ignore event */
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Ignoring port %d link established event - "
"link down",
saddr->cport);
goto linklost;
}
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d link established event",
saddr->cport);
/*
* For the sanity sake check if a device is attached - check
* return state of a port probing.
*/
if (sata_device.satadev_type != SATA_DTYPE_NONE &&
sata_device.satadev_type != SATA_DTYPE_PMULT) {
/*
* HBA port probe indicated that there is a device
* attached. Check if the framework had device info
* structure attached for this device.
*/
if (cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
ASSERT(SATA_CPORTINFO_DRV_INFO(cportinfo) !=
NULL);
sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
if ((sdinfo->satadrv_type &
SATA_VALID_DEV_TYPE) != 0) {
/*
* Dev info structure is present.
* If dev_type is set to known type in
* the framework's drive info struct
* then the device existed before and
* the link was probably lost
* momentarily - in such case
* we may want to check device
* identity.
* Identity check is not supported now.
*
* Link established event
* triggers device reset event.
*/
(SATA_CPORTINFO_DRV_INFO(cportinfo))->
satadrv_event_flags |=
SATA_EVNT_DEVICE_RESET;
}
} else if (cportinfo->cport_dev_type ==
SATA_DTYPE_NONE) {
/*
* We got new device attached! If HBA does not
* generate device attached events, trigger it
* here.
*/
if (!(SATA_FEATURES(sata_hba_inst) &
SATA_CTLF_HOTPLUG)) {
cportinfo->cport_event_flags |=
SATA_EVNT_DEVICE_ATTACHED;
}
}
/* Reset link lost timeout */
cportinfo->cport_link_lost_time = 0;
}
}
linklost:
if (event_flags & SATA_EVNT_LINK_LOST) {
if ((sata_device.satadev_scr.sstatus &
SATA_PORT_DEVLINK_UP_MASK) == SATA_PORT_DEVLINK_UP) {
/* Ignore event */
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Ignoring port %d link lost event - link is up",
saddr->cport);
goto done;
}
#ifdef SATA_DEBUG
if (cportinfo->cport_link_lost_time == 0) {
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d link lost event",
saddr->cport);
}
#endif
/*
* When HBA cannot generate device attached/detached events,
* we need to track link lost time and eventually generate
* device detach event.
*/
if (!(SATA_FEATURES(sata_hba_inst) & SATA_CTLF_HOTPLUG)) {
/* We are tracking link lost time */
if (cportinfo->cport_link_lost_time == 0) {
/* save current time (lbolt value) */
cportinfo->cport_link_lost_time =
ddi_get_lbolt();
/* just keep link lost event */
cportinfo->cport_event_flags |=
SATA_EVNT_LINK_LOST;
} else {
clock_t cur_time = ddi_get_lbolt();
if ((cur_time -
cportinfo->cport_link_lost_time) >=
drv_usectohz(
SATA_EVNT_LINK_LOST_TIMEOUT)) {
/* trigger device detach event */
cportinfo->cport_event_flags |=
SATA_EVNT_DEVICE_DETACHED;
cportinfo->cport_link_lost_time = 0;
SATADBG1(SATA_DBG_EVENTS,
sata_hba_inst,
"Triggering port %d "
"device detached event",
saddr->cport);
} else {
/* keep link lost event */
cportinfo->cport_event_flags |=
SATA_EVNT_LINK_LOST;
}
}
}
/*
* We could change port state to disable/delay access to
* the attached device until the link is recovered.
*/
}
done:
event_flags = cportinfo->cport_event_flags;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
if (event_flags != 0) {
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
}
}
/*
* Device Detached Event processing.
* Port is probed to find if a device is really gone. If so,
* the device info structure is detached from the SATA port info structure
* and released.
* Port status is updated.
*
* NOTE: Process cports event only, no port multiplier ports.
*/
static void
sata_process_device_detached(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_cport_info_t *cportinfo;
sata_drive_info_t *sdevinfo;
sata_device_t sata_device;
dev_info_t *tdip;
int rval;
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d device detached", saddr->cport);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
/* Clear event flag */
cportinfo->cport_event_flags &= ~SATA_EVNT_DEVICE_DETACHED;
/* If the port is in SHUTDOWN or FAILED state, ignore detach event. */
if ((cportinfo->cport_state &
(SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
return;
}
/* For sanity, re-probe the port */
sata_device.satadev_rev = SATA_DEVICE_REV;
sata_device.satadev_addr = *saddr;
/*
* We have to exit mutex, because the HBA probe port function may
* block on its own mutex.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, &sata_device);
if (rval != SATA_SUCCESS) {
/* Something went wrong? Fail the port */
cportinfo->cport_state = SATA_PSTATE_FAILED;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
SATA_LOG_D((sata_hba_inst, CE_WARN,
"SATA port %d probing failed",
saddr->cport));
/*
* We may want to release device info structure, but
* it is not necessary.
*/
return;
} else {
/* port probed successfully */
cportinfo->cport_state |= SATA_STATE_PROBED | SATA_STATE_READY;
}
/*
* Check if a device is still attached. For sanity, check also
* link status - if no link, there is no device.
*/
if ((sata_device.satadev_scr.sstatus & SATA_PORT_DEVLINK_UP_MASK) ==
SATA_PORT_DEVLINK_UP && sata_device.satadev_type !=
SATA_DTYPE_NONE) {
/*
* Device is still attached - ignore detach event.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Ignoring detach - device still attached to port %d",
sata_device.satadev_addr.cport);
return;
}
/*
* We need to detach and release device info structure here
*/
if (SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
sdevinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
(void) kmem_free((void *)sdevinfo,
sizeof (sata_drive_info_t));
}
cportinfo->cport_dev_type = SATA_DTYPE_NONE;
/*
* Device cannot be reached anymore, even if the target node may be
* still present.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
sata_log(sata_hba_inst, CE_WARN, "SATA device detached at port %d",
sata_device.satadev_addr.cport);
/*
* Try to offline a device and remove target node if it still exists
*/
tdip = sata_get_target_dip(SATA_DIP(sata_hba_inst), saddr->cport);
if (tdip != NULL) {
/*
* Target node exists. Unconfigure device then remove
* the target node (one ndi operation).
*/
if (ndi_devi_offline(tdip, NDI_DEVI_REMOVE) != NDI_SUCCESS) {
/*
* PROBLEM - no device, but target node remained
* This happens when the file was open or node was
* waiting for resources.
*/
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_process_device_detached: "
"Failed to remove target node for "
"detached SATA device."));
/*
* Set target node state to DEVI_DEVICE_REMOVED.
* But re-check first that the node still exists.
*/
tdip = sata_get_target_dip(SATA_DIP(sata_hba_inst),
saddr->cport);
if (tdip != NULL) {
sata_set_device_removed(tdip);
/*
* Instruct event daemon to retry the
* cleanup later.
*/
sata_set_target_node_cleanup(sata_hba_inst,
&sata_device.satadev_addr);
}
}
}
/*
* Generate sysevent - EC_DR / ESC_DR_AP_STATE_CHANGE
* with the hint: SE_HINT_REMOVE
*/
sata_gen_sysevent(sata_hba_inst, saddr, SE_HINT_REMOVE);
}
/*
* Device Attached Event processing.
* Port state is checked to verify that a device is really attached. If so,
* the device info structure is created and attached to the SATA port info
* structure.
*
* If attached device cannot be identified or set-up, the retry for the
* attach processing is set-up. Subsequent daemon run would try again to
* identify the device, until the time limit is reached
* (SATA_DEV_IDENTIFY_TIMEOUT).
*
* This function cannot be called in interrupt context (it may sleep).
*
* NOTE: Process cports event only, no port multiplier ports.
*/
static void
sata_process_device_attached(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_cport_info_t *cportinfo;
sata_drive_info_t *sdevinfo;
sata_device_t sata_device;
dev_info_t *tdip;
uint32_t event_flags;
int rval;
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d device attached", saddr->cport);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
/* Clear attach event flag first */
cportinfo->cport_event_flags &= ~SATA_EVNT_DEVICE_ATTACHED;
/* If the port is in SHUTDOWN or FAILED state, ignore event. */
if ((cportinfo->cport_state &
(SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) {
cportinfo->cport_dev_attach_time = 0;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
return;
}
/*
* If the sata_drive_info structure is found attached to the port info,
* despite the fact the device was removed and now it is re-attached,
* the old drive info structure was not removed.
* Arbitrarily release device info structure.
*/
if (SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
sdevinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
(void) kmem_free((void *)sdevinfo,
sizeof (sata_drive_info_t));
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Arbitrarily detaching old device info.", NULL);
}
cportinfo->cport_dev_type = SATA_DTYPE_NONE;
/* For sanity, re-probe the port */
sata_device.satadev_rev = SATA_DEVICE_REV;
sata_device.satadev_addr = *saddr;
/*
* We have to exit mutex, because the HBA probe port function may
* block on its own mutex.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
(SATA_DIP(sata_hba_inst), &sata_device);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
sata_update_port_info(sata_hba_inst, &sata_device);
if (rval != SATA_SUCCESS) {
/* Something went wrong? Fail the port */
cportinfo->cport_state = SATA_PSTATE_FAILED;
cportinfo->cport_dev_attach_time = 0;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
SATA_LOG_D((sata_hba_inst, CE_WARN,
"SATA port %d probing failed",
saddr->cport));
return;
} else {
/* port probed successfully */
cportinfo->cport_state |= SATA_STATE_PROBED | SATA_STATE_READY;
}
/*
* Check if a device is still attached. For sanity, check also
* link status - if no link, there is no device.
*/
if ((sata_device.satadev_scr.sstatus & SATA_PORT_DEVLINK_UP_MASK) !=
SATA_PORT_DEVLINK_UP || sata_device.satadev_type ==
SATA_DTYPE_NONE) {
/*
* No device - ignore attach event.
*/
cportinfo->cport_dev_attach_time = 0;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Ignoring attach - no device connected to port %d",
sata_device.satadev_addr.cport);
return;
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
/*
* Generate sysevent - EC_DR / ESC_DR_AP_STATE_CHANGE
* with the hint: SE_HINT_INSERT
*/
sata_gen_sysevent(sata_hba_inst, saddr, SE_HINT_INSERT);
/*
* Port reprobing will take care of the creation of the device
* info structure and determination of the device type.
*/
sata_device.satadev_addr = *saddr;
(void) sata_reprobe_port(sata_hba_inst, &sata_device,
SATA_DEV_IDENTIFY_NORETRY);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
cport_mutex);
if ((cportinfo->cport_state & SATA_STATE_READY) &&
(cportinfo->cport_dev_type != SATA_DTYPE_NONE)) {
/* Some device is attached to the port */
if (cportinfo->cport_dev_type == SATA_DTYPE_UNKNOWN) {
/*
* A device was not successfully attached.
* Track retry time for device identification.
*/
if (cportinfo->cport_dev_attach_time != 0) {
clock_t cur_time = ddi_get_lbolt();
/*
* If the retry time limit was not exceeded,
* reinstate attach event.
*/
if ((cur_time -
cportinfo->cport_dev_attach_time) <
drv_usectohz(
SATA_DEV_IDENTIFY_TIMEOUT)) {
/* OK, restore attach event */
cportinfo->cport_event_flags |=
SATA_EVNT_DEVICE_ATTACHED;
} else {
/* Timeout - cannot identify device */
cportinfo->cport_dev_attach_time = 0;
sata_log(sata_hba_inst,
CE_WARN,
"Could not identify SATA device "
"at port %d",
saddr->cport);
}
} else {
/*
* Start tracking time for device
* identification.
* Save current time (lbolt value).
*/
cportinfo->cport_dev_attach_time =
ddi_get_lbolt();
/* Restore attach event */
cportinfo->cport_event_flags |=
SATA_EVNT_DEVICE_ATTACHED;
}
} else {
/*
* If device was successfully attached, the subsequent
* action depends on a state of the
* sata_auto_online variable. If it is set to zero.
* an explicit 'configure' command will be needed to
* configure it. If its value is non-zero, we will
* attempt to online (configure) the device.
* First, log the message indicating that a device
* was attached.
*/
cportinfo->cport_dev_attach_time = 0;
sata_log(sata_hba_inst, CE_WARN,
"SATA device detected at port %d", saddr->cport);
if (SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
sata_drive_info_t new_sdinfo;
/* Log device info data */
new_sdinfo = *(SATA_CPORTINFO_DRV_INFO(
cportinfo));
sata_show_drive_info(sata_hba_inst,
&new_sdinfo);
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
/*
* Make sure that there is no target node for that
* device. If so, release it. It should not happen,
* unless we had problem removing the node when
* device was detached.
*/
tdip = sata_get_target_dip(SATA_DIP(sata_hba_inst),
saddr->cport);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
if (tdip != NULL) {
#ifdef SATA_DEBUG
if ((cportinfo->cport_event_flags &
SATA_EVNT_TARGET_NODE_CLEANUP) == 0)
sata_log(sata_hba_inst, CE_WARN,
"sata_process_device_attached: "
"old device target node exists!");
#endif
/*
* target node exists - try to unconfigure
* device and remove the node.
*/
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
rval = ndi_devi_offline(tdip,
NDI_DEVI_REMOVE);
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
if (rval == NDI_SUCCESS) {
cportinfo->cport_event_flags &=
~SATA_EVNT_TARGET_NODE_CLEANUP;
cportinfo->cport_tgtnode_clean = B_TRUE;
} else {
/*
* PROBLEM - the target node remained
* and it belongs to a previously
* attached device.
* This happens when the file was open
* or the node was waiting for
* resources at the time the
* associated device was removed.
* Instruct event daemon to retry the
* cleanup later.
*/
sata_log(sata_hba_inst,
CE_WARN,
"Application(s) accessing "
"previously attached SATA "
"device have to release "
"it before newly inserted "
"device can be made accessible.",
saddr->cport);
cportinfo->cport_event_flags |=
SATA_EVNT_TARGET_NODE_CLEANUP;
cportinfo->cport_tgtnode_clean =
B_FALSE;
}
}
if (sata_auto_online != 0) {
cportinfo->cport_event_flags |=
SATA_EVNT_AUTOONLINE_DEVICE;
}
}
} else {
cportinfo->cport_dev_attach_time = 0;
}
event_flags = cportinfo->cport_event_flags;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
if (event_flags != 0) {
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
}
}
/*
* Device Target Node Cleanup Event processing.
* If the target node associated with a sata port device is in
* DEVI_DEVICE_REMOVED state, an attempt is made to remove it.
* If the target node cannot be removed, the event flag is left intact,
* so that event daemon may re-run this function later.
*
* This function cannot be called in interrupt context (it may sleep).
*
* NOTE: Processes cport events only, not port multiplier ports.
*/
static void
sata_process_target_node_cleanup(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_cport_info_t *cportinfo;
dev_info_t *tdip;
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d device target node cleanup", saddr->cport);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
/*
* Check if there is target node for that device and it is in the
* DEVI_DEVICE_REMOVED state. If so, release it.
*/
tdip = sata_get_target_dip(SATA_DIP(sata_hba_inst), saddr->cport);
if (tdip != NULL) {
/*
* target node exists - check if it is target node of
* a removed device.
*/
if (sata_check_device_removed(tdip) == B_TRUE) {
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"sata_process_target_node_cleanup: "
"old device target node exists!", NULL);
/*
* Unconfigure and remove the target node
*/
if (ndi_devi_offline(tdip, NDI_DEVI_REMOVE) ==
NDI_SUCCESS) {
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
cportinfo->cport_event_flags &=
~SATA_EVNT_TARGET_NODE_CLEANUP;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
return;
}
/*
* Event daemon will retry the cleanup later.
*/
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
}
} else {
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
cportinfo->cport_event_flags &=
~SATA_EVNT_TARGET_NODE_CLEANUP;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
}
}
/*
* Device AutoOnline Event processing.
* If attached device is to be onlined, an attempt is made to online this
* device, but only if there is no lingering (old) target node present.
* If the device cannot be onlined, the event flag is left intact,
* so that event daemon may re-run this function later.
*
* This function cannot be called in interrupt context (it may sleep).
*
* NOTE: Processes cport events only, not port multiplier ports.
*/
static void
sata_process_device_autoonline(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
sata_cport_info_t *cportinfo;
sata_drive_info_t *sdinfo;
sata_device_t sata_device;
dev_info_t *tdip;
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"Processing port %d attached device auto-onlining", saddr->cport);
cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
/*
* Check if device is present and recognized. If not, reset event.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
if ((cportinfo->cport_dev_type & SATA_VALID_DEV_TYPE) == 0) {
/* Nothing to online */
cportinfo->cport_event_flags &= ~SATA_EVNT_AUTOONLINE_DEVICE;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
return;
}
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
/*
* Check if there is target node for this device and if it is in the
* DEVI_DEVICE_REMOVED state. If so, abort onlining but keep
* the event for later processing.
*/
tdip = sata_get_target_dip(SATA_DIP(sata_hba_inst), saddr->cport);
if (tdip != NULL) {
/*
* target node exists - check if it is target node of
* a removed device.
*/
if (sata_check_device_removed(tdip) == B_TRUE) {
SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
"sata_process_device_autoonline: "
"old device target node exists!", NULL);
/*
* Event daemon will retry device onlining later.
*/
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
return;
}
/*
* If the target node is not in the 'removed" state, assume
* that it belongs to this device. There is nothing more to do,
* but reset the event.
*/
} else {
/*
* Try to online the device
* If there is any reset-related event, remove it. We are
* configuring the device and no state restoring is needed.
*/
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
sata_device.satadev_addr = *saddr;
if (saddr->qual == SATA_ADDR_CPORT)
sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
else
sata_device.satadev_addr.qual = SATA_ADDR_DPMPORT;
sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
if (sdinfo != NULL) {
if (sdinfo->satadrv_event_flags &
(SATA_EVNT_DEVICE_RESET |
SATA_EVNT_INPROC_DEVICE_RESET))
sdinfo->satadrv_event_flags = 0;
sdinfo->satadrv_event_flags |=
SATA_EVNT_CLEAR_DEVICE_RESET;
/* Need to create a new target node. */
cportinfo->cport_tgtnode_clean = B_TRUE;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
tdip = sata_create_target_node(SATA_DIP(sata_hba_inst),
sata_hba_inst, &sata_device.satadev_addr);
if (tdip == NULL) {
/*
* Configure (onlining) failed.
* We will NOT retry
*/
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_process_device_autoonline: "
"configuring SATA device at port %d failed",
saddr->cport));
}
} else {
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
}
}
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
cportinfo->cport_event_flags &= ~SATA_EVNT_AUTOONLINE_DEVICE;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
saddr->cport)->cport_mutex);
}
static void
sata_gen_sysevent(sata_hba_inst_t *sata_hba_inst, sata_address_t *saddr,
int hint)
{
char ap[MAXPATHLEN];
nvlist_t *ev_attr_list = NULL;
int err;
/* Allocate and build sysevent attribute list */
err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, DDI_NOSLEEP);
if (err != 0) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_gen_sysevent: "
"cannot allocate memory for sysevent attributes\n"));
return;
}
/* Add hint attribute */
err = nvlist_add_string(ev_attr_list, DR_HINT, SE_HINT2STR(hint));
if (err != 0) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_gen_sysevent: "
"failed to add DR_HINT attr for sysevent"));
nvlist_free(ev_attr_list);
return;
}
/*
* Add AP attribute.
* Get controller pathname and convert it into AP pathname by adding
* a target number.
*/
(void) snprintf(ap, MAXPATHLEN, "/devices");
(void) ddi_pathname(SATA_DIP(sata_hba_inst), ap + strlen(ap));
(void) snprintf(ap + strlen(ap), MAXPATHLEN - strlen(ap), ":%d",
SATA_MAKE_AP_NUMBER(saddr->cport, saddr->pmport, saddr->qual));
err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap);
if (err != 0) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_gen_sysevent: "
"failed to add DR_AP_ID attr for sysevent"));
nvlist_free(ev_attr_list);
return;
}
/* Generate/log sysevent */
err = ddi_log_sysevent(SATA_DIP(sata_hba_inst), DDI_VENDOR_SUNW, EC_DR,
ESC_DR_AP_STATE_CHANGE, ev_attr_list, NULL, DDI_NOSLEEP);
if (err != DDI_SUCCESS) {
SATA_LOG_D((sata_hba_inst, CE_WARN,
"sata_gen_sysevent: "
"cannot log sysevent, err code %x\n", err));
}
nvlist_free(ev_attr_list);
}
/*
* Set DEVI_DEVICE_REMOVED state in the SATA device target node.
*/
static void
sata_set_device_removed(dev_info_t *tdip)
{
int circ;
ASSERT(tdip != NULL);
ndi_devi_enter(tdip, &circ);
mutex_enter(&DEVI(tdip)->devi_lock);
DEVI_SET_DEVICE_REMOVED(tdip);
mutex_exit(&DEVI(tdip)->devi_lock);
ndi_devi_exit(tdip, circ);
}
/*
* Set internal event instructing event daemon to try
* to perform the target node cleanup.
*/
static void
sata_set_target_node_cleanup(sata_hba_inst_t *sata_hba_inst,
sata_address_t *saddr)
{
mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
SATA_CPORT_EVENT_FLAGS(sata_hba_inst, saddr->cport) |=
SATA_EVNT_TARGET_NODE_CLEANUP;
SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_tgtnode_clean =
B_FALSE;
mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
mutex_enter(&sata_hba_inst->satahba_mutex);
sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
mutex_exit(&sata_hba_inst->satahba_mutex);
mutex_enter(&sata_mutex);
sata_event_pending |= SATA_EVNT_MAIN;
mutex_exit(&sata_mutex);
}
/*
* Check if the SATA device target node is in DEVI_DEVICE_REMOVED state,
* i.e. check if the target node state indicates that it belongs to a removed
* device.
*
* Returns B_TRUE if the target node is in DEVI_DEVICE_REMOVED state,
* B_FALSE otherwise.
*
* NOTE: No port multiplier support.
*/
static boolean_t
sata_check_device_removed(dev_info_t *tdip)
{
ASSERT(tdip != NULL);
if (DEVI_IS_DEVICE_REMOVED(tdip))
return (B_TRUE);
else
return (B_FALSE);
}
/* ************************ FAULT INJECTTION **************************** */
#ifdef SATA_INJECT_FAULTS
static uint32_t sata_fault_count = 0;
static uint32_t sata_fault_suspend_count = 0;
/*
* Inject sata pkt fault
* It modifies returned values of the sata packet.
* It returns immediately if:
* pkt fault injection is not enabled (via sata_inject_fault,
* sata_inject_fault_count), or invalid fault is specified (sata_fault_type),
* or pkt does not contain command to be faulted (set in sata_fault_cmd), or
* pkt is not directed to specified fault controller/device
* (sata_fault_ctrl_dev and sata_fault_device).
* If fault controller is not specified, fault injection applies to all
* controllers and devices.
*
* First argument is the pointer to the executed sata packet.
* Second argument is a pointer to a value returned by the HBA tran_start
* function.
* Third argument specifies injected error. Injected sata packet faults
* are the satapkt_reason values.
* SATA_PKT_BUSY -1 Not completed, busy
* SATA_PKT_DEV_ERROR 1 Device reported error
* SATA_PKT_QUEUE_FULL 2 Not accepted, queue full
* SATA_PKT_PORT_ERROR 3 Not completed, port error
* SATA_PKT_CMD_UNSUPPORTED 4 Cmd unsupported
* SATA_PKT_ABORTED 5 Aborted by request
* SATA_PKT_TIMEOUT 6 Operation timeut
* SATA_PKT_RESET 7 Aborted by reset request
*
* Additional global variables affecting the execution:
*
* sata_inject_fault_count variable specifies number of times in row the
* error is injected. Value of -1 specifies permanent fault, ie. every time
* the fault injection point is reached, the fault is injected and a pause
* between fault injection specified by sata_inject_fault_pause_count is
* ignored). Fault injection routine decrements sata_inject_fault_count
* (if greater than zero) until it reaches 0. No fault is injected when
* sata_inject_fault_count is 0 (zero).
*
* sata_inject_fault_pause_count variable specifies number of times a fault
* injection is bypassed (pause between fault injections).
* If set to 0, a fault is injected only a number of times specified by
* sata_inject_fault_count.
*
* The fault counts are static, so for periodic errors they have to be manually
* reset to start repetition sequence from scratch.
* If the original value returned by the HBA tran_start function is not
* SATA_TRAN_ACCEPTED and pkt reason is not SATA_PKT_COMPLETED, no error
* is injected (to avoid masking real problems);
*
* NOTE: In its current incarnation, this function should be invoked only for
* commands executed in SYNCHRONOUS mode.
*/
static void
sata_inject_pkt_fault(sata_pkt_t *spkt, int *rval, int fault)
{
if (sata_inject_fault != SATA_INJECT_PKT_FAULT)
return;
if (sata_inject_fault_count == 0)
return;
if (fault == 0)
return;
if (sata_fault_cmd != spkt->satapkt_cmd.satacmd_cmd_reg)
return;
if (sata_fault_ctrl != NULL) {
sata_pkt_txlate_t *spx =
(sata_pkt_txlate_t *)spkt->satapkt_framework_private;
if (sata_fault_ctrl != NULL && sata_fault_ctrl !=
spx->txlt_sata_hba_inst->satahba_dip)
return;
if (sata_fault_device.satadev_addr.cport !=
spkt->satapkt_device.satadev_addr.cport ||
sata_fault_device.satadev_addr.pmport !=
spkt->satapkt_device.satadev_addr.pmport ||
sata_fault_device.satadev_addr.qual !=
spkt->satapkt_device.satadev_addr.qual)
return;
}
/* Modify pkt return parameters */
if (*rval != SATA_TRAN_ACCEPTED ||
spkt->satapkt_reason != SATA_PKT_COMPLETED) {
sata_fault_count = 0;
sata_fault_suspend_count = 0;
return;
}
if (sata_fault_count == 0 && sata_fault_suspend_count != 0) {
/* Pause in the injection */
sata_fault_suspend_count -= 1;
return;
}
if (sata_fault_count == 0 && sata_fault_suspend_count == 0) {
/*
* Init inject fault cycle. If fault count is set to -1,
* it is a permanent fault.
*/
if (sata_inject_fault_count != -1) {
sata_fault_count = sata_inject_fault_count;
sata_fault_suspend_count =
sata_inject_fault_pause_count;
if (sata_fault_suspend_count == 0)
sata_inject_fault_count = 0;
}
}
if (sata_fault_count != 0)
sata_fault_count -= 1;
switch (fault) {
case SATA_PKT_BUSY:
*rval = SATA_TRAN_BUSY;
spkt->satapkt_reason = SATA_PKT_BUSY;
break;
case SATA_PKT_QUEUE_FULL:
*rval = SATA_TRAN_QUEUE_FULL;
spkt->satapkt_reason = SATA_PKT_QUEUE_FULL;
break;
case SATA_PKT_CMD_UNSUPPORTED:
*rval = SATA_TRAN_CMD_UNSUPPORTED;
spkt->satapkt_reason = SATA_PKT_CMD_UNSUPPORTED;
break;
case SATA_PKT_PORT_ERROR:
/* This is "rejected" command */
*rval = SATA_TRAN_PORT_ERROR;
spkt->satapkt_reason = SATA_PKT_PORT_ERROR;
/* Additional error setup could be done here - port state */
break;
case SATA_PKT_DEV_ERROR:
spkt->satapkt_reason = SATA_PKT_DEV_ERROR;
/*
* Additional error setup could be done here
*/
break;
case SATA_PKT_ABORTED:
spkt->satapkt_reason = SATA_PKT_ABORTED;
break;
case SATA_PKT_TIMEOUT:
spkt->satapkt_reason = SATA_PKT_TIMEOUT;
/* Additional error setup could be done here */
break;
case SATA_PKT_RESET:
spkt->satapkt_reason = SATA_PKT_RESET;
/*
* Additional error setup could be done here - device reset
*/
break;
default:
break;
}
}
#endif