#include <sys/scsi/adapters/smrt/smrt.h>
static int smrt_attach(dev_info_t *, ddi_attach_cmd_t);
static int smrt_detach(dev_info_t *, ddi_detach_cmd_t);
static int smrt_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static void smrt_cleanup(smrt_t *);
static int smrt_command_comparator(const void *, const void *);
void *smrt_state;
static ddi_dma_attr_t smrt_dma_attr_template = {
.dma_attr_version = DMA_ATTR_V0,
.dma_attr_addr_lo = 0x0000000000000000,
.dma_attr_addr_hi = 0xFFFFFFFFFFFFFFFF,
.dma_attr_count_max = 0x00FFFFFF,
.dma_attr_align = 0x20,
.dma_attr_burstsizes = 0x20,
.dma_attr_minxfer = DMA_UNIT_8,
.dma_attr_maxxfer = 0xFFFFFFFF,
.dma_attr_seg = 0xFFFFFFFF,
.dma_attr_sgllen = 1,
.dma_attr_granular = 512,
.dma_attr_flags = 0
};
ddi_device_acc_attr_t smrt_dev_attributes = {
.devacc_attr_version = DDI_DEVICE_ATTR_V0,
.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC,
.devacc_attr_dataorder = DDI_STRICTORDER_ACC,
.devacc_attr_access = 0
};
static struct cb_ops smrt_cb_ops = {
.cb_rev = CB_REV,
.cb_flag = D_NEW | D_MP,
.cb_open = scsi_hba_open,
.cb_close = scsi_hba_close,
.cb_ioctl = smrt_ioctl,
.cb_strategy = nodev,
.cb_print = nodev,
.cb_dump = nodev,
.cb_read = nodev,
.cb_write = nodev,
.cb_devmap = nodev,
.cb_mmap = nodev,
.cb_segmap = nodev,
.cb_chpoll = nochpoll,
.cb_prop_op = ddi_prop_op,
.cb_str = NULL,
.cb_aread = nodev,
.cb_awrite = nodev
};
static struct dev_ops smrt_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_attach = smrt_attach,
.devo_detach = smrt_detach,
.devo_cb_ops = &smrt_cb_ops,
.devo_getinfo = nodev,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_reset = nodev,
.devo_bus_ops = NULL,
.devo_power = nodev,
.devo_quiesce = nodev
};
static struct modldrv smrt_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "HP Smart Array",
.drv_dev_ops = &smrt_dev_ops
};
static struct modlinkage smrt_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &smrt_modldrv, NULL }
};
int
_init()
{
int r;
VERIFY0(ddi_soft_state_init(&smrt_state, sizeof (smrt_t), 0));
if ((r = scsi_hba_init(&smrt_modlinkage)) != 0) {
goto fail;
}
if ((r = mod_install(&smrt_modlinkage)) != 0) {
scsi_hba_fini(&smrt_modlinkage);
goto fail;
}
return (r);
fail:
ddi_soft_state_fini(&smrt_state);
return (r);
}
int
_fini()
{
int r;
if ((r = mod_remove(&smrt_modlinkage)) == 0) {
scsi_hba_fini(&smrt_modlinkage);
ddi_soft_state_fini(&smrt_state);
}
return (r);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&smrt_modlinkage, modinfop));
}
static int
smrt_iport_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
const char *addr;
dev_info_t *pdip;
int instance;
smrt_t *smrt;
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
addr = scsi_hba_iport_unit_address(dip);
VERIFY(addr != NULL);
pdip = ddi_get_parent(dip);
instance = ddi_get_instance(pdip);
smrt = ddi_get_soft_state(smrt_state, instance);
VERIFY(smrt != NULL);
if (strcmp(addr, SMRT_IPORT_VIRT) == 0) {
if (smrt_logvol_hba_setup(smrt, dip) != DDI_SUCCESS)
return (DDI_FAILURE);
smrt->smrt_virt_iport = dip;
} else if (strcmp(addr, SMRT_IPORT_PHYS) == 0) {
if (smrt_phys_hba_setup(smrt, dip) != DDI_SUCCESS)
return (DDI_FAILURE);
smrt->smrt_phys_iport = dip;
} else {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
smrt_iport_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
const char *addr;
scsi_hba_tran_t *tran;
smrt_t *smrt;
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
tran = ddi_get_driver_private(dip);
VERIFY(tran != NULL);
smrt = tran->tran_hba_private;
VERIFY(smrt != NULL);
addr = scsi_hba_iport_unit_address(dip);
VERIFY(addr != NULL);
if (strcmp(addr, SMRT_IPORT_VIRT) == 0) {
smrt_logvol_hba_teardown(smrt, dip);
smrt->smrt_virt_iport = NULL;
} else if (strcmp(addr, SMRT_IPORT_PHYS) == 0) {
smrt_phys_hba_teardown(smrt, dip);
smrt->smrt_phys_iport = NULL;
} else {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
smrt_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
uint32_t instance;
smrt_t *smrt;
boolean_t check_for_interrupts = B_FALSE;
int r;
char taskq_name[64];
if (scsi_hba_iport_unit_address(dip) != NULL)
return (smrt_iport_attach(dip, cmd));
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(smrt_state, instance) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "could not allocate soft state");
return (DDI_FAILURE);
}
if ((smrt = ddi_get_soft_state(smrt_state, instance)) == NULL) {
dev_err(dip, CE_WARN, "could not get soft state");
ddi_soft_state_free(smrt_state, instance);
return (DDI_FAILURE);
}
smrt->smrt_dip = dip;
smrt->smrt_instance = instance;
smrt->smrt_next_tag = SMRT_MIN_TAG_NUMBER;
list_create(&smrt->smrt_commands, sizeof (smrt_command_t),
offsetof(smrt_command_t, smcm_link));
list_create(&smrt->smrt_finishq, sizeof (smrt_command_t),
offsetof(smrt_command_t, smcm_link_finish));
list_create(&smrt->smrt_abortq, sizeof (smrt_command_t),
offsetof(smrt_command_t, smcm_link_abort));
list_create(&smrt->smrt_volumes, sizeof (smrt_volume_t),
offsetof(smrt_volume_t, smlv_link));
list_create(&smrt->smrt_physicals, sizeof (smrt_physical_t),
offsetof(smrt_physical_t, smpt_link));
list_create(&smrt->smrt_targets, sizeof (smrt_target_t),
offsetof(smrt_target_t, smtg_link_ctlr));
avl_create(&smrt->smrt_inflight, smrt_command_comparator,
sizeof (smrt_command_t), offsetof(smrt_command_t,
smcm_node));
cv_init(&smrt->smrt_cv_finishq, NULL, CV_DRIVER, NULL);
smrt->smrt_init_level |= SMRT_INITLEVEL_BASIC;
if (smrt_device_setup(smrt) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "device setup failed");
goto fail;
}
if (smrt_ctlr_init(smrt) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "controller initialisation failed");
goto fail;
}
VERIFY(smrt->smrt_sg_cnt > 0);
smrt->smrt_dma_attr = smrt_dma_attr_template;
smrt->smrt_dma_attr.dma_attr_sgllen = smrt->smrt_sg_cnt;
if (smrt_interrupts_setup(smrt) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "interrupt handler setup failed");
goto fail;
}
mutex_init(&smrt->smrt_mutex, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(smrt->smrt_interrupt_pri));
smrt->smrt_init_level |= SMRT_INITLEVEL_MUTEX;
smrt->smrt_status |= SMRT_CTLR_STATUS_RUNNING;
if (smrt_interrupts_enable(smrt) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "interrupt handler could not be enabled");
goto fail;
}
if (smrt_ctrl_hba_setup(smrt) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "SCSI framework setup failed");
goto fail;
}
smrt_intr_set(smrt, B_TRUE);
check_for_interrupts = B_TRUE;
smrt->smrt_periodic = ddi_periodic_add(smrt_periodic, smrt,
SMRT_PERIODIC_RATE * NANOSEC, DDI_IPL_0);
smrt->smrt_init_level |= SMRT_INITLEVEL_PERIODIC;
(void) snprintf(taskq_name, sizeof (taskq_name), "smrt_discover_%u",
instance);
smrt->smrt_discover_taskq = ddi_taskq_create(smrt->smrt_dip, taskq_name,
1, TASKQ_DEFAULTPRI, 0);
if (smrt->smrt_discover_taskq == NULL) {
dev_err(dip, CE_WARN, "failed to create discovery task queue");
goto fail;
}
smrt->smrt_init_level |= SMRT_INITLEVEL_TASKQ;
if ((r = smrt_event_init(smrt)) != 0) {
dev_err(dip, CE_WARN, "could not initialize event subsystem "
"(%d)", r);
goto fail;
}
smrt->smrt_init_level |= SMRT_INITLEVEL_ASYNC_EVENT;
if (scsi_hba_iport_register(dip, SMRT_IPORT_VIRT) != DDI_SUCCESS)
goto fail;
if (scsi_hba_iport_register(dip, SMRT_IPORT_PHYS) != DDI_SUCCESS)
goto fail;
ddi_report_dev(dip);
return (DDI_SUCCESS);
fail:
if (check_for_interrupts) {
if (smrt->smrt_stats.smrts_claimed_interrupts == 0) {
dev_err(dip, CE_WARN, "controller did not interrupt "
"during attach");
}
}
smrt_cleanup(smrt);
return (DDI_FAILURE);
}
static int
smrt_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
scsi_hba_tran_t *tran = (scsi_hba_tran_t *)ddi_get_driver_private(dip);
smrt_t *smrt = (smrt_t *)tran->tran_hba_private;
if (scsi_hba_iport_unit_address(dip) != NULL)
return (smrt_iport_detach(dip, cmd));
if (cmd != DDI_DETACH) {
return (DDI_FAILURE);
}
mutex_enter(&smrt->smrt_mutex);
if (!list_is_empty(&smrt->smrt_targets)) {
mutex_exit(&smrt->smrt_mutex);
dev_err(smrt->smrt_dip, CE_WARN, "cannot detach; targets still "
"using HBA");
return (DDI_FAILURE);
}
if (smrt->smrt_virt_iport != NULL || smrt->smrt_phys_iport != NULL) {
mutex_exit(&smrt->smrt_mutex);
dev_err(smrt->smrt_dip, CE_WARN, "cannot detach: iports still "
"attached");
return (DDI_FAILURE);
}
smrt->smrt_status |= SMRT_CTLR_STATUS_DETACHING;
mutex_exit(&smrt->smrt_mutex);
smrt_cleanup(smrt);
return (DDI_SUCCESS);
}
static int
smrt_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rval)
{
int inst = MINOR2INST(getminor(dev));
int status;
if (secpolicy_sys_config(credp, B_FALSE) != 0) {
return (EPERM);
}
if (ddi_get_soft_state(smrt_state, inst) == NULL) {
return (ENXIO);
}
switch (cmd) {
default:
status = scsi_hba_ioctl(dev, cmd, arg, mode, credp, rval);
break;
}
return (status);
}
static void
smrt_cleanup(smrt_t *smrt)
{
if (smrt->smrt_init_level & SMRT_INITLEVEL_ASYNC_EVENT) {
smrt_event_fini(smrt);
smrt->smrt_init_level &= ~SMRT_INITLEVEL_ASYNC_EVENT;
}
smrt_interrupts_teardown(smrt);
if (smrt->smrt_init_level & SMRT_INITLEVEL_TASKQ) {
ddi_taskq_destroy(smrt->smrt_discover_taskq);
smrt->smrt_discover_taskq = NULL;
smrt->smrt_init_level &= ~SMRT_INITLEVEL_TASKQ;
}
if (smrt->smrt_init_level & SMRT_INITLEVEL_PERIODIC) {
ddi_periodic_delete(smrt->smrt_periodic);
smrt->smrt_init_level &= ~SMRT_INITLEVEL_PERIODIC;
}
smrt_ctrl_hba_teardown(smrt);
smrt_ctlr_teardown(smrt);
smrt_device_teardown(smrt);
if (smrt->smrt_init_level & SMRT_INITLEVEL_BASIC) {
smrt_logvol_teardown(smrt);
smrt_phys_teardown(smrt);
cv_destroy(&smrt->smrt_cv_finishq);
VERIFY(list_is_empty(&smrt->smrt_commands));
list_destroy(&smrt->smrt_commands);
list_destroy(&smrt->smrt_finishq);
list_destroy(&smrt->smrt_abortq);
VERIFY(list_is_empty(&smrt->smrt_volumes));
list_destroy(&smrt->smrt_volumes);
VERIFY(list_is_empty(&smrt->smrt_physicals));
list_destroy(&smrt->smrt_physicals);
VERIFY(list_is_empty(&smrt->smrt_targets));
list_destroy(&smrt->smrt_targets);
VERIFY(avl_is_empty(&smrt->smrt_inflight));
avl_destroy(&smrt->smrt_inflight);
smrt->smrt_init_level &= ~SMRT_INITLEVEL_BASIC;
}
if (smrt->smrt_init_level & SMRT_INITLEVEL_MUTEX) {
mutex_destroy(&smrt->smrt_mutex);
smrt->smrt_init_level &= ~SMRT_INITLEVEL_MUTEX;
}
VERIFY0(smrt->smrt_init_level);
ddi_soft_state_free(smrt_state, ddi_get_instance(smrt->smrt_dip));
}
static int
smrt_command_comparator(const void *lp, const void *rp)
{
const smrt_command_t *l = lp;
const smrt_command_t *r = rp;
if (l->smcm_tag > r->smcm_tag) {
return (1);
} else if (l->smcm_tag < r->smcm_tag) {
return (-1);
} else {
return (0);
}
}