#include <sys/atomic.h>
#include <sys/conf.h>
#include <sys/disp.h>
#include <sys/ddi.h>
#include <sys/dkio.h>
#include <sys/efi_partition.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/kstat.h>
#include <sys/mach_descrip.h>
#include <sys/modctl.h>
#include <sys/mdeg.h>
#include <sys/note.h>
#include <sys/open.h>
#include <sys/random.h>
#include <sys/sdt.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/types.h>
#include <sys/promif.h>
#include <sys/var.h>
#include <sys/vtoc.h>
#include <sys/archsystm.h>
#include <sys/sysmacros.h>
#include <sys/cdio.h>
#include <sys/dktp/fdisk.h>
#include <sys/dktp/dadkio.h>
#include <sys/fs/dv_node.h>
#include <sys/mhd.h>
#include <sys/scsi/generic/sense.h>
#include <sys/scsi/impl/uscsi.h>
#include <sys/scsi/impl/services.h>
#include <sys/scsi/targets/sddef.h>
#include <sys/ldoms.h>
#include <sys/ldc.h>
#include <sys/vio_common.h>
#include <sys/vio_mailbox.h>
#include <sys/vio_util.h>
#include <sys/vdsk_common.h>
#include <sys/vdsk_mailbox.h>
#include <sys/vdc.h>
#define VD_OLDVTOC_LIMIT 0x7fffffff
static int vdc_open(dev_t *dev, int flag, int otyp, cred_t *cred);
static int vdc_close(dev_t dev, int flag, int otyp, cred_t *cred);
static int vdc_strategy(struct buf *buf);
static int vdc_print(dev_t dev, char *str);
static int vdc_dump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk);
static int vdc_read(dev_t dev, struct uio *uio, cred_t *cred);
static int vdc_write(dev_t dev, struct uio *uio, cred_t *cred);
static int vdc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp);
static int vdc_aread(dev_t dev, struct aio_req *aio, cred_t *cred);
static int vdc_awrite(dev_t dev, struct aio_req *aio, cred_t *cred);
static int vdc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
void *arg, void **resultp);
static int vdc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int vdc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int vdc_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int mod_flags, char *name, caddr_t valuep, int *lengthp);
static void vdc_min(struct buf *bufp);
static int vdc_send(vdc_t *vdc, caddr_t pkt, size_t *msglen);
static int vdc_do_ldc_init(vdc_t *vdc, vdc_server_t *srvr);
static int vdc_start_ldc_connection(vdc_t *vdc);
static int vdc_create_device_nodes(vdc_t *vdc);
static int vdc_create_device_nodes_efi(vdc_t *vdc);
static int vdc_create_device_nodes_vtoc(vdc_t *vdc);
static void vdc_create_io_kstats(vdc_t *vdc);
static void vdc_create_err_kstats(vdc_t *vdc);
static void vdc_set_err_kstats(vdc_t *vdc);
static int vdc_get_md_node(dev_info_t *dip, md_t **mdpp,
mde_cookie_t *vd_nodep);
static int vdc_init_ports(vdc_t *vdc, md_t *mdp, mde_cookie_t vd_nodep);
static void vdc_fini_ports(vdc_t *vdc);
static void vdc_switch_server(vdc_t *vdcp);
static int vdc_do_ldc_up(vdc_t *vdc);
static void vdc_terminate_ldc(vdc_t *vdc, vdc_server_t *srvr);
static int vdc_init_descriptor_ring(vdc_t *vdc);
static void vdc_destroy_descriptor_ring(vdc_t *vdc);
static int vdc_setup_devid(vdc_t *vdc);
static void vdc_store_label_efi(vdc_t *, efi_gpt_t *, efi_gpe_t *);
static void vdc_store_label_vtoc(vdc_t *, struct dk_geom *,
struct extvtoc *);
static void vdc_store_label_unk(vdc_t *vdc);
static boolean_t vdc_is_opened(vdc_t *vdc);
static void vdc_update_size(vdc_t *vdc, size_t, size_t, size_t);
static int vdc_update_vio_bsize(vdc_t *vdc, uint32_t);
static int vdc_init_ver_negotiation(vdc_t *vdc, vio_ver_t ver);
static int vdc_ver_negotiation(vdc_t *vdcp);
static int vdc_init_attr_negotiation(vdc_t *vdc);
static int vdc_attr_negotiation(vdc_t *vdcp);
static int vdc_init_dring_negotiate(vdc_t *vdc);
static int vdc_dring_negotiation(vdc_t *vdcp);
static int vdc_send_rdx(vdc_t *vdcp);
static int vdc_rdx_exchange(vdc_t *vdcp);
static boolean_t vdc_is_supported_version(vio_ver_msg_t *ver_msg);
static void vdc_process_msg_thread(vdc_t *vdc);
static int vdc_recv(vdc_t *vdc, vio_msg_t *msgp, size_t *nbytesp);
static uint_t vdc_handle_cb(uint64_t event, caddr_t arg);
static int vdc_process_data_msg(vdc_t *vdc, vio_msg_t *msg);
static int vdc_handle_ver_msg(vdc_t *vdc, vio_ver_msg_t *ver_msg);
static int vdc_handle_attr_msg(vdc_t *vdc, vd_attr_msg_t *attr_msg);
static int vdc_handle_dring_reg_msg(vdc_t *vdc, vio_dring_reg_msg_t *msg);
static int vdc_send_request(vdc_t *vdcp, int operation,
caddr_t addr, size_t nbytes, int slice, diskaddr_t offset,
buf_t *bufp, vio_desc_direction_t dir, int flags);
static int vdc_map_to_shared_dring(vdc_t *vdcp, int idx);
static int vdc_populate_descriptor(vdc_t *vdcp, int operation,
caddr_t addr, size_t nbytes, int slice, diskaddr_t offset,
buf_t *bufp, vio_desc_direction_t dir, int flags);
static int vdc_do_sync_op(vdc_t *vdcp, int operation, caddr_t addr,
size_t nbytes, int slice, diskaddr_t offset,
vio_desc_direction_t dir, boolean_t);
static int vdc_do_op(vdc_t *vdc, int op, caddr_t addr, size_t nbytes,
int slice, diskaddr_t offset, struct buf *bufp,
vio_desc_direction_t dir, int flags);
static int vdc_wait_for_response(vdc_t *vdcp, vio_msg_t *msgp);
static int vdc_drain_response(vdc_t *vdcp, struct buf *buf);
static int vdc_depopulate_descriptor(vdc_t *vdc, uint_t idx);
static int vdc_populate_mem_hdl(vdc_t *vdcp, vdc_local_desc_t *ldep);
static int vdc_verify_seq_num(vdc_t *vdc, vio_dring_msg_t *dring_msg);
static int vd_process_ioctl(dev_t dev, int cmd, caddr_t arg, int mode,
int *rvalp);
static int vd_process_efi_ioctl(void *vdisk, int cmd, uintptr_t arg);
static void vdc_create_fake_geometry(vdc_t *vdc);
static int vdc_validate_geometry(vdc_t *vdc);
static void vdc_validate(vdc_t *vdc);
static void vdc_validate_task(void *arg);
static int vdc_null_copy_func(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_get_wce_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_set_wce_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_get_vtoc_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_set_vtoc_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_get_extvtoc_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_set_extvtoc_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_get_geom_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_set_geom_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_get_efi_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static int vdc_set_efi_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir);
static void vdc_ownership_update(vdc_t *vdc, int ownership_flags);
static int vdc_access_set(vdc_t *vdc, uint64_t flags);
static vdc_io_t *vdc_eio_queue(vdc_t *vdc, int index);
static void vdc_eio_unqueue(vdc_t *vdc, clock_t deadline,
boolean_t complete_io);
static int vdc_eio_check(vdc_t *vdc, int flags);
static void vdc_eio_thread(void *arg);
static uint_t vdc_hshake_retries = VDC_HSHAKE_RETRIES;
static uint_t vdc_hattr_min_initial = VDC_HATTR_MIN_INITIAL;
static uint_t vdc_hattr_min = VDC_HATTR_MIN;
static int vdc_timeout = 0;
static int vdc_ldcup_timeout = 1;
static uint64_t vdc_hz_min_ldc_delay;
static uint64_t vdc_min_timeout_ldc = 1 * MILLISEC;
static uint64_t vdc_hz_max_ldc_delay;
static uint64_t vdc_max_timeout_ldc = 100 * MILLISEC;
static uint64_t vdc_ldc_read_init_delay = 1 * MILLISEC;
static uint64_t vdc_ldc_read_max_delay = 100 * MILLISEC;
static uint64_t vdc_usec_timeout_dump = 100 * MILLISEC;
static int vdc_dump_retries = 100;
static uint16_t vdc_scsi_timeout = 60;
static uint64_t vdc_ownership_delay = 6 * MICROSEC;
static volatile uint32_t vdc_instance_count = 0;
static boolean_t vdc_scsi_log_error = B_FALSE;
static void *vdc_state;
int vdc_msglevel = 0x0;
uint64_t vdc_matchinst = 0ull;
static const vio_ver_t vdc_version[] = {{1, 1}};
static struct cb_ops vdc_cb_ops = {
vdc_open,
vdc_close,
vdc_strategy,
vdc_print,
vdc_dump,
vdc_read,
vdc_write,
vdc_ioctl,
nodev,
nodev,
nodev,
nochpoll,
vdc_prop_op,
NULL,
D_MP | D_64BIT,
CB_REV,
vdc_aread,
vdc_awrite
};
static struct dev_ops vdc_ops = {
DEVO_REV,
0,
vdc_getinfo,
nulldev,
nulldev,
vdc_attach,
vdc_detach,
nodev,
&vdc_cb_ops,
NULL,
nulldev,
ddi_quiesce_not_needed,
};
static struct modldrv modldrv = {
&mod_driverops,
"virtual disk client",
&vdc_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
int
_init(void)
{
int status;
if ((status = ddi_soft_state_init(&vdc_state, sizeof (vdc_t), 1)) != 0)
return (status);
if ((status = mod_install(&modlinkage)) != 0)
ddi_soft_state_fini(&vdc_state);
return (status);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int status;
if ((status = mod_remove(&modlinkage)) != 0)
return (status);
ddi_soft_state_fini(&vdc_state);
return (0);
}
static int
vdc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
_NOTE(ARGUNUSED(dip))
int instance = VDCUNIT((dev_t)arg);
vdc_t *vdc = NULL;
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((vdc = ddi_get_soft_state(vdc_state, instance)) == NULL) {
*resultp = NULL;
return (DDI_FAILURE);
}
*resultp = vdc->dip;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
*resultp = (void *)(uintptr_t)instance;
return (DDI_SUCCESS);
default:
*resultp = NULL;
return (DDI_FAILURE);
}
}
static int
vdc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
kt_did_t eio_tid, ownership_tid;
int instance;
int rv;
vdc_server_t *srvr;
vdc_t *vdc = NULL;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
ASSERT(cmd == DDI_DETACH);
instance = ddi_get_instance(dip);
DMSGX(1, "[%d] Entered\n", instance);
if ((vdc = ddi_get_soft_state(vdc_state, instance)) == NULL) {
cmn_err(CE_NOTE, "[%d] Couldn't get state structure", instance);
return (DDI_FAILURE);
}
if (vdc_is_opened(vdc)) {
DMSG(vdc, 0, "[%d] Cannot detach: device is open", instance);
return (DDI_FAILURE);
}
if (vdc->dkio_flush_pending) {
DMSG(vdc, 0,
"[%d] Cannot detach: %d outstanding DKIO flushes\n",
instance, vdc->dkio_flush_pending);
return (DDI_FAILURE);
}
if (vdc->validate_pending) {
DMSG(vdc, 0,
"[%d] Cannot detach: %d outstanding validate request\n",
instance, vdc->validate_pending);
return (DDI_FAILURE);
}
DMSG(vdc, 0, "[%d] proceeding...\n", instance);
mutex_enter(&vdc->ownership_lock);
if (vdc->ownership & VDC_OWNERSHIP_GRANTED) {
rv = vdc_access_set(vdc, VD_ACCESS_SET_CLEAR);
if (rv == 0) {
vdc_ownership_update(vdc, VDC_OWNERSHIP_NONE);
}
}
mutex_exit(&vdc->ownership_lock);
mutex_enter(&vdc->lock);
vdc->lifecycle = VDC_LC_DETACHING;
mutex_exit(&vdc->lock);
for (srvr = vdc->server_list; srvr != NULL; srvr = srvr->next) {
rv = ldc_set_cb_mode(srvr->ldc_handle, LDC_CB_DISABLE);
DMSG(vdc, 0, "callback disabled (ldc=%lu, rv=%d)\n",
srvr->ldc_id, rv);
}
if (vdc->initialized & VDC_THREAD) {
mutex_enter(&vdc->read_lock);
if ((vdc->read_state == VDC_READ_WAITING) ||
(vdc->read_state == VDC_READ_RESET)) {
vdc->read_state = VDC_READ_RESET;
cv_signal(&vdc->read_cv);
}
mutex_exit(&vdc->read_lock);
mutex_enter(&vdc->lock);
if (vdc->state == VDC_STATE_INIT_WAITING) {
DMSG(vdc, 0,
"[%d] write reset - move to resetting state...\n",
instance);
vdc->state = VDC_STATE_RESETTING;
cv_signal(&vdc->initwait_cv);
} else if (vdc->state == VDC_STATE_FAILED) {
vdc->io_pending = B_TRUE;
cv_signal(&vdc->io_pending_cv);
}
mutex_exit(&vdc->lock);
thread_join(vdc->msg_proc_thr->t_did);
ASSERT(vdc->state == VDC_STATE_DETACH);
DMSG(vdc, 0, "[%d] Reset thread exit and join ..\n",
vdc->instance);
}
mutex_enter(&vdc->lock);
if (vdc->initialized & VDC_DRING)
vdc_destroy_descriptor_ring(vdc);
vdc_fini_ports(vdc);
if (vdc->eio_thread) {
eio_tid = vdc->eio_thread->t_did;
vdc->failfast_interval = 0;
ASSERT(vdc->num_servers == 0);
cv_signal(&vdc->eio_cv);
} else {
eio_tid = 0;
}
if (vdc->ownership & VDC_OWNERSHIP_WANTED) {
ownership_tid = vdc->ownership_thread->t_did;
vdc->ownership = VDC_OWNERSHIP_NONE;
cv_signal(&vdc->ownership_cv);
} else {
ownership_tid = 0;
}
mutex_exit(&vdc->lock);
if (eio_tid != 0)
thread_join(eio_tid);
if (ownership_tid != 0)
thread_join(ownership_tid);
if (vdc->initialized & VDC_MINOR)
ddi_remove_minor_node(dip, NULL);
if (vdc->io_stats) {
kstat_delete(vdc->io_stats);
vdc->io_stats = NULL;
}
if (vdc->err_stats) {
kstat_delete(vdc->err_stats);
vdc->err_stats = NULL;
}
if (vdc->initialized & VDC_LOCKS) {
mutex_destroy(&vdc->lock);
mutex_destroy(&vdc->read_lock);
mutex_destroy(&vdc->ownership_lock);
cv_destroy(&vdc->initwait_cv);
cv_destroy(&vdc->dring_free_cv);
cv_destroy(&vdc->membind_cv);
cv_destroy(&vdc->sync_blocked_cv);
cv_destroy(&vdc->read_cv);
cv_destroy(&vdc->running_cv);
cv_destroy(&vdc->io_pending_cv);
cv_destroy(&vdc->ownership_cv);
cv_destroy(&vdc->eio_cv);
}
if (vdc->minfo)
kmem_free(vdc->minfo, sizeof (struct dk_minfo));
if (vdc->cinfo)
kmem_free(vdc->cinfo, sizeof (struct dk_cinfo));
if (vdc->vtoc)
kmem_free(vdc->vtoc, sizeof (struct extvtoc));
if (vdc->geom)
kmem_free(vdc->geom, sizeof (struct dk_geom));
if (vdc->devid) {
ddi_devid_unregister(dip);
ddi_devid_free(vdc->devid);
}
if (vdc->initialized & VDC_SOFT_STATE)
ddi_soft_state_free(vdc_state, instance);
DMSG(vdc, 0, "[%d] End %p\n", instance, (void *)vdc);
return (DDI_SUCCESS);
}
static int
vdc_do_attach(dev_info_t *dip)
{
int instance;
vdc_t *vdc = NULL;
int status;
md_t *mdp;
mde_cookie_t vd_node;
ASSERT(dip != NULL);
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(vdc_state, instance) != DDI_SUCCESS) {
cmn_err(CE_NOTE, "[%d] Couldn't alloc state structure",
instance);
return (DDI_FAILURE);
}
if ((vdc = ddi_get_soft_state(vdc_state, instance)) == NULL) {
cmn_err(CE_NOTE, "[%d] Couldn't get state structure", instance);
return (DDI_FAILURE);
}
vdc->initialized = VDC_SOFT_STATE;
vdc_hz_min_ldc_delay = drv_usectohz(vdc_min_timeout_ldc);
vdc_hz_max_ldc_delay = drv_usectohz(vdc_max_timeout_ldc);
vdc->dip = dip;
vdc->instance = instance;
vdc->vdisk_type = VD_DISK_TYPE_UNK;
vdc->vdisk_label = VD_DISK_LABEL_UNK;
vdc->state = VDC_STATE_INIT;
vdc->lifecycle = VDC_LC_ATTACHING;
vdc->session_id = 0;
vdc->vdisk_bsize = DEV_BSIZE;
vdc->vio_bmask = 0;
vdc->vio_bshift = 0;
vdc->max_xfer_sz = maxphys / vdc->vdisk_bsize;
vdc->operations = VD_OP_MASK_READ;
vdc->vtoc = NULL;
vdc->geom = NULL;
vdc->cinfo = NULL;
vdc->minfo = NULL;
mutex_init(&vdc->lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&vdc->initwait_cv, NULL, CV_DRIVER, NULL);
cv_init(&vdc->dring_free_cv, NULL, CV_DRIVER, NULL);
cv_init(&vdc->membind_cv, NULL, CV_DRIVER, NULL);
cv_init(&vdc->running_cv, NULL, CV_DRIVER, NULL);
cv_init(&vdc->io_pending_cv, NULL, CV_DRIVER, NULL);
vdc->io_pending = B_FALSE;
vdc->threads_pending = 0;
vdc->sync_op_blocked = B_FALSE;
cv_init(&vdc->sync_blocked_cv, NULL, CV_DRIVER, NULL);
mutex_init(&vdc->ownership_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&vdc->ownership_cv, NULL, CV_DRIVER, NULL);
cv_init(&vdc->eio_cv, NULL, CV_DRIVER, NULL);
mutex_init(&vdc->read_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&vdc->read_cv, NULL, CV_DRIVER, NULL);
vdc->read_state = VDC_READ_IDLE;
vdc->initialized |= VDC_LOCKS;
if (vdc_get_md_node(dip, &mdp, &vd_node) != 0) {
cmn_err(CE_NOTE, "[%d] Could not get machine description node",
instance);
return (DDI_FAILURE);
}
if (vdc_init_ports(vdc, mdp, vd_node) != 0) {
cmn_err(CE_NOTE, "[%d] Error initialising ports", instance);
return (DDI_FAILURE);
}
(void) md_fini_handle(mdp);
vdc_create_io_kstats(vdc);
vdc_create_err_kstats(vdc);
vdc->vdisk_label = VD_DISK_LABEL_UNK;
vdc->vtoc = kmem_zalloc(sizeof (struct extvtoc), KM_SLEEP);
vdc->geom = kmem_zalloc(sizeof (struct dk_geom), KM_SLEEP);
vdc->minfo = kmem_zalloc(sizeof (struct dk_minfo), KM_SLEEP);
vdc->msg_proc_thr = thread_create(NULL, 0, vdc_process_msg_thread,
vdc, 0, &p0, TS_RUN, minclsyspri);
if (vdc->msg_proc_thr == NULL) {
cmn_err(CE_NOTE, "[%d] Failed to create msg processing thread",
instance);
return (DDI_FAILURE);
}
if (vdc->num_servers > 1) {
vdc->eio_thread = thread_create(NULL, 0, vdc_eio_thread, vdc, 0,
&p0, TS_RUN, v.v_maxsyspri - 2);
if (vdc->eio_thread == NULL) {
cmn_err(CE_NOTE, "[%d] Failed to create error "
"I/O thread", instance);
return (DDI_FAILURE);
}
}
vdc->initialized |= VDC_THREAD;
atomic_inc_32(&vdc_instance_count);
mutex_enter(&vdc->lock);
(void) vdc_validate_geometry(vdc);
mutex_exit(&vdc->lock);
status = vdc_create_device_nodes(vdc);
if (status) {
DMSG(vdc, 0, "[%d] Failed to create device nodes",
instance);
goto return_status;
}
vdc_set_err_kstats(vdc);
ddi_report_dev(dip);
ASSERT(vdc->lifecycle == VDC_LC_ONLINE ||
vdc->lifecycle == VDC_LC_ONLINE_PENDING);
DMSG(vdc, 0, "[%d] Attach tasks successful\n", instance);
return_status:
DMSG(vdc, 0, "[%d] Attach completed\n", instance);
return (status);
}
static int
vdc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int status;
switch (cmd) {
case DDI_ATTACH:
if ((status = vdc_do_attach(dip)) != 0)
(void) vdc_detach(dip, DDI_DETACH);
return (status);
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
vdc_do_ldc_init(vdc_t *vdc, vdc_server_t *srvr)
{
int status = 0;
ldc_status_t ldc_state;
ldc_attr_t ldc_attr;
ASSERT(vdc != NULL);
ASSERT(srvr != NULL);
ldc_attr.devclass = LDC_DEV_BLK;
ldc_attr.instance = vdc->instance;
ldc_attr.mode = LDC_MODE_UNRELIABLE;
ldc_attr.mtu = VD_LDC_MTU;
if ((srvr->state & VDC_LDC_INIT) == 0) {
status = ldc_init(srvr->ldc_id, &ldc_attr,
&srvr->ldc_handle);
if (status != 0) {
DMSG(vdc, 0, "[%d] ldc_init(chan %ld) returned %d",
vdc->instance, srvr->ldc_id, status);
return (status);
}
srvr->state |= VDC_LDC_INIT;
}
status = ldc_status(srvr->ldc_handle, &ldc_state);
if (status != 0) {
DMSG(vdc, 0, "[%d] Cannot discover LDC status [err=%d]",
vdc->instance, status);
goto init_exit;
}
srvr->ldc_state = ldc_state;
if ((srvr->state & VDC_LDC_CB) == 0) {
status = ldc_reg_callback(srvr->ldc_handle, vdc_handle_cb,
(caddr_t)srvr);
if (status != 0) {
DMSG(vdc, 0, "[%d] LDC callback reg. failed (%d)",
vdc->instance, status);
goto init_exit;
}
srvr->state |= VDC_LDC_CB;
}
if (srvr->ldc_state == LDC_INIT) {
status = ldc_open(srvr->ldc_handle);
if (status != 0) {
DMSG(vdc, 0, "[%d] ldc_open(chan %ld) returned %d",
vdc->instance, srvr->ldc_id, status);
goto init_exit;
}
srvr->state |= VDC_LDC_OPEN;
}
init_exit:
if (status) {
vdc_terminate_ldc(vdc, srvr);
}
return (status);
}
static int
vdc_start_ldc_connection(vdc_t *vdc)
{
int status = 0;
ASSERT(vdc != NULL);
ASSERT(MUTEX_HELD(&vdc->lock));
status = vdc_do_ldc_up(vdc);
DMSG(vdc, 0, "[%d] Finished bringing up LDC\n", vdc->instance);
return (status);
}
static int
vdc_stop_ldc_connection(vdc_t *vdcp)
{
int status;
ASSERT(vdcp != NULL);
ASSERT(MUTEX_HELD(&vdcp->lock));
DMSG(vdcp, 0, ": Resetting connection to vDisk server : state %d\n",
vdcp->state);
status = ldc_down(vdcp->curr_server->ldc_handle);
DMSG(vdcp, 0, "ldc_down() = %d\n", status);
vdcp->initialized &= ~VDC_HANDSHAKE;
DMSG(vdcp, 0, "initialized=%x\n", vdcp->initialized);
return (status);
}
static void
vdc_create_io_kstats(vdc_t *vdc)
{
if (vdc->io_stats != NULL) {
DMSG(vdc, 0, "[%d] I/O kstat already exists\n", vdc->instance);
return;
}
vdc->io_stats = kstat_create(VDC_DRIVER_NAME, vdc->instance, NULL,
"disk", KSTAT_TYPE_IO, 1, KSTAT_FLAG_PERSISTENT);
if (vdc->io_stats != NULL) {
vdc->io_stats->ks_lock = &vdc->lock;
kstat_install(vdc->io_stats);
} else {
cmn_err(CE_NOTE, "[%d] Failed to create kstat: I/O statistics"
" will not be gathered", vdc->instance);
}
}
static void
vdc_create_err_kstats(vdc_t *vdc)
{
vd_err_stats_t *stp;
char kstatmodule_err[KSTAT_STRLEN];
char kstatname[KSTAT_STRLEN];
int ndata = (sizeof (vd_err_stats_t) / sizeof (kstat_named_t));
int instance = vdc->instance;
if (vdc->err_stats != NULL) {
DMSG(vdc, 0, "[%d] ERR kstat already exists\n", vdc->instance);
return;
}
(void) snprintf(kstatmodule_err, sizeof (kstatmodule_err),
"%serr", VDC_DRIVER_NAME);
(void) snprintf(kstatname, sizeof (kstatname),
"%s%d,err", VDC_DRIVER_NAME, instance);
vdc->err_stats = kstat_create(kstatmodule_err, instance, kstatname,
"device_error", KSTAT_TYPE_NAMED, ndata, KSTAT_FLAG_PERSISTENT);
if (vdc->err_stats == NULL) {
cmn_err(CE_NOTE, "[%d] Failed to create kstat: Error statistics"
" will not be gathered", instance);
return;
}
stp = (vd_err_stats_t *)vdc->err_stats->ks_data;
kstat_named_init(&stp->vd_softerrs, "Soft Errors",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->vd_transerrs, "Transport Errors",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->vd_protoerrs, "Protocol Errors",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->vd_vid, "Vendor",
KSTAT_DATA_CHAR);
kstat_named_init(&stp->vd_pid, "Product",
KSTAT_DATA_CHAR);
kstat_named_init(&stp->vd_capacity, "Size",
KSTAT_DATA_ULONGLONG);
vdc->err_stats->ks_update = nulldev;
kstat_install(vdc->err_stats);
}
static void
vdc_set_err_kstats(vdc_t *vdc)
{
vd_err_stats_t *stp;
if (vdc->err_stats == NULL)
return;
mutex_enter(&vdc->lock);
stp = (vd_err_stats_t *)vdc->err_stats->ks_data;
ASSERT(stp != NULL);
stp->vd_capacity.value.ui64 = vdc->vdisk_size * vdc->vdisk_bsize;
(void) strcpy(stp->vd_vid.value.c, "SUN");
(void) strcpy(stp->vd_pid.value.c, "VDSK");
mutex_exit(&vdc->lock);
}
static int
vdc_create_device_nodes_efi(vdc_t *vdc)
{
ddi_remove_minor_node(vdc->dip, "h");
ddi_remove_minor_node(vdc->dip, "h,raw");
if (ddi_create_minor_node(vdc->dip, "wd", S_IFBLK,
VD_MAKE_DEV(vdc->instance, VD_EFI_WD_SLICE),
DDI_NT_BLOCK, 0) != DDI_SUCCESS) {
cmn_err(CE_NOTE, "[%d] Couldn't add block node 'wd'",
vdc->instance);
return (EIO);
}
vdc->initialized |= VDC_MINOR;
if (ddi_create_minor_node(vdc->dip, "wd,raw", S_IFCHR,
VD_MAKE_DEV(vdc->instance, VD_EFI_WD_SLICE),
DDI_NT_BLOCK, 0) != DDI_SUCCESS) {
cmn_err(CE_NOTE, "[%d] Couldn't add block node 'wd,raw'",
vdc->instance);
return (EIO);
}
return (0);
}
static int
vdc_create_device_nodes_vtoc(vdc_t *vdc)
{
ddi_remove_minor_node(vdc->dip, "wd");
ddi_remove_minor_node(vdc->dip, "wd,raw");
if (ddi_create_minor_node(vdc->dip, "h", S_IFBLK,
VD_MAKE_DEV(vdc->instance, VD_EFI_WD_SLICE),
DDI_NT_BLOCK, 0) != DDI_SUCCESS) {
cmn_err(CE_NOTE, "[%d] Couldn't add block node 'h'",
vdc->instance);
return (EIO);
}
vdc->initialized |= VDC_MINOR;
if (ddi_create_minor_node(vdc->dip, "h,raw", S_IFCHR,
VD_MAKE_DEV(vdc->instance, VD_EFI_WD_SLICE),
DDI_NT_BLOCK, 0) != DDI_SUCCESS) {
cmn_err(CE_NOTE, "[%d] Couldn't add block node 'h,raw'",
vdc->instance);
return (EIO);
}
return (0);
}
static int
vdc_create_device_nodes(vdc_t *vdc)
{
char name[sizeof ("s,raw")];
dev_info_t *dip = NULL;
int instance, status;
int num_slices = 1;
int i;
ASSERT(vdc != NULL);
instance = vdc->instance;
dip = vdc->dip;
switch (vdc->vdisk_type) {
case VD_DISK_TYPE_DISK:
case VD_DISK_TYPE_UNK:
num_slices = V_NUMPAR;
break;
case VD_DISK_TYPE_SLICE:
num_slices = 1;
break;
default:
ASSERT(0);
}
for (i = 0; i < num_slices; i++) {
if (i == VD_EFI_WD_SLICE) {
if (vdc->vdisk_label == VD_DISK_LABEL_EFI)
status = vdc_create_device_nodes_efi(vdc);
else
status = vdc_create_device_nodes_vtoc(vdc);
if (status != 0)
return (status);
continue;
}
(void) snprintf(name, sizeof (name), "%c", 'a' + i);
if (ddi_create_minor_node(dip, name, S_IFBLK,
VD_MAKE_DEV(instance, i), DDI_NT_BLOCK, 0) != DDI_SUCCESS) {
cmn_err(CE_NOTE, "[%d] Couldn't add block node '%s'",
instance, name);
return (EIO);
}
vdc->initialized |= VDC_MINOR;
(void) snprintf(name, sizeof (name), "%c%s", 'a' + i, ",raw");
if (ddi_create_minor_node(dip, name, S_IFCHR,
VD_MAKE_DEV(instance, i), DDI_NT_BLOCK, 0) != DDI_SUCCESS) {
cmn_err(CE_NOTE, "[%d] Couldn't add raw node '%s'",
instance, name);
return (EIO);
}
}
return (0);
}
static int
vdc_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int mod_flags,
char *name, caddr_t valuep, int *lengthp)
{
int instance = ddi_get_instance(dip);
vdc_t *vdc;
uint64_t nblocks;
uint_t blksize;
vdc = ddi_get_soft_state(vdc_state, instance);
if (dev == DDI_DEV_T_ANY || vdc == NULL) {
return (ddi_prop_op(dev, dip, prop_op, mod_flags,
name, valuep, lengthp));
}
mutex_enter(&vdc->lock);
(void) vdc_validate_geometry(vdc);
if (vdc->vdisk_label == VD_DISK_LABEL_UNK) {
mutex_exit(&vdc->lock);
return (ddi_prop_op(dev, dip, prop_op, mod_flags,
name, valuep, lengthp));
}
nblocks = vdc->slice[VDCPART(dev)].nblocks;
blksize = vdc->vdisk_bsize;
mutex_exit(&vdc->lock);
return (ddi_prop_op_nblocks_blksize(dev, dip, prop_op, mod_flags,
name, valuep, lengthp, nblocks, blksize));
}
static boolean_t
vdc_is_opened(vdc_t *vdc)
{
int i;
for (i = 0; i < V_NUMPAR; i++) {
if (vdc->open_lyr[i] > 0)
return (B_TRUE);
}
for (i = 0; i < OTYPCNT; i++) {
if (vdc->open[i] != 0)
return (B_TRUE);
}
return (B_FALSE);
}
static int
vdc_mark_opened(vdc_t *vdc, int slice, int flag, int otyp)
{
uint8_t slicemask;
int i;
ASSERT(otyp < OTYPCNT);
ASSERT(slice < V_NUMPAR);
ASSERT(MUTEX_HELD(&vdc->lock));
slicemask = 1 << slice;
if (vdc->vdisk_type == VD_DISK_TYPE_SLICE && slice != 0)
return (EIO);
if (vdc->open_excl & slicemask)
return (EBUSY);
if (flag & FEXCL) {
if (vdc->open_lyr[slice] > 0)
return (EBUSY);
for (i = 0; i < OTYPCNT; i++) {
if (vdc->open[i] & slicemask)
return (EBUSY);
}
vdc->open_excl |= slicemask;
}
if (otyp == OTYP_LYR) {
vdc->open_lyr[slice]++;
} else {
vdc->open[otyp] |= slicemask;
}
return (0);
}
static void
vdc_mark_closed(vdc_t *vdc, int slice, int flag, int otyp)
{
uint8_t slicemask;
ASSERT(otyp < OTYPCNT);
ASSERT(slice < V_NUMPAR);
ASSERT(MUTEX_HELD(&vdc->lock));
slicemask = 1 << slice;
if (otyp == OTYP_LYR) {
ASSERT(vdc->open_lyr[slice] > 0);
vdc->open_lyr[slice]--;
} else {
vdc->open[otyp] &= ~slicemask;
}
if (flag & FEXCL)
vdc->open_excl &= ~slicemask;
}
static int
vdc_open(dev_t *dev, int flag, int otyp, cred_t *cred)
{
_NOTE(ARGUNUSED(cred))
int instance, nodelay;
int slice, status = 0;
vdc_t *vdc;
ASSERT(dev != NULL);
instance = VDCUNIT(*dev);
if (otyp >= OTYPCNT)
return (EINVAL);
if ((vdc = ddi_get_soft_state(vdc_state, instance)) == NULL) {
cmn_err(CE_NOTE, "[%d] Couldn't get state structure", instance);
return (ENXIO);
}
DMSG(vdc, 0, "minor = %d flag = %x, otyp = %x\n",
getminor(*dev), flag, otyp);
slice = VDCPART(*dev);
nodelay = flag & (FNDELAY | FNONBLOCK);
if ((flag & FWRITE) && (!nodelay) &&
!(VD_OP_SUPPORTED(vdc->operations, VD_OP_BWRITE))) {
return (EROFS);
}
mutex_enter(&vdc->lock);
status = vdc_mark_opened(vdc, slice, flag, otyp);
if (status != 0) {
mutex_exit(&vdc->lock);
return (status);
}
if (vdc->vdisk_type != VD_DISK_TYPE_UNK && nodelay) {
if (vdc->validate_pending > 0) {
mutex_exit(&vdc->lock);
return (0);
}
if (taskq_dispatch(system_taskq, vdc_validate_task,
(void *)vdc, TQ_NOSLEEP) == TASKQID_INVALID) {
vdc_mark_closed(vdc, slice, flag, otyp);
mutex_exit(&vdc->lock);
return (ENXIO);
}
vdc->validate_pending++;
mutex_exit(&vdc->lock);
return (0);
}
mutex_exit(&vdc->lock);
vdc_validate(vdc);
mutex_enter(&vdc->lock);
if (vdc->vdisk_type == VD_DISK_TYPE_UNK ||
(vdc->vdisk_type == VD_DISK_TYPE_SLICE && slice != 0) ||
(!nodelay && (vdc->vdisk_label == VD_DISK_LABEL_UNK ||
vdc->slice[slice].nblocks == 0))) {
vdc_mark_closed(vdc, slice, flag, otyp);
status = EIO;
}
mutex_exit(&vdc->lock);
return (status);
}
static int
vdc_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
_NOTE(ARGUNUSED(cred))
int instance;
int slice;
int rv, rval;
vdc_t *vdc;
instance = VDCUNIT(dev);
if (otyp >= OTYPCNT)
return (EINVAL);
if ((vdc = ddi_get_soft_state(vdc_state, instance)) == NULL) {
cmn_err(CE_NOTE, "[%d] Couldn't get state structure", instance);
return (ENXIO);
}
DMSG(vdc, 0, "[%d] flag = %x, otyp = %x\n", instance, flag, otyp);
slice = VDCPART(dev);
rv = vd_process_ioctl(dev, DKIOCFLUSHWRITECACHE, NULL, FKIOCTL, &rval);
if (rv != 0 && rv != ENOTSUP && rv != ENOTTY && rv != EROFS) {
DMSG(vdc, 0, "[%d] flush failed with error %d on close\n",
instance, rv);
return (EIO);
}
mutex_enter(&vdc->lock);
vdc_mark_closed(vdc, slice, flag, otyp);
mutex_exit(&vdc->lock);
return (0);
}
static int
vdc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp)
{
_NOTE(ARGUNUSED(credp))
return (vd_process_ioctl(dev, cmd, (caddr_t)arg, mode, rvalp));
}
static int
vdc_print(dev_t dev, char *str)
{
cmn_err(CE_NOTE, "vdc%d: %s", VDCUNIT(dev), str);
return (0);
}
static int
vdc_dump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk)
{
int rv, flags;
size_t nbytes = nblk * DEV_BSIZE;
int instance = VDCUNIT(dev);
vdc_t *vdc = NULL;
diskaddr_t vio_blkno;
if ((vdc = ddi_get_soft_state(vdc_state, instance)) == NULL) {
cmn_err(CE_NOTE, "[%d] Couldn't get state structure", instance);
return (ENXIO);
}
DMSG(vdc, 2, "[%d] dump %ld bytes at block 0x%lx : addr=0x%p\n",
instance, nbytes, blkno, (void *)addr);
if ((blkno & vdc->vio_bmask) != 0) {
DMSG(vdc, 0, "Misaligned block number (%lu)\n", blkno);
return (EINVAL);
}
vio_blkno = blkno >> vdc->vio_bshift;
flags = (ddi_in_panic())? VDC_OP_STATE_RUNNING : VDC_OP_NORMAL;
rv = vdc_do_op(vdc, VD_OP_BWRITE, addr, nbytes, VDCPART(dev),
vio_blkno, NULL, VIO_write_dir, flags);
if (rv) {
DMSG(vdc, 0, "Failed to do a disk dump (err=%d)\n", rv);
return (rv);
}
DMSG(vdc, 0, "[%d] End\n", instance);
return (0);
}
static int
vdc_strategy(struct buf *buf)
{
diskaddr_t vio_blkno;
vdc_t *vdc = NULL;
int instance = VDCUNIT(buf->b_edev);
int op = (buf->b_flags & B_READ) ? VD_OP_BREAD : VD_OP_BWRITE;
int slice;
if ((vdc = ddi_get_soft_state(vdc_state, instance)) == NULL) {
cmn_err(CE_NOTE, "[%d] Couldn't get state structure", instance);
bioerror(buf, ENXIO);
biodone(buf);
return (0);
}
DMSG(vdc, 2, "[%d] %s %ld bytes at block %llx : b_addr=0x%p\n",
instance, (buf->b_flags & B_READ) ? "Read" : "Write",
buf->b_bcount, buf->b_lblkno, (void *)buf->b_un.b_addr);
bp_mapin(buf);
if ((long)buf->b_private == VD_SLICE_NONE) {
slice = VD_SLICE_NONE;
} else {
slice = VDCPART(buf->b_edev);
}
if ((buf->b_lblkno & vdc->vio_bmask) != 0) {
bioerror(buf, EINVAL);
biodone(buf);
return (0);
}
vio_blkno = buf->b_lblkno >> vdc->vio_bshift;
(void) vdc_do_op(vdc, op, (caddr_t)buf->b_un.b_addr,
buf->b_bcount, slice, vio_blkno,
buf, (op == VD_OP_BREAD) ? VIO_read_dir : VIO_write_dir,
VDC_OP_NORMAL);
return (0);
}
static void
vdc_min(struct buf *bufp)
{
vdc_t *vdc = NULL;
int instance = VDCUNIT(bufp->b_edev);
vdc = ddi_get_soft_state(vdc_state, instance);
VERIFY(vdc != NULL);
if (bufp->b_bcount > (vdc->max_xfer_sz * vdc->vdisk_bsize)) {
bufp->b_bcount = vdc->max_xfer_sz * vdc->vdisk_bsize;
}
}
static int
vdc_read(dev_t dev, struct uio *uio, cred_t *cred)
{
_NOTE(ARGUNUSED(cred))
DMSGX(1, "[%d] Entered", VDCUNIT(dev));
return (physio(vdc_strategy, NULL, dev, B_READ, vdc_min, uio));
}
static int
vdc_write(dev_t dev, struct uio *uio, cred_t *cred)
{
_NOTE(ARGUNUSED(cred))
DMSGX(1, "[%d] Entered", VDCUNIT(dev));
return (physio(vdc_strategy, NULL, dev, B_WRITE, vdc_min, uio));
}
static int
vdc_aread(dev_t dev, struct aio_req *aio, cred_t *cred)
{
_NOTE(ARGUNUSED(cred))
DMSGX(1, "[%d] Entered", VDCUNIT(dev));
return (aphysio(vdc_strategy, anocancel, dev, B_READ, vdc_min, aio));
}
static int
vdc_awrite(dev_t dev, struct aio_req *aio, cred_t *cred)
{
_NOTE(ARGUNUSED(cred))
DMSGX(1, "[%d] Entered", VDCUNIT(dev));
return (aphysio(vdc_strategy, anocancel, dev, B_WRITE, vdc_min, aio));
}
static int
vdc_init_ver_negotiation(vdc_t *vdc, vio_ver_t ver)
{
vio_ver_msg_t pkt;
size_t msglen = sizeof (pkt);
int status = -1;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
DMSG(vdc, 0, "[%d] Entered.\n", vdc->instance);
vdc->session_id = ((uint32_t)gettick() & 0xffffffff);
DMSG(vdc, 0, "[%d] Set SID to 0x%lx\n", vdc->instance, vdc->session_id);
pkt.tag.vio_msgtype = VIO_TYPE_CTRL;
pkt.tag.vio_subtype = VIO_SUBTYPE_INFO;
pkt.tag.vio_subtype_env = VIO_VER_INFO;
pkt.tag.vio_sid = vdc->session_id;
pkt.dev_class = VDEV_DISK;
pkt.ver_major = ver.major;
pkt.ver_minor = ver.minor;
status = vdc_send(vdc, (caddr_t)&pkt, &msglen);
DMSG(vdc, 0, "[%d] Ver info sent (status = %d)\n",
vdc->instance, status);
if ((status != 0) || (msglen != sizeof (vio_ver_msg_t))) {
DMSG(vdc, 0, "[%d] Failed to send Ver negotiation info: "
"id(%lx) rv(%d) size(%ld)", vdc->instance,
vdc->curr_server->ldc_handle, status, msglen);
if (msglen != sizeof (vio_ver_msg_t))
status = ENOMSG;
}
return (status);
}
static int
vdc_ver_negotiation(vdc_t *vdcp)
{
vio_msg_t vio_msg;
int status;
if (status = vdc_init_ver_negotiation(vdcp, vdc_version[0]))
return (status);
mutex_exit(&vdcp->lock);
status = vdc_wait_for_response(vdcp, &vio_msg);
mutex_enter(&vdcp->lock);
if (status) {
DMSG(vdcp, 0,
"[%d] Failed waiting for Ver negotiation response, rv(%d)",
vdcp->instance, status);
return (status);
}
if (vio_msg.tag.vio_msgtype != VIO_TYPE_CTRL ||
vio_msg.tag.vio_subtype == VIO_SUBTYPE_INFO) {
DMSG(vdcp, 0, "[%d] Invalid ver negotiation response\n",
vdcp->instance);
return (EPROTO);
}
return (vdc_handle_ver_msg(vdcp, (vio_ver_msg_t *)&vio_msg));
}
static int
vdc_init_attr_negotiation(vdc_t *vdc)
{
vd_attr_msg_t pkt;
size_t msglen = sizeof (pkt);
int status;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
DMSG(vdc, 0, "[%d] entered\n", vdc->instance);
pkt.tag.vio_msgtype = VIO_TYPE_CTRL;
pkt.tag.vio_subtype = VIO_SUBTYPE_INFO;
pkt.tag.vio_subtype_env = VIO_ATTR_INFO;
pkt.tag.vio_sid = vdc->session_id;
pkt.max_xfer_sz = vdc->max_xfer_sz;
pkt.vdisk_block_size = vdc->vdisk_bsize;
pkt.xfer_mode = VIO_DRING_MODE_V1_0;
pkt.operations = 0;
pkt.vdisk_type = 0;
pkt.vdisk_media = 0;
pkt.vdisk_size = 0;
status = vdc_send(vdc, (caddr_t)&pkt, &msglen);
DMSG(vdc, 0, "Attr info sent (status = %d)\n", status);
if ((status != 0) || (msglen != sizeof (vd_attr_msg_t))) {
DMSG(vdc, 0, "[%d] Failed to send Attr negotiation info: "
"id(%lx) rv(%d) size(%ld)", vdc->instance,
vdc->curr_server->ldc_handle, status, msglen);
if (msglen != sizeof (vd_attr_msg_t))
status = ENOMSG;
}
return (status);
}
static int
vdc_attr_negotiation(vdc_t *vdcp)
{
int status;
vio_msg_t vio_msg;
if (status = vdc_init_attr_negotiation(vdcp))
return (status);
mutex_exit(&vdcp->lock);
status = vdc_wait_for_response(vdcp, &vio_msg);
mutex_enter(&vdcp->lock);
if (status) {
DMSG(vdcp, 0,
"[%d] Failed waiting for Attr negotiation response, rv(%d)",
vdcp->instance, status);
return (status);
}
if (vio_msg.tag.vio_msgtype != VIO_TYPE_CTRL ||
vio_msg.tag.vio_subtype == VIO_SUBTYPE_INFO) {
DMSG(vdcp, 0, "[%d] Invalid attr negotiation response\n",
vdcp->instance);
return (EPROTO);
}
return (vdc_handle_attr_msg(vdcp, (vd_attr_msg_t *)&vio_msg));
}
static int
vdc_init_dring_negotiate(vdc_t *vdc)
{
vio_dring_reg_msg_t pkt;
size_t msglen = sizeof (pkt);
int status = -1;
int retry;
int nretries = 10;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
for (retry = 0; retry < nretries; retry++) {
status = vdc_init_descriptor_ring(vdc);
if (status != EAGAIN)
break;
drv_usecwait(vdc_min_timeout_ldc);
}
if (status != 0) {
DMSG(vdc, 0, "[%d] Failed to init DRing (status = %d)\n",
vdc->instance, status);
return (status);
}
DMSG(vdc, 0, "[%d] Init of descriptor ring completed (status = %d)\n",
vdc->instance, status);
pkt.tag.vio_msgtype = VIO_TYPE_CTRL;
pkt.tag.vio_subtype = VIO_SUBTYPE_INFO;
pkt.tag.vio_subtype_env = VIO_DRING_REG;
pkt.tag.vio_sid = vdc->session_id;
pkt.dring_ident = 0;
pkt.num_descriptors = vdc->dring_len;
pkt.descriptor_size = vdc->dring_entry_size;
pkt.options = (VIO_TX_DRING | VIO_RX_DRING);
pkt.ncookies = vdc->dring_cookie_count;
pkt.cookie[0] = vdc->dring_cookie[0];
status = vdc_send(vdc, (caddr_t)&pkt, &msglen);
if (status != 0) {
DMSG(vdc, 0, "[%d] Failed to register DRing (err = %d)",
vdc->instance, status);
}
return (status);
}
static int
vdc_dring_negotiation(vdc_t *vdcp)
{
int status;
vio_msg_t vio_msg;
if (status = vdc_init_dring_negotiate(vdcp))
return (status);
mutex_exit(&vdcp->lock);
status = vdc_wait_for_response(vdcp, &vio_msg);
mutex_enter(&vdcp->lock);
if (status) {
DMSG(vdcp, 0,
"[%d] Failed waiting for Dring negotiation response,"
" rv(%d)", vdcp->instance, status);
return (status);
}
if (vio_msg.tag.vio_msgtype != VIO_TYPE_CTRL ||
vio_msg.tag.vio_subtype == VIO_SUBTYPE_INFO) {
DMSG(vdcp, 0, "[%d] Invalid Dring negotiation response\n",
vdcp->instance);
return (EPROTO);
}
return (vdc_handle_dring_reg_msg(vdcp,
(vio_dring_reg_msg_t *)&vio_msg));
}
static int
vdc_send_rdx(vdc_t *vdcp)
{
vio_msg_t msg;
size_t msglen = sizeof (vio_msg_t);
int status;
msg.tag.vio_msgtype = VIO_TYPE_CTRL;
msg.tag.vio_subtype = VIO_SUBTYPE_INFO;
msg.tag.vio_subtype_env = VIO_RDX;
msg.tag.vio_sid = vdcp->session_id;
status = vdc_send(vdcp, (caddr_t)&msg, &msglen);
if (status != 0) {
DMSG(vdcp, 0, "[%d] Failed to send RDX message (%d)",
vdcp->instance, status);
}
return (status);
}
static int
vdc_handle_rdx(vdc_t *vdcp, vio_rdx_msg_t *msgp)
{
_NOTE(ARGUNUSED(vdcp))
_NOTE(ARGUNUSED(msgp))
ASSERT(msgp->tag.vio_msgtype == VIO_TYPE_CTRL);
ASSERT(msgp->tag.vio_subtype == VIO_SUBTYPE_ACK);
ASSERT(msgp->tag.vio_subtype_env == VIO_RDX);
DMSG(vdcp, 1, "[%d] Got an RDX msg", vdcp->instance);
return (0);
}
static int
vdc_rdx_exchange(vdc_t *vdcp)
{
int status;
vio_msg_t vio_msg;
if (status = vdc_send_rdx(vdcp))
return (status);
mutex_exit(&vdcp->lock);
status = vdc_wait_for_response(vdcp, &vio_msg);
mutex_enter(&vdcp->lock);
if (status) {
DMSG(vdcp, 0, "[%d] Failed waiting for RDX response, rv(%d)",
vdcp->instance, status);
return (status);
}
if (vio_msg.tag.vio_msgtype != VIO_TYPE_CTRL ||
vio_msg.tag.vio_subtype != VIO_SUBTYPE_ACK) {
DMSG(vdcp, 0, "[%d] Invalid RDX response\n", vdcp->instance);
return (EPROTO);
}
return (vdc_handle_rdx(vdcp, (vio_rdx_msg_t *)&vio_msg));
}
static int
vdc_recv(vdc_t *vdc, vio_msg_t *msgp, size_t *nbytesp)
{
int status;
uint64_t delay_time;
size_t len;
delay_time = vdc_ldc_read_init_delay;
for (;;) {
len = *nbytesp;
status = ldc_read(vdc->curr_server->ldc_handle,
(caddr_t)msgp, &len);
if (status == EAGAIN) {
delay_time *= 2;
if (delay_time >= vdc_ldc_read_max_delay)
delay_time = vdc_ldc_read_max_delay;
delay(delay_time);
continue;
}
if (status != 0) {
DMSG(vdc, 0, "ldc_read returned %d\n", status);
break;
}
if (len != 0) {
*nbytesp = len;
break;
}
mutex_enter(&vdc->read_lock);
while (vdc->read_state != VDC_READ_PENDING) {
if (vdc->read_state == VDC_READ_RESET) {
mutex_exit(&vdc->read_lock);
return (ECONNRESET);
}
vdc->read_state = VDC_READ_WAITING;
cv_wait(&vdc->read_cv, &vdc->read_lock);
}
vdc->read_state = VDC_READ_IDLE;
mutex_exit(&vdc->read_lock);
delay_time = vdc_ldc_read_init_delay;
}
return (status);
}
#ifdef DEBUG
void
vdc_decode_tag(vdc_t *vdcp, vio_msg_t *msg)
{
char *ms, *ss, *ses;
switch (msg->tag.vio_msgtype) {
#define Q(_s) case _s : ms = #_s; break;
Q(VIO_TYPE_CTRL)
Q(VIO_TYPE_DATA)
Q(VIO_TYPE_ERR)
#undef Q
default: ms = "unknown"; break;
}
switch (msg->tag.vio_subtype) {
#define Q(_s) case _s : ss = #_s; break;
Q(VIO_SUBTYPE_INFO)
Q(VIO_SUBTYPE_ACK)
Q(VIO_SUBTYPE_NACK)
#undef Q
default: ss = "unknown"; break;
}
switch (msg->tag.vio_subtype_env) {
#define Q(_s) case _s : ses = #_s; break;
Q(VIO_VER_INFO)
Q(VIO_ATTR_INFO)
Q(VIO_DRING_REG)
Q(VIO_DRING_UNREG)
Q(VIO_RDX)
Q(VIO_PKT_DATA)
Q(VIO_DESC_DATA)
Q(VIO_DRING_DATA)
#undef Q
default: ses = "unknown"; break;
}
DMSG(vdcp, 3, "(%x/%x/%x) message : (%s/%s/%s)\n",
msg->tag.vio_msgtype, msg->tag.vio_subtype,
msg->tag.vio_subtype_env, ms, ss, ses);
}
#endif
static int
vdc_send(vdc_t *vdc, caddr_t pkt, size_t *msglen)
{
size_t size = 0;
int status = 0;
clock_t delay_ticks;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
ASSERT(msglen != NULL);
ASSERT(*msglen != 0);
#ifdef DEBUG
vdc_decode_tag(vdc, (vio_msg_t *)(uintptr_t)pkt);
#endif
delay_ticks = vdc_hz_min_ldc_delay;
do {
size = *msglen;
status = ldc_write(vdc->curr_server->ldc_handle, pkt, &size);
if (status == EWOULDBLOCK) {
delay(delay_ticks);
delay_ticks *= 2;
if (delay_ticks > vdc_hz_max_ldc_delay)
delay_ticks = vdc_hz_max_ldc_delay;
}
} while (status == EWOULDBLOCK);
if (status == EIO || status == ECONNRESET) {
mutex_enter(&vdc->read_lock);
if ((vdc->read_state == VDC_READ_WAITING) ||
(vdc->read_state == VDC_READ_RESET))
cv_signal(&vdc->read_cv);
vdc->read_state = VDC_READ_RESET;
mutex_exit(&vdc->read_lock);
if (vdc->state == VDC_STATE_INIT_WAITING) {
DMSG(vdc, 0, "[%d] write reset - "
"vdc is resetting ..\n", vdc->instance);
vdc->state = VDC_STATE_RESETTING;
cv_signal(&vdc->initwait_cv);
}
return (ECONNRESET);
}
*msglen = size;
return (status);
}
static int
vdc_get_md_node(dev_info_t *dip, md_t **mdpp, mde_cookie_t *vd_nodep)
{
int status = ENOENT;
char *node_name = NULL;
md_t *mdp = NULL;
int num_nodes;
int num_vdevs;
mde_cookie_t rootnode;
mde_cookie_t *listp = NULL;
boolean_t found_inst = B_FALSE;
int listsz;
int idx;
uint64_t md_inst;
int obp_inst;
int instance = ddi_get_instance(dip);
if (!ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, OBP_REG)) {
cmn_err(CE_WARN, "'%s' property does not exist", OBP_REG);
return (ENOENT);
}
obp_inst = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
OBP_REG, -1);
DMSGX(1, "[%d] OBP inst=%d\n", instance, obp_inst);
if ((mdp = md_get_handle()) == NULL) {
cmn_err(CE_WARN, "unable to init machine description");
return (ENXIO);
}
num_nodes = md_node_count(mdp);
ASSERT(num_nodes > 0);
listsz = num_nodes * sizeof (mde_cookie_t);
listp = kmem_zalloc(listsz, KM_SLEEP);
rootnode = md_root_node(mdp);
ASSERT(rootnode != MDE_INVAL_ELEM_COOKIE);
num_vdevs = md_scan_dag(mdp, rootnode,
md_find_name(mdp, VDC_MD_VDEV_NAME),
md_find_name(mdp, "fwd"), listp);
if (num_vdevs <= 0) {
cmn_err(CE_NOTE, "No '%s' node found", VDC_MD_VDEV_NAME);
status = ENOENT;
goto done;
}
DMSGX(1, "[%d] num_vdevs=%d\n", instance, num_vdevs);
for (idx = 0; idx < num_vdevs; idx++) {
status = md_get_prop_str(mdp, listp[idx], "name", &node_name);
if ((status != 0) || (node_name == NULL)) {
cmn_err(CE_NOTE, "Unable to get name of node type '%s'"
": err %d", VDC_MD_VDEV_NAME, status);
continue;
}
DMSGX(1, "[%d] Found node '%s'\n", instance, node_name);
if (strcmp(VDC_MD_DISK_NAME, node_name) == 0) {
status = md_get_prop_val(mdp, listp[idx],
VDC_MD_CFG_HDL, &md_inst);
DMSGX(1, "[%d] vdc inst in MD=%lx\n",
instance, md_inst);
if ((status == 0) && (md_inst == obp_inst)) {
found_inst = B_TRUE;
break;
}
}
}
if (!found_inst) {
DMSGX(0, "Unable to find correct '%s' node", VDC_MD_DISK_NAME);
status = ENOENT;
goto done;
}
DMSGX(0, "[%d] MD inst=%lx\n", instance, md_inst);
*vd_nodep = listp[idx];
*mdpp = mdp;
done:
kmem_free(listp, listsz);
return (status);
}
static int
vdc_init_ports(vdc_t *vdc, md_t *mdp, mde_cookie_t vd_nodep)
{
int status = 0;
int idx;
int num_nodes;
int num_vports;
int num_chans;
int listsz;
mde_cookie_t vd_port;
mde_cookie_t *chanp = NULL;
mde_cookie_t *portp = NULL;
vdc_server_t *srvr;
vdc_server_t *prev_srvr = NULL;
num_nodes = md_node_count(mdp);
ASSERT(num_nodes > 0);
listsz = num_nodes * sizeof (mde_cookie_t);
portp = kmem_zalloc(listsz, KM_SLEEP);
chanp = kmem_zalloc(listsz, KM_SLEEP);
num_vports = md_scan_dag(mdp, vd_nodep,
md_find_name(mdp, VDC_MD_PORT_NAME),
md_find_name(mdp, "fwd"), portp);
if (num_vports == 0) {
DMSGX(0, "Found no '%s' node for '%s' port\n",
VDC_MD_PORT_NAME, VDC_MD_VDEV_NAME);
status = ENOENT;
goto done;
}
DMSGX(1, "Found %d '%s' node(s) for '%s' port\n",
num_vports, VDC_MD_PORT_NAME, VDC_MD_VDEV_NAME);
vdc->num_servers = 0;
for (idx = 0; idx < num_vports; idx++) {
vd_port = portp[idx];
srvr = kmem_zalloc(sizeof (vdc_server_t), KM_SLEEP);
srvr->vdcp = vdc;
srvr->svc_state = VDC_SERVICE_OFFLINE;
srvr->log_state = VDC_SERVICE_NONE;
if (md_get_prop_val(mdp, vd_port, VDC_MD_ID, &srvr->id) != 0) {
cmn_err(CE_NOTE, "vDisk port '%s' property not found",
VDC_MD_ID);
kmem_free(srvr, sizeof (vdc_server_t));
continue;
}
if (md_get_prop_val(mdp, vd_port, VDC_MD_TIMEOUT,
&srvr->ctimeout) != 0) {
srvr->ctimeout = 0;
}
num_chans = md_scan_dag(mdp, vd_port,
md_find_name(mdp, VDC_MD_CHAN_NAME),
md_find_name(mdp, "fwd"), chanp);
if (num_chans <= 0) {
cmn_err(CE_NOTE, "No '%s' node for '%s' port",
VDC_MD_CHAN_NAME, VDC_MD_VDEV_NAME);
kmem_free(srvr, sizeof (vdc_server_t));
continue;
} else if (num_chans != 1) {
DMSGX(0, "Expected 1 '%s' node for '%s' port, "
"found %d\n", VDC_MD_CHAN_NAME, VDC_MD_VDEV_NAME,
num_chans);
}
if (md_get_prop_val(mdp, chanp[0], VDC_MD_ID,
&srvr->ldc_id) != 0) {
cmn_err(CE_NOTE, "Channel '%s' property not found",
VDC_MD_ID);
kmem_free(srvr, sizeof (vdc_server_t));
continue;
}
if (vdc_do_ldc_init(vdc, srvr) != 0) {
kmem_free(srvr, sizeof (vdc_server_t));
continue;
}
if (prev_srvr)
prev_srvr->next = srvr;
else
vdc->server_list = srvr;
prev_srvr = srvr;
vdc->num_servers++;
}
if (vdc->server_list != NULL) {
vdc->curr_server = vdc->server_list;
status = 0;
} else {
status = ENOENT;
}
done:
kmem_free(chanp, listsz);
kmem_free(portp, listsz);
return (status);
}
static int
vdc_do_ldc_up(vdc_t *vdc)
{
int status;
ldc_status_t ldc_state;
ASSERT(MUTEX_HELD(&vdc->lock));
DMSG(vdc, 0, "[%d] Bringing up channel %lx\n",
vdc->instance, vdc->curr_server->ldc_id);
if (vdc->lifecycle == VDC_LC_DETACHING)
return (EINVAL);
if ((status = ldc_up(vdc->curr_server->ldc_handle)) != 0) {
switch (status) {
case ECONNREFUSED:
DMSG(vdc, 0, "[%d] ldc_up(%lx,...) return %d\n",
vdc->instance, vdc->curr_server->ldc_id, status);
status = 0;
break;
default:
DMSG(vdc, 0, "[%d] Failed to bring up LDC: "
"channel=%ld, err=%d", vdc->instance,
vdc->curr_server->ldc_id, status);
break;
}
}
if (ldc_status(vdc->curr_server->ldc_handle, &ldc_state) == 0) {
vdc->curr_server->ldc_state = ldc_state;
if (ldc_state == LDC_UP) {
DMSG(vdc, 0, "[%d] LDC channel already up\n",
vdc->instance);
vdc->seq_num = 1;
vdc->seq_num_reply = 0;
}
}
return (status);
}
static void
vdc_terminate_ldc(vdc_t *vdc, vdc_server_t *srvr)
{
int instance = ddi_get_instance(vdc->dip);
if (srvr->state & VDC_LDC_OPEN) {
DMSG(vdc, 0, "[%d] ldc_close()\n", instance);
(void) ldc_close(srvr->ldc_handle);
}
if (srvr->state & VDC_LDC_CB) {
DMSG(vdc, 0, "[%d] ldc_unreg_callback()\n", instance);
(void) ldc_unreg_callback(srvr->ldc_handle);
}
if (srvr->state & VDC_LDC_INIT) {
DMSG(vdc, 0, "[%d] ldc_fini()\n", instance);
(void) ldc_fini(srvr->ldc_handle);
srvr->ldc_handle = 0;
}
srvr->state &= ~(VDC_LDC_INIT | VDC_LDC_CB | VDC_LDC_OPEN);
}
static void
vdc_fini_ports(vdc_t *vdc)
{
int instance = ddi_get_instance(vdc->dip);
vdc_server_t *srvr, *prev_srvr;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
DMSG(vdc, 0, "[%d] initialized=%x\n", instance, vdc->initialized);
srvr = vdc->server_list;
while (srvr) {
vdc_terminate_ldc(vdc, srvr);
prev_srvr = srvr;
srvr = srvr->next;
kmem_free(prev_srvr, sizeof (vdc_server_t));
}
vdc->server_list = NULL;
vdc->num_servers = 0;
}
static int
vdc_init_descriptor_ring(vdc_t *vdc)
{
vd_dring_entry_t *dep = NULL;
int status = 0;
int i;
DMSG(vdc, 0, "[%d] initialized=%x\n", vdc->instance, vdc->initialized);
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
ASSERT(maxphys <= VD_MAX_BLOCK_SIZE);
if ((vdc->initialized & VDC_DRING_INIT) == 0) {
DMSG(vdc, 0, "[%d] ldc_mem_dring_create\n", vdc->instance);
if ((vdc->max_xfer_sz * vdc->vdisk_bsize) < maxphys) {
DMSG(vdc, 0, "[%d] using minimum DRing size\n",
vdc->instance);
vdc->dring_max_cookies = maxphys / PAGESIZE;
} else {
vdc->dring_max_cookies =
(vdc->max_xfer_sz * vdc->vdisk_bsize) / PAGESIZE;
}
vdc->dring_entry_size = (sizeof (vd_dring_entry_t) +
(sizeof (ldc_mem_cookie_t) *
(vdc->dring_max_cookies - 1)));
vdc->dring_len = VD_DRING_LEN;
status = ldc_mem_dring_create(vdc->dring_len,
vdc->dring_entry_size, &vdc->dring_hdl);
if ((vdc->dring_hdl == 0) || (status != 0)) {
DMSG(vdc, 0, "[%d] Descriptor ring creation failed",
vdc->instance);
return (status);
}
vdc->initialized |= VDC_DRING_INIT;
}
if ((vdc->initialized & VDC_DRING_BOUND) == 0) {
DMSG(vdc, 0, "[%d] ldc_mem_dring_bind\n", vdc->instance);
vdc->dring_cookie =
kmem_zalloc(sizeof (ldc_mem_cookie_t), KM_SLEEP);
status = ldc_mem_dring_bind(vdc->curr_server->ldc_handle,
vdc->dring_hdl,
LDC_SHADOW_MAP|LDC_DIRECT_MAP, LDC_MEM_RW,
&vdc->dring_cookie[0],
&vdc->dring_cookie_count);
if (status != 0) {
DMSG(vdc, 0, "[%d] Failed to bind descriptor ring "
"(%lx) to channel (%lx) status=%d\n",
vdc->instance, vdc->dring_hdl,
vdc->curr_server->ldc_handle, status);
return (status);
}
ASSERT(vdc->dring_cookie_count == 1);
vdc->initialized |= VDC_DRING_BOUND;
}
status = ldc_mem_dring_info(vdc->dring_hdl, &vdc->dring_mem_info);
if (status != 0) {
DMSG(vdc, 0,
"[%d] Failed to get info for descriptor ring (%lx)\n",
vdc->instance, vdc->dring_hdl);
return (status);
}
if ((vdc->initialized & VDC_DRING_LOCAL) == 0) {
DMSG(vdc, 0, "[%d] local dring\n", vdc->instance);
vdc->local_dring =
kmem_zalloc(vdc->dring_len * sizeof (vdc_local_desc_t),
KM_SLEEP);
vdc->initialized |= VDC_DRING_LOCAL;
}
vdc->initialized |= VDC_DRING_ENTRY;
for (i = 0; i < vdc->dring_len; i++) {
dep = VDC_GET_DRING_ENTRY_PTR(vdc, i);
dep->hdr.dstate = VIO_DESC_FREE;
status = ldc_mem_alloc_handle(vdc->curr_server->ldc_handle,
&vdc->local_dring[i].desc_mhdl);
if (status != 0) {
DMSG(vdc, 0, "![%d] Failed to alloc mem handle for"
" descriptor %d", vdc->instance, i);
return (status);
}
vdc->local_dring[i].is_free = B_TRUE;
vdc->local_dring[i].dep = dep;
}
vdc->dring_curr_idx = VDC_DRING_FIRST_ENTRY;
return (status);
}
static void
vdc_destroy_descriptor_ring(vdc_t *vdc)
{
vdc_local_desc_t *ldep = NULL;
ldc_mem_handle_t mhdl = 0;
ldc_mem_info_t minfo;
int status = -1;
int i;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
DMSG(vdc, 0, "[%d] Entered\n", vdc->instance);
if (vdc->initialized & VDC_DRING_ENTRY) {
DMSG(vdc, 0,
"[%d] Removing Local DRing entries\n", vdc->instance);
for (i = 0; i < vdc->dring_len; i++) {
ldep = &vdc->local_dring[i];
mhdl = ldep->desc_mhdl;
if (mhdl == 0)
continue;
if ((status = ldc_mem_info(mhdl, &minfo)) != 0) {
DMSG(vdc, 0,
"ldc_mem_info returned an error: %d\n",
status);
ldep->desc_mhdl = 0;
continue;
}
if (minfo.status == LDC_BOUND) {
(void) ldc_mem_unbind_handle(mhdl);
}
(void) ldc_mem_free_handle(mhdl);
ldep->desc_mhdl = 0;
}
vdc->initialized &= ~VDC_DRING_ENTRY;
}
if (vdc->initialized & VDC_DRING_LOCAL) {
DMSG(vdc, 0, "[%d] Freeing Local DRing\n", vdc->instance);
kmem_free(vdc->local_dring,
vdc->dring_len * sizeof (vdc_local_desc_t));
vdc->initialized &= ~VDC_DRING_LOCAL;
}
if (vdc->initialized & VDC_DRING_BOUND) {
DMSG(vdc, 0, "[%d] Unbinding DRing\n", vdc->instance);
status = ldc_mem_dring_unbind(vdc->dring_hdl);
if (status == 0) {
vdc->initialized &= ~VDC_DRING_BOUND;
} else {
DMSG(vdc, 0, "[%d] Error %d unbinding DRing %lx",
vdc->instance, status, vdc->dring_hdl);
}
kmem_free(vdc->dring_cookie, sizeof (ldc_mem_cookie_t));
}
if (vdc->initialized & VDC_DRING_INIT) {
DMSG(vdc, 0, "[%d] Destroying DRing\n", vdc->instance);
status = ldc_mem_dring_destroy(vdc->dring_hdl);
if (status == 0) {
vdc->dring_hdl = 0;
bzero(&vdc->dring_mem_info, sizeof (ldc_mem_info_t));
vdc->initialized &= ~VDC_DRING_INIT;
} else {
DMSG(vdc, 0, "[%d] Error %d destroying DRing (%lx)",
vdc->instance, status, vdc->dring_hdl);
}
}
}
static int
vdc_map_to_shared_dring(vdc_t *vdcp, int idx)
{
vdc_local_desc_t *ldep;
vd_dring_entry_t *dep;
int rv;
ldep = &(vdcp->local_dring[idx]);
if (ldep->nbytes > 0) {
rv = vdc_populate_mem_hdl(vdcp, ldep);
if (rv) {
DMSG(vdcp, 0, "[%d] Cannot populate mem handle\n",
vdcp->instance);
return (rv);
}
}
dep = ldep->dep;
ASSERT(dep != NULL);
dep->payload.req_id = VDC_GET_NEXT_REQ_ID(vdcp);
dep->payload.operation = ldep->operation;
dep->payload.addr = ldep->offset;
dep->payload.nbytes = ldep->nbytes;
dep->payload.status = (uint32_t)-1;
dep->payload.slice = ldep->slice;
dep->hdr.dstate = VIO_DESC_READY;
dep->hdr.ack = 1;
return (0);
}
static int
vdc_send_request(vdc_t *vdcp, int operation, caddr_t addr,
size_t nbytes, int slice, diskaddr_t offset, buf_t *bufp,
vio_desc_direction_t dir, int flags)
{
int rv = 0;
ASSERT(vdcp != NULL);
ASSERT(slice == VD_SLICE_NONE || slice < V_NUMPAR);
mutex_enter(&vdcp->lock);
if (((operation == VD_OP_BREAD) || (operation == VD_OP_BWRITE)) &&
!(flags & VDC_OP_RESUBMIT)) {
DTRACE_IO1(start, buf_t *, bufp);
VD_KSTAT_WAITQ_ENTER(vdcp);
}
if (!(flags & VDC_OP_STATE_RUNNING)) {
rv = vdc_populate_descriptor(vdcp, operation, addr,
nbytes, slice, offset, bufp, dir, flags);
goto done;
}
do {
while (vdcp->state != VDC_STATE_RUNNING) {
if (vdcp->state == VDC_STATE_DETACH) {
rv = ENXIO;
goto done;
}
if (ddi_in_panic()) {
rv = EIO;
goto done;
}
if (vdcp->state == VDC_STATE_FAILED) {
vdcp->io_pending = B_TRUE;
cv_signal(&vdcp->io_pending_cv);
}
cv_wait(&vdcp->running_cv, &vdcp->lock);
if (vdcp->state == VDC_STATE_FAILED) {
rv = EIO;
goto done;
}
}
} while (vdc_populate_descriptor(vdcp, operation, addr,
nbytes, slice, offset, bufp, dir, flags & ~VDC_OP_RESUBMIT));
done:
if ((operation == VD_OP_BREAD) || (operation == VD_OP_BWRITE)) {
if (rv == 0) {
VD_KSTAT_WAITQ_TO_RUNQ(vdcp);
DTRACE_PROBE1(send, buf_t *, bufp);
} else {
VD_UPDATE_ERR_STATS(vdcp, vd_transerrs);
if (!(flags & VDC_OP_RESUBMIT)) {
VD_KSTAT_WAITQ_EXIT(vdcp);
DTRACE_IO1(done, buf_t *, bufp);
}
}
}
mutex_exit(&vdcp->lock);
return (rv);
}
static int
vdc_populate_descriptor(vdc_t *vdcp, int operation, caddr_t addr,
size_t nbytes, int slice, diskaddr_t offset,
buf_t *bufp, vio_desc_direction_t dir, int flags)
{
vdc_local_desc_t *local_dep = NULL;
int idx;
int next_idx;
vio_dring_msg_t dmsg;
size_t msglen;
int rv;
ASSERT(MUTEX_HELD(&vdcp->lock));
vdcp->threads_pending++;
loop:
DMSG(vdcp, 2, ": dring_curr_idx = %d\n", vdcp->dring_curr_idx);
if (flags & VDC_OP_DRING_RESERVED) {
idx = VDC_DRING_FIRST_RESV;
local_dep = &(vdcp->local_dring[idx]);
} else {
idx = vdcp->dring_curr_idx;
local_dep = &(vdcp->local_dring[idx]);
if (!local_dep->is_free) {
DMSG(vdcp, 2, "[%d]: dring full - waiting for space\n",
vdcp->instance);
cv_wait(&vdcp->dring_free_cv, &vdcp->lock);
if (vdcp->state == VDC_STATE_RUNNING ||
vdcp->state == VDC_STATE_HANDLE_PENDING) {
goto loop;
}
vdcp->threads_pending--;
return (ECONNRESET);
}
next_idx = idx + 1;
if (next_idx >= vdcp->dring_len)
next_idx = VDC_DRING_FIRST_ENTRY;
vdcp->dring_curr_idx = next_idx;
}
ASSERT(local_dep->is_free);
local_dep->operation = operation;
local_dep->addr = addr;
local_dep->nbytes = nbytes;
local_dep->slice = slice;
local_dep->offset = offset;
local_dep->buf = bufp;
local_dep->dir = dir;
local_dep->flags = flags;
local_dep->is_free = B_FALSE;
rv = vdc_map_to_shared_dring(vdcp, idx);
if (rv) {
if (flags & VDC_OP_DRING_RESERVED) {
DMSG(vdcp, 0, "[%d]: cannot bind memory - error\n",
vdcp->instance);
local_dep->is_free = B_TRUE;
vdcp->threads_pending--;
return (rv);
}
DMSG(vdcp, 0, "[%d]: cannot bind memory - waiting ..\n",
vdcp->instance);
local_dep->is_free = B_TRUE;
vdcp->dring_curr_idx = idx;
cv_wait(&vdcp->membind_cv, &vdcp->lock);
if (vdcp->state == VDC_STATE_RUNNING ||
vdcp->state == VDC_STATE_HANDLE_PENDING) {
goto loop;
}
vdcp->threads_pending--;
return (ECONNRESET);
}
VIO_INIT_DRING_DATA_TAG(dmsg);
VDC_INIT_DRING_DATA_MSG_IDS(dmsg, vdcp);
dmsg.dring_ident = vdcp->dring_ident;
dmsg.start_idx = idx;
dmsg.end_idx = idx;
vdcp->seq_num++;
DTRACE_PROBE2(populate, int, vdcp->instance,
vdc_local_desc_t *, local_dep);
DMSG(vdcp, 2, "ident=0x%lx, st=%u, end=%u, seq=%ld\n",
vdcp->dring_ident, dmsg.start_idx, dmsg.end_idx, dmsg.seq_num);
msglen = sizeof (dmsg);
rv = vdc_send(vdcp, (caddr_t)&dmsg, &msglen);
switch (rv) {
case ECONNRESET:
rv = 0;
break;
case 0:
DMSG(vdcp, 1, "sent via LDC: rv=%d\n", rv);
break;
default:
DMSG(vdcp, 0, "unexpected error, rv=%d\n", rv);
rv = ENXIO;
break;
}
vdcp->threads_pending--;
return (rv);
}
static int
vdc_do_op(vdc_t *vdc, int op, caddr_t addr, size_t nbytes, int slice,
diskaddr_t offset, struct buf *bufp, vio_desc_direction_t dir, int flags)
{
vio_msg_t vio_msg;
struct buf buf;
int rv;
if (bufp == NULL) {
bioinit(&buf);
buf.b_bcount = nbytes;
buf.b_flags = B_BUSY;
bufp = &buf;
}
rv = vdc_send_request(vdc, op, addr, nbytes, slice, offset, bufp,
dir, flags);
if (rv != 0)
goto done;
if (flags & VDC_OP_STATE_RUNNING) {
if (ddi_in_panic()) {
rv = vdc_drain_response(vdc, bufp);
goto done;
}
} else {
rv = vdc_wait_for_response(vdc, &vio_msg);
if (rv == 0)
rv = vdc_process_data_msg(vdc, &vio_msg);
if (rv) {
mutex_enter(&vdc->lock);
if (op == VD_OP_BREAD || op == VD_OP_BWRITE) {
if (flags & VDC_OP_RESUBMIT) {
VD_KSTAT_RUNQ_BACK_TO_WAITQ(vdc);
} else {
VD_KSTAT_RUNQ_EXIT(vdc);
DTRACE_IO1(done, buf_t *, bufp);
}
}
mutex_exit(&vdc->lock);
goto done;
}
}
if (bufp == &buf)
rv = biowait(bufp);
done:
if (bufp == &buf) {
biofini(bufp);
} else if (rv != 0) {
bioerror(bufp, EIO);
biodone(bufp);
}
return (rv);
}
static int
vdc_do_sync_op(vdc_t *vdcp, int operation, caddr_t addr, size_t nbytes,
int slice, diskaddr_t offset, vio_desc_direction_t dir, boolean_t rconflict)
{
int status;
int flags = VDC_OP_NORMAL;
mutex_enter(&vdcp->lock);
vdcp->sync_op_cnt++;
while (vdcp->sync_op_blocked && vdcp->state != VDC_STATE_DETACH) {
if (ddi_in_panic()) {
vdcp->sync_op_cnt--;
mutex_exit(&vdcp->lock);
return (EIO);
} else {
cv_wait(&vdcp->sync_blocked_cv, &vdcp->lock);
}
}
if (vdcp->state == VDC_STATE_DETACH) {
cv_broadcast(&vdcp->sync_blocked_cv);
vdcp->sync_op_cnt--;
mutex_exit(&vdcp->lock);
return (ENXIO);
}
vdcp->sync_op_blocked = B_TRUE;
mutex_exit(&vdcp->lock);
if (!rconflict)
flags &= ~VDC_OP_ERRCHK_CONFLICT;
status = vdc_do_op(vdcp, operation, addr, nbytes, slice, offset,
NULL, dir, flags);
mutex_enter(&vdcp->lock);
DMSG(vdcp, 2, ": operation returned %d\n", status);
if (vdcp->state == VDC_STATE_DETACH) {
status = ENXIO;
}
vdcp->sync_op_blocked = B_FALSE;
vdcp->sync_op_cnt--;
cv_signal(&vdcp->sync_blocked_cv);
mutex_exit(&vdcp->lock);
return (status);
}
static int
vdc_drain_response(vdc_t *vdc, struct buf *buf)
{
int rv, idx, retries;
size_t msglen;
vdc_local_desc_t *ldep = NULL;
vio_dring_msg_t dmsg;
struct buf *mbuf;
boolean_t ack;
mutex_enter(&vdc->lock);
retries = 0;
for (;;) {
msglen = sizeof (dmsg);
rv = ldc_read(vdc->curr_server->ldc_handle, (caddr_t)&dmsg,
&msglen);
if (rv) {
rv = EINVAL;
break;
}
if ((rv == 0) && (msglen == 0)) {
if (retries++ > vdc_dump_retries) {
rv = EAGAIN;
break;
}
drv_usecwait(vdc_usec_timeout_dump);
continue;
}
if ((dmsg.tag.vio_msgtype != VIO_TYPE_DATA) ||
(dmsg.tag.vio_subtype_env != VIO_DRING_DATA)) {
DMSG(vdc, 0, "discard pkt: type=%d sub=%d env=%d\n",
dmsg.tag.vio_msgtype,
dmsg.tag.vio_subtype,
dmsg.tag.vio_subtype_env);
continue;
}
switch (dmsg.tag.vio_subtype) {
case VIO_SUBTYPE_ACK:
ack = B_TRUE;
break;
case VIO_SUBTYPE_NACK:
ack = B_FALSE;
break;
default:
continue;
}
idx = dmsg.start_idx;
if (idx >= vdc->dring_len) {
DMSG(vdc, 0, "[%d] Bogus ack data : start %d\n",
vdc->instance, idx);
continue;
}
ldep = &vdc->local_dring[idx];
if (ldep->dep->hdr.dstate != VIO_DESC_DONE) {
DMSG(vdc, 0, "[%d] Entry @ %d - state !DONE %d\n",
vdc->instance, idx, ldep->dep->hdr.dstate);
continue;
}
mbuf = ldep->buf;
ASSERT(mbuf != NULL);
mbuf->b_resid = mbuf->b_bcount - ldep->dep->payload.nbytes;
bioerror(mbuf, ack ? ldep->dep->payload.status : EIO);
biodone(mbuf);
rv = vdc_depopulate_descriptor(vdc, idx);
if (buf != NULL && buf == mbuf) {
rv = 0;
goto done;
}
if ((idx + 1) % vdc->dring_len == vdc->dring_curr_idx) {
rv = (buf != NULL)? ESRCH: 0;
break;
}
}
done:
mutex_exit(&vdc->lock);
DMSG(vdc, 0, "End idx=%d\n", idx);
return (rv);
}
static int
vdc_depopulate_descriptor(vdc_t *vdc, uint_t idx)
{
vd_dring_entry_t *dep = NULL;
vdc_local_desc_t *ldep = NULL;
int status = ENXIO;
int rv = 0;
ASSERT(vdc != NULL);
ASSERT(idx < vdc->dring_len);
ldep = &vdc->local_dring[idx];
ASSERT(ldep != NULL);
ASSERT(MUTEX_HELD(&vdc->lock));
DTRACE_PROBE2(depopulate, int, vdc->instance, vdc_local_desc_t *, ldep);
DMSG(vdc, 2, ": idx = %d\n", idx);
dep = ldep->dep;
ASSERT(dep != NULL);
ASSERT((dep->hdr.dstate == VIO_DESC_DONE) ||
(dep->payload.status == ECANCELED));
VDC_MARK_DRING_ENTRY_FREE(vdc, idx);
ldep->is_free = B_TRUE;
status = dep->payload.status;
DMSG(vdc, 2, ": is_free = %d : status = %d\n", ldep->is_free, status);
if (ldep->nbytes == 0) {
cv_signal(&vdc->dring_free_cv);
return (status);
}
if (ldep->align_addr) {
ASSERT(ldep->addr != NULL);
if (dep->payload.nbytes > 0)
bcopy(ldep->align_addr, ldep->addr,
dep->payload.nbytes);
kmem_free(ldep->align_addr,
sizeof (caddr_t) * P2ROUNDUP(ldep->nbytes, 8));
ldep->align_addr = NULL;
}
rv = ldc_mem_unbind_handle(ldep->desc_mhdl);
if (rv != 0) {
DMSG(vdc, 0, "?[%d] unbind mhdl 0x%lx @ idx %d failed (%d)",
vdc->instance, ldep->desc_mhdl, idx, rv);
if (status == 0)
status = EINVAL;
}
cv_signal(&vdc->membind_cv);
cv_signal(&vdc->dring_free_cv);
return (status);
}
static int
vdc_populate_mem_hdl(vdc_t *vdcp, vdc_local_desc_t *ldep)
{
vd_dring_entry_t *dep = NULL;
ldc_mem_handle_t mhdl;
caddr_t vaddr;
size_t nbytes;
uint8_t perm = LDC_MEM_RW;
uint8_t maptype;
int rv = 0;
int i;
ASSERT(vdcp != NULL);
dep = ldep->dep;
mhdl = ldep->desc_mhdl;
switch (ldep->dir) {
case VIO_read_dir:
perm = LDC_MEM_W;
break;
case VIO_write_dir:
perm = LDC_MEM_R;
break;
case VIO_both_dir:
perm = LDC_MEM_RW;
break;
default:
ASSERT(0);
}
vaddr = ldep->addr;
nbytes = ldep->nbytes;
if (((uint64_t)vaddr & 0x7) != 0) {
ASSERT(ldep->align_addr == NULL);
ldep->align_addr =
kmem_alloc(sizeof (caddr_t) *
P2ROUNDUP(nbytes, 8), KM_SLEEP);
DMSG(vdcp, 0, "[%d] Misaligned address %p reallocating "
"(buf=%p nb=%ld op=%d)\n",
vdcp->instance, (void *)vaddr, (void *)ldep->align_addr,
nbytes, ldep->operation);
if (perm != LDC_MEM_W)
bcopy(vaddr, ldep->align_addr, nbytes);
vaddr = ldep->align_addr;
}
maptype = LDC_IO_MAP|LDC_SHADOW_MAP;
rv = ldc_mem_bind_handle(mhdl, vaddr, P2ROUNDUP(nbytes, 8),
maptype, perm, &dep->payload.cookie[0], &dep->payload.ncookies);
DMSG(vdcp, 2, "[%d] bound mem handle; ncookies=%d\n",
vdcp->instance, dep->payload.ncookies);
if (rv != 0) {
DMSG(vdcp, 0, "[%d] Failed to bind LDC memory handle "
"(mhdl=%p, buf=%p, err=%d)\n",
vdcp->instance, (void *)mhdl, (void *)vaddr, rv);
if (ldep->align_addr) {
kmem_free(ldep->align_addr,
sizeof (caddr_t) * P2ROUNDUP(nbytes, 8));
ldep->align_addr = NULL;
}
return (EAGAIN);
}
for (i = 1; i < dep->payload.ncookies; i++) {
rv = ldc_mem_nextcookie(mhdl, &dep->payload.cookie[i]);
if (rv != 0) {
(void) ldc_mem_unbind_handle(mhdl);
DMSG(vdcp, 0, "?[%d] Failed to get next cookie "
"(mhdl=%lx cnum=%d), err=%d",
vdcp->instance, mhdl, i, rv);
if (ldep->align_addr) {
kmem_free(ldep->align_addr,
sizeof (caddr_t) * ldep->nbytes);
ldep->align_addr = NULL;
}
return (EAGAIN);
}
}
return (rv);
}
static uint_t
vdc_handle_cb(uint64_t event, caddr_t arg)
{
ldc_status_t ldc_state;
int rv = 0;
vdc_server_t *srvr = (vdc_server_t *)(void *)arg;
vdc_t *vdc = srvr->vdcp;
ASSERT(vdc != NULL);
DMSG(vdc, 1, "evt=%lx seqID=%ld\n", event, vdc->seq_num);
mutex_enter(&vdc->lock);
if (vdc->curr_server != srvr) {
DMSG(vdc, 0, "[%d] Ignoring event 0x%lx for port@%ld\n",
vdc->instance, event, srvr->id);
mutex_exit(&vdc->lock);
return (LDC_SUCCESS);
}
if (event & LDC_EVT_UP) {
DMSG(vdc, 0, "[%d] Received LDC_EVT_UP\n", vdc->instance);
rv = ldc_status(srvr->ldc_handle, &ldc_state);
if (rv != 0) {
DMSG(vdc, 0, "[%d] Couldn't get LDC status %d",
vdc->instance, rv);
mutex_exit(&vdc->lock);
return (LDC_SUCCESS);
}
if (srvr->ldc_state != LDC_UP &&
ldc_state == LDC_UP) {
vdc->seq_num = 1;
vdc->seq_num_reply = 0;
vdc->io_pending = B_TRUE;
srvr->ldc_state = ldc_state;
cv_signal(&vdc->initwait_cv);
cv_signal(&vdc->io_pending_cv);
}
}
if (event & LDC_EVT_READ) {
DMSG(vdc, 1, "[%d] Received LDC_EVT_READ\n", vdc->instance);
mutex_enter(&vdc->read_lock);
cv_signal(&vdc->read_cv);
vdc->read_state = VDC_READ_PENDING;
mutex_exit(&vdc->read_lock);
mutex_exit(&vdc->lock);
return (LDC_SUCCESS);
}
if (event & (LDC_EVT_RESET|LDC_EVT_DOWN)) {
DMSG(vdc, 0, "[%d] Received LDC RESET event\n", vdc->instance);
mutex_enter(&vdc->read_lock);
if ((vdc->read_state == VDC_READ_WAITING) ||
(vdc->read_state == VDC_READ_RESET))
cv_signal(&vdc->read_cv);
vdc->read_state = VDC_READ_RESET;
mutex_exit(&vdc->read_lock);
if (vdc->state == VDC_STATE_INIT_WAITING) {
vdc->state = VDC_STATE_RESETTING;
cv_signal(&vdc->initwait_cv);
} else if (vdc->state == VDC_STATE_FAILED) {
vdc->io_pending = B_TRUE;
cv_signal(&vdc->io_pending_cv);
}
}
mutex_exit(&vdc->lock);
if (event & ~(LDC_EVT_UP | LDC_EVT_RESET | LDC_EVT_DOWN | LDC_EVT_READ))
DMSG(vdc, 0, "![%d] Unexpected LDC event (%lx) received",
vdc->instance, event);
return (LDC_SUCCESS);
}
static int
vdc_wait_for_response(vdc_t *vdcp, vio_msg_t *msgp)
{
size_t nbytes = sizeof (*msgp);
int status;
ASSERT(vdcp != NULL);
DMSG(vdcp, 1, "[%d] Entered\n", vdcp->instance);
status = vdc_recv(vdcp, msgp, &nbytes);
DMSG(vdcp, 3, "vdc_read() done.. status=0x%x size=0x%x\n",
status, (int)nbytes);
if (status) {
DMSG(vdcp, 0, "?[%d] Error %d reading LDC msg\n",
vdcp->instance, status);
return (status);
}
if (nbytes < sizeof (vio_msg_tag_t)) {
DMSG(vdcp, 0, "?[%d] Expect %lu bytes; recv'd %lu\n",
vdcp->instance, sizeof (vio_msg_tag_t), nbytes);
return (ENOMSG);
}
DMSG(vdcp, 2, "[%d] (%x/%x/%x)\n", vdcp->instance,
msgp->tag.vio_msgtype,
msgp->tag.vio_subtype,
msgp->tag.vio_subtype_env);
if ((msgp->tag.vio_sid != vdcp->session_id) &&
(msgp->tag.vio_subtype_env != VIO_VER_INFO)) {
DMSG(vdcp, 0, "[%d] Invalid SID: received 0x%x, "
"expected 0x%lx [seq num %lx @ %d]",
vdcp->instance, msgp->tag.vio_sid,
vdcp->session_id,
((vio_dring_msg_t *)msgp)->seq_num,
((vio_dring_msg_t *)msgp)->start_idx);
return (ENOMSG);
}
return (0);
}
static int
vdc_resubmit_backup_dring(vdc_t *vdcp)
{
int processed = 0;
int count;
int b_idx;
int rv = 0;
int dring_size;
vdc_local_desc_t *curr_ldep;
ASSERT(MUTEX_NOT_HELD(&vdcp->lock));
ASSERT(vdcp->state == VDC_STATE_HANDLE_PENDING);
if (vdcp->local_dring_backup == NULL) {
return (0);
}
DMSG(vdcp, 1, "restoring pending dring entries (len=%d, tail=%d)\n",
vdcp->local_dring_backup_len, vdcp->local_dring_backup_tail);
b_idx = vdcp->local_dring_backup_tail;
for (count = 0; count < vdcp->local_dring_backup_len; count++) {
curr_ldep = &(vdcp->local_dring_backup[b_idx]);
if (!curr_ldep->is_free) {
DMSG(vdcp, 1, "resubmitting entry idx=%x\n", b_idx);
rv = vdc_do_op(vdcp, curr_ldep->operation,
curr_ldep->addr, curr_ldep->nbytes,
curr_ldep->slice, curr_ldep->offset,
curr_ldep->buf, curr_ldep->dir,
(curr_ldep->flags & ~VDC_OP_STATE_RUNNING) |
VDC_OP_RESUBMIT);
if (rv) {
DMSG(vdcp, 1, "[%d] resubmit entry %d failed\n",
vdcp->instance, b_idx);
goto done;
}
curr_ldep->is_free = B_TRUE;
processed++;
}
if (++b_idx >= vdcp->local_dring_backup_len)
b_idx = 0;
}
dring_size = vdcp->local_dring_backup_len *
sizeof (vdcp->local_dring_backup[0]);
(void) kmem_free(vdcp->local_dring_backup, dring_size);
vdcp->local_dring_backup = NULL;
done:
DTRACE_PROBE2(processed, int, processed, vdc_t *, vdcp);
return (rv);
}
void
vdc_cancel_backup_dring(vdc_t *vdcp)
{
vdc_local_desc_t *ldep;
struct buf *bufp;
int count;
int b_idx;
int dring_size;
int cancelled = 0;
ASSERT(MUTEX_HELD(&vdcp->lock));
ASSERT(vdcp->state == VDC_STATE_FAILED);
if (vdcp->local_dring_backup == NULL) {
return;
}
DMSG(vdcp, 1, "cancelling pending dring entries (len=%d, tail=%d)\n",
vdcp->local_dring_backup_len, vdcp->local_dring_backup_tail);
b_idx = vdcp->local_dring_backup_tail;
for (count = 0; count < vdcp->local_dring_backup_len; count++) {
ldep = &(vdcp->local_dring_backup[b_idx]);
if (!ldep->is_free) {
DMSG(vdcp, 1, "cancelling entry idx=%x\n", b_idx);
cancelled++;
bufp = ldep->buf;
ASSERT(bufp != NULL);
bufp->b_resid = bufp->b_bcount;
if (ldep->operation == VD_OP_BREAD ||
ldep->operation == VD_OP_BWRITE) {
VD_UPDATE_ERR_STATS(vdcp, vd_softerrs);
VD_KSTAT_WAITQ_EXIT(vdcp);
DTRACE_IO1(done, buf_t *, bufp);
}
bioerror(bufp, EIO);
biodone(bufp);
}
if (++b_idx >= vdcp->local_dring_backup_len)
b_idx = 0;
}
dring_size = vdcp->local_dring_backup_len *
sizeof (vdcp->local_dring_backup[0]);
(void) kmem_free(vdcp->local_dring_backup, dring_size);
vdcp->local_dring_backup = NULL;
DTRACE_PROBE2(cancelled, int, cancelled, vdc_t *, vdcp);
}
void
vdc_connection_timeout(void *arg)
{
vdc_t *vdcp = (vdc_t *)arg;
mutex_enter(&vdcp->lock);
vdcp->ctimeout_reached = B_TRUE;
mutex_exit(&vdcp->lock);
}
static void
vdc_backup_local_dring(vdc_t *vdcp)
{
int b_idx, count, dring_size;
vdc_local_desc_t *curr_ldep;
ASSERT(MUTEX_HELD(&vdcp->lock));
ASSERT(vdcp->state == VDC_STATE_RESETTING);
if (vdcp->local_dring_backup != NULL) {
DMSG(vdcp, 1, "reusing local descriptor ring backup "
"(len=%d, tail=%d)\n", vdcp->local_dring_backup_len,
vdcp->local_dring_backup_tail);
return;
}
if (!(vdcp->initialized & VDC_DRING_LOCAL))
return;
DMSG(vdcp, 1, "backing up the local descriptor ring (len=%d, "
"tail=%d)\n", vdcp->dring_len, vdcp->dring_curr_idx);
dring_size = vdcp->dring_len * sizeof (vdcp->local_dring[0]);
vdcp->local_dring_backup = kmem_alloc(dring_size, KM_SLEEP);
bcopy(vdcp->local_dring, vdcp->local_dring_backup, dring_size);
vdcp->local_dring_backup_tail = vdcp->dring_curr_idx;
vdcp->local_dring_backup_len = vdcp->dring_len;
b_idx = vdcp->local_dring_backup_tail;
for (count = 0; count < vdcp->local_dring_backup_len; count++) {
curr_ldep = &(vdcp->local_dring_backup[b_idx]);
if (!curr_ldep->is_free &&
(curr_ldep->operation == VD_OP_BREAD ||
curr_ldep->operation == VD_OP_BWRITE)) {
VD_KSTAT_RUNQ_BACK_TO_WAITQ(vdcp);
}
if (++b_idx >= vdcp->local_dring_backup_len)
b_idx = 0;
}
}
static void
vdc_switch_server(vdc_t *vdcp)
{
int rv;
vdc_server_t *curr_server, *new_server;
ASSERT(MUTEX_HELD(&vdcp->lock));
if (vdcp->num_servers == 1) {
return;
}
curr_server = vdcp->curr_server;
new_server =
(curr_server->next) ? curr_server->next : vdcp->server_list;
ASSERT(curr_server != new_server);
rv = ldc_down(curr_server->ldc_handle);
if (rv) {
DMSG(vdcp, 0, "[%d] Cannot bring channel down, port %ld\n",
vdcp->instance, curr_server->id);
return;
}
vdcp->curr_server = new_server;
DMSG(vdcp, 0, "[%d] Switched to next vdisk server, port@%ld, ldc@%ld\n",
vdcp->instance, vdcp->curr_server->id, vdcp->curr_server->ldc_id);
}
static void
vdc_print_svc_status(vdc_t *vdcp)
{
int instance;
uint64_t ldc_id, port_id;
vdc_service_state_t svc_state;
ASSERT(mutex_owned(&vdcp->lock));
svc_state = vdcp->curr_server->svc_state;
if (vdcp->curr_server->log_state == svc_state)
return;
instance = vdcp->instance;
ldc_id = vdcp->curr_server->ldc_id;
port_id = vdcp->curr_server->id;
switch (svc_state) {
case VDC_SERVICE_OFFLINE:
cmn_err(CE_CONT, "?vdisk@%d is offline\n", instance);
break;
case VDC_SERVICE_CONNECTED:
cmn_err(CE_CONT, "?vdisk@%d is connected using ldc@%ld,%ld\n",
instance, ldc_id, port_id);
break;
case VDC_SERVICE_ONLINE:
cmn_err(CE_CONT, "?vdisk@%d is online using ldc@%ld,%ld\n",
instance, ldc_id, port_id);
break;
case VDC_SERVICE_FAILED:
cmn_err(CE_CONT, "?vdisk@%d access to service failed "
"using ldc@%ld,%ld\n", instance, ldc_id, port_id);
break;
case VDC_SERVICE_FAULTED:
cmn_err(CE_CONT, "?vdisk@%d access to backend failed "
"using ldc@%ld,%ld\n", instance, ldc_id, port_id);
break;
default:
ASSERT(0);
break;
}
vdcp->curr_server->log_state = svc_state;
}
static boolean_t
vdc_handshake_retry(vdc_t *vdcp, int hshake_cnt, int hattr_cnt)
{
int hattr_total = 0;
vdc_server_t *srvr;
ASSERT(vdcp->lifecycle != VDC_LC_DETACHING);
vdcp->curr_server->hshake_cnt = hshake_cnt;
vdcp->curr_server->hattr_cnt = hattr_cnt;
if (hattr_cnt == 0)
vdcp->curr_server->hattr_total = 0;
else
vdcp->curr_server->hattr_total += hattr_cnt;
if (vdcp->lifecycle == VDC_LC_ONLINE)
return (B_TRUE);
if (vdcp->lifecycle == VDC_LC_ATTACHING) {
for (srvr = vdcp->server_list; srvr != NULL;
srvr = srvr->next) {
if (srvr->hshake_cnt == 0) {
return (B_TRUE);
}
}
return (B_FALSE);
}
ASSERT(vdcp->lifecycle == VDC_LC_ONLINE_PENDING);
for (srvr = vdcp->server_list; srvr != NULL; srvr = srvr->next) {
if (srvr->hattr_cnt == 0) {
return (B_TRUE);
}
hattr_total += srvr->hattr_total;
}
return (hattr_total < vdcp->hattr_min);
}
static void
vdc_process_msg_thread(vdc_t *vdcp)
{
boolean_t failure_msg = B_FALSE;
int status;
int ctimeout;
timeout_id_t tmid = 0;
clock_t ldcup_timeout = 0;
vdc_server_t *srvr;
vdc_service_state_t svc_state;
int hshake_cnt = 0;
int hattr_cnt = 0;
mutex_enter(&vdcp->lock);
ASSERT(vdcp->lifecycle == VDC_LC_ATTACHING);
for (;;) {
#define Q(_s) (vdcp->state == _s) ? #_s :
DMSG(vdcp, 3, "state = %d (%s)\n", vdcp->state,
Q(VDC_STATE_INIT)
Q(VDC_STATE_INIT_WAITING)
Q(VDC_STATE_NEGOTIATE)
Q(VDC_STATE_HANDLE_PENDING)
Q(VDC_STATE_FAULTED)
Q(VDC_STATE_FAILED)
Q(VDC_STATE_RUNNING)
Q(VDC_STATE_RESETTING)
Q(VDC_STATE_DETACH)
"UNKNOWN");
#undef Q
switch (vdcp->state) {
case VDC_STATE_INIT:
ctimeout = (vdc_timeout != 0)?
vdc_timeout : vdcp->curr_server->ctimeout;
if (ctimeout != 0 && tmid == 0) {
tmid = timeout(vdc_connection_timeout, vdcp,
ctimeout * drv_usectohz(MICROSEC));
}
if (vdcp->lifecycle == VDC_LC_DETACHING) {
vdcp->state = VDC_STATE_DETACH;
break;
}
if (vdcp->ctimeout_reached) {
ASSERT(tmid != 0);
tmid = 0;
vdcp->state = VDC_STATE_FAILED;
break;
}
if (hshake_cnt >= vdc_hshake_retries || hattr_cnt > 0) {
if (!vdc_handshake_retry(vdcp, hshake_cnt,
hattr_cnt)) {
DMSG(vdcp, 0, "[%d] too many "
"handshakes", vdcp->instance);
vdcp->state = VDC_STATE_FAILED;
break;
}
vdc_switch_server(vdcp);
hshake_cnt = 0;
hattr_cnt = 0;
}
hshake_cnt++;
status = vdc_start_ldc_connection(vdcp);
if (status != EINVAL) {
vdcp->state = VDC_STATE_INIT_WAITING;
} else {
vdcp->curr_server->svc_state =
VDC_SERVICE_FAILED;
vdc_print_svc_status(vdcp);
}
break;
case VDC_STATE_INIT_WAITING:
if (vdcp->curr_server->ldc_state == LDC_UP) {
vdcp->state = VDC_STATE_NEGOTIATE;
break;
}
ldcup_timeout = ddi_get_lbolt() + (vdc_ldcup_timeout *
drv_usectohz(MICROSEC));
status = cv_timedwait(&vdcp->initwait_cv, &vdcp->lock,
ldcup_timeout);
if (status == -1 &&
vdcp->state == VDC_STATE_INIT_WAITING &&
vdcp->curr_server->ldc_state != LDC_UP) {
vdcp->curr_server->svc_state =
VDC_SERVICE_FAILED;
vdc_print_svc_status(vdcp);
vdcp->state = VDC_STATE_INIT;
break;
}
if (vdcp->state != VDC_STATE_INIT_WAITING) {
DMSG(vdcp, 0,
"state moved to %d out from under us...\n",
vdcp->state);
}
break;
case VDC_STATE_NEGOTIATE:
switch (status = vdc_ver_negotiation(vdcp)) {
case 0:
break;
default:
DMSG(vdcp, 0, "ver negotiate failed (%d)..\n",
status);
goto reset;
}
hattr_cnt++;
switch (status = vdc_attr_negotiation(vdcp)) {
case 0:
break;
default:
DMSG(vdcp, 0, "attr negotiate failed (%d)..\n",
status);
goto reset;
}
switch (status = vdc_dring_negotiation(vdcp)) {
case 0:
break;
default:
DMSG(vdcp, 0, "dring negotiate failed (%d)..\n",
status);
goto reset;
}
switch (status = vdc_rdx_exchange(vdcp)) {
case 0:
vdcp->state = VDC_STATE_HANDLE_PENDING;
goto done;
default:
DMSG(vdcp, 0, "RDX xchg failed ..(%d)\n",
status);
goto reset;
}
reset:
DMSG(vdcp, 0, "negotiation failed: resetting (%d)\n",
status);
vdcp->state = VDC_STATE_RESETTING;
vdcp->self_reset = B_TRUE;
vdcp->curr_server->svc_state = VDC_SERVICE_FAILED;
vdc_print_svc_status(vdcp);
done:
DMSG(vdcp, 0, "negotiation complete (state=0x%x)...\n",
vdcp->state);
break;
case VDC_STATE_HANDLE_PENDING:
DMSG(vdcp, 0, "[%d] connection to service domain is up",
vdcp->instance);
vdcp->curr_server->svc_state = VDC_SERVICE_CONNECTED;
mutex_exit(&vdcp->lock);
if (vdcp->num_servers > 1 &&
vdc_eio_check(vdcp, 0) != 0) {
mutex_enter(&vdcp->lock);
vdcp->curr_server->svc_state =
VDC_SERVICE_FAULTED;
vdcp->state = VDC_STATE_FAULTED;
break;
}
if (tmid != 0) {
(void) untimeout(tmid);
tmid = 0;
vdcp->ctimeout_reached = B_FALSE;
}
(void) vdc_setup_devid(vdcp);
status = vdc_resubmit_backup_dring(vdcp);
mutex_enter(&vdcp->lock);
if (status) {
vdcp->state = VDC_STATE_RESETTING;
vdcp->self_reset = B_TRUE;
vdcp->curr_server->svc_state =
VDC_SERVICE_FAILED;
vdc_print_svc_status(vdcp);
} else {
vdcp->state = VDC_STATE_RUNNING;
}
break;
case VDC_STATE_FAULTED:
vdc_print_svc_status(vdcp);
for (srvr = vdcp->server_list; srvr != NULL;
srvr = srvr->next) {
svc_state = srvr->svc_state;
if (svc_state != VDC_SERVICE_FAULTED)
break;
}
if (srvr != NULL) {
vdcp->state = VDC_STATE_RESETTING;
vdcp->self_reset = B_TRUE;
} else {
vdcp->state = VDC_STATE_FAILED;
}
break;
case VDC_STATE_FAILED:
cmn_err(CE_NOTE, "vdisk@%d disk access failed",
vdcp->instance);
failure_msg = B_TRUE;
if (vdcp->lifecycle == VDC_LC_ATTACHING) {
vdcp->lifecycle = VDC_LC_ONLINE_PENDING;
vdcp->hattr_min = vdc_hattr_min_initial;
} else {
vdcp->hattr_min = vdc_hattr_min;
}
if (tmid != 0) {
(void) untimeout(tmid);
tmid = 0;
}
cv_broadcast(&vdcp->running_cv);
vdc_cancel_backup_dring(vdcp);
while (!vdcp->io_pending)
cv_wait(&vdcp->io_pending_cv, &vdcp->lock);
for (srvr = vdcp->server_list; srvr != NULL;
srvr = srvr->next) {
srvr->svc_state = VDC_SERVICE_OFFLINE;
srvr->hshake_cnt = 0;
srvr->hattr_cnt = 0;
srvr->hattr_total = 0;
}
hshake_cnt = 0;
hattr_cnt = 0;
vdcp->ctimeout_reached = B_FALSE;
vdcp->state = VDC_STATE_RESETTING;
vdcp->self_reset = B_TRUE;
break;
case VDC_STATE_RUNNING:
if (vdcp->lifecycle == VDC_LC_DETACHING) {
vdcp->state = VDC_STATE_DETACH;
break;
}
vdcp->lifecycle = VDC_LC_ONLINE;
if (failure_msg) {
cmn_err(CE_NOTE, "vdisk@%d disk access "
"recovered", vdcp->instance);
failure_msg = B_FALSE;
}
cv_broadcast(&vdcp->running_cv);
if (vdcp->failfast_interval != 0 ||
vdcp->num_servers > 1)
cv_signal(&vdcp->eio_cv);
if (vdcp->ownership & VDC_OWNERSHIP_WANTED)
vdcp->ownership |= VDC_OWNERSHIP_RESET;
cv_signal(&vdcp->ownership_cv);
vdcp->curr_server->svc_state = VDC_SERVICE_ONLINE;
vdc_print_svc_status(vdcp);
mutex_exit(&vdcp->lock);
for (;;) {
vio_msg_t msg;
status = vdc_wait_for_response(vdcp, &msg);
if (status) break;
DMSG(vdcp, 1, "[%d] new pkt(s) available\n",
vdcp->instance);
status = vdc_process_data_msg(vdcp, &msg);
if (status) {
DMSG(vdcp, 1, "[%d] process_data_msg "
"returned err=%d\n", vdcp->instance,
status);
break;
}
}
mutex_enter(&vdcp->lock);
for (srvr = vdcp->server_list; srvr != NULL;
srvr = srvr->next) {
srvr->svc_state = VDC_SERVICE_OFFLINE;
srvr->log_state = VDC_SERVICE_NONE;
srvr->hshake_cnt = 0;
srvr->hattr_cnt = 0;
srvr->hattr_total = 0;
}
hshake_cnt = 0;
hattr_cnt = 0;
vdc_print_svc_status(vdcp);
vdcp->state = VDC_STATE_RESETTING;
vdcp->self_reset = B_TRUE;
break;
case VDC_STATE_RESETTING:
DMSG(vdcp, 0, "Initiating channel reset "
"(pending = %d)\n", (int)vdcp->threads_pending);
if (vdcp->self_reset) {
DMSG(vdcp, 0,
"[%d] calling stop_ldc_connection.\n",
vdcp->instance);
status = vdc_stop_ldc_connection(vdcp);
vdcp->self_reset = B_FALSE;
}
while (vdcp->threads_pending) {
cv_broadcast(&vdcp->membind_cv);
cv_broadcast(&vdcp->dring_free_cv);
mutex_exit(&vdcp->lock);
delay(vdc_hz_min_ldc_delay);
mutex_enter(&vdcp->lock);
}
ASSERT(vdcp->threads_pending == 0);
ASSERT(vdcp->read_state != VDC_READ_WAITING);
vdcp->read_state = VDC_READ_IDLE;
vdcp->io_pending = B_FALSE;
vdc_eio_unqueue(vdcp, 0, B_FALSE);
vdc_backup_local_dring(vdcp);
vdc_destroy_descriptor_ring(vdcp);
vdcp->state = VDC_STATE_INIT;
break;
case VDC_STATE_DETACH:
DMSG(vdcp, 0, "[%d] Reset thread exit cleanup ..\n",
vdcp->instance);
mutex_exit(&vdcp->lock);
if (tmid != 0) {
(void) untimeout(tmid);
tmid = 0;
}
mutex_enter(&vdcp->lock);
cv_broadcast(&vdcp->running_cv);
while (vdcp->sync_op_cnt > 0) {
cv_broadcast(&vdcp->sync_blocked_cv);
mutex_exit(&vdcp->lock);
delay(vdc_hz_min_ldc_delay);
mutex_enter(&vdcp->lock);
}
mutex_exit(&vdcp->lock);
DMSG(vdcp, 0, "[%d] Msg processing thread exiting ..\n",
vdcp->instance);
thread_exit();
break;
}
}
}
static int
vdc_process_data_msg(vdc_t *vdcp, vio_msg_t *msg)
{
int status = 0;
vio_dring_msg_t *dring_msg;
vdc_local_desc_t *ldep = NULL;
int start, end;
int idx;
int op;
dring_msg = (vio_dring_msg_t *)msg;
ASSERT(msg->tag.vio_msgtype == VIO_TYPE_DATA);
ASSERT(vdcp != NULL);
mutex_enter(&vdcp->lock);
idx = start = dring_msg->start_idx;
end = dring_msg->end_idx;
if ((start >= vdcp->dring_len) ||
(end >= vdcp->dring_len) || (end < -1)) {
VD_UPDATE_ERR_STATS(vdcp, vd_softerrs);
DMSG(vdcp, 0, "[%d] Bogus ACK data : start %d, end %d\n",
vdcp->instance, start, end);
mutex_exit(&vdcp->lock);
return (EINVAL);
}
switch (vdc_verify_seq_num(vdcp, dring_msg)) {
case VDC_SEQ_NUM_TODO:
break;
case VDC_SEQ_NUM_SKIP:
mutex_exit(&vdcp->lock);
return (0);
case VDC_SEQ_NUM_INVALID:
VD_UPDATE_ERR_STATS(vdcp, vd_softerrs);
DMSG(vdcp, 0, "[%d] invalid seqno\n", vdcp->instance);
mutex_exit(&vdcp->lock);
return (ENXIO);
}
if (msg->tag.vio_subtype == VIO_SUBTYPE_NACK) {
VD_UPDATE_ERR_STATS(vdcp, vd_softerrs);
VDC_DUMP_DRING_MSG(dring_msg);
DMSG(vdcp, 0, "[%d] DATA NACK\n", vdcp->instance);
mutex_exit(&vdcp->lock);
return (EIO);
} else if (msg->tag.vio_subtype == VIO_SUBTYPE_INFO) {
VD_UPDATE_ERR_STATS(vdcp, vd_protoerrs);
mutex_exit(&vdcp->lock);
return (EPROTO);
}
DMSG(vdcp, 1, ": start %d end %d\n", start, end);
ASSERT(start == end);
ldep = &vdcp->local_dring[idx];
DMSG(vdcp, 1, ": state 0x%x\n", ldep->dep->hdr.dstate);
if (ldep->dep->hdr.dstate == VIO_DESC_DONE) {
struct buf *bufp;
status = ldep->dep->payload.status;
bufp = ldep->buf;
ASSERT(bufp != NULL);
bufp->b_resid = bufp->b_bcount - ldep->dep->payload.nbytes;
bioerror(bufp, status);
if (status != 0) {
DMSG(vdcp, 1, "I/O status=%d\n", status);
}
DMSG(vdcp, 1,
"I/O complete req=%ld bytes resp=%ld bytes\n",
bufp->b_bcount, ldep->dep->payload.nbytes);
if (status != 0 &&
((vdcp->num_servers > 1 &&
(ldep->flags & VDC_OP_ERRCHK_BACKEND)) ||
(vdcp->failfast_interval != 0 &&
(ldep->flags & VDC_OP_ERRCHK_CONFLICT)))) {
(void) vdc_eio_queue(vdcp, idx);
} else {
op = ldep->operation;
if (op == VD_OP_BREAD || op == VD_OP_BWRITE) {
if (status == 0) {
VD_UPDATE_IO_STATS(vdcp, op,
ldep->dep->payload.nbytes);
} else {
VD_UPDATE_ERR_STATS(vdcp, vd_softerrs);
}
VD_KSTAT_RUNQ_EXIT(vdcp);
DTRACE_IO1(done, buf_t *, bufp);
}
(void) vdc_depopulate_descriptor(vdcp, idx);
biodone(bufp);
}
}
mutex_exit(&vdcp->lock);
DTRACE_PROBE2(processed, int, 1, vdc_t *, vdcp);
return (0);
}
static int
vdc_handle_ver_msg(vdc_t *vdc, vio_ver_msg_t *ver_msg)
{
int status = 0;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
if (ver_msg->tag.vio_subtype_env != VIO_VER_INFO) {
return (EPROTO);
}
if (ver_msg->dev_class != VDEV_DISK_SERVER) {
return (EINVAL);
}
switch (ver_msg->tag.vio_subtype) {
case VIO_SUBTYPE_ACK:
if (vdc_is_supported_version(ver_msg)) {
vdc->ver.major = ver_msg->ver_major;
vdc->ver.minor = ver_msg->ver_minor;
ASSERT(vdc->ver.major > 0);
} else {
status = EPROTO;
}
break;
case VIO_SUBTYPE_NACK:
(void) vdc_is_supported_version(ver_msg);
if (ver_msg->ver_major > 0) {
size_t len = sizeof (*ver_msg);
ASSERT(vdc->ver.major > 0);
ver_msg->tag.vio_subtype = VIO_SUBTYPE_INFO;
ver_msg->dev_class = VDEV_DISK;
status = vdc_send(vdc, (caddr_t)ver_msg, &len);
DMSG(vdc, 0, "[%d] Resend VER info (LDC status = %d)\n",
vdc->instance, status);
if (len != sizeof (*ver_msg))
status = EBADMSG;
} else {
DMSG(vdc, 0, "[%d] No common version with vDisk server",
vdc->instance);
status = ENOTSUP;
}
break;
case VIO_SUBTYPE_INFO:
status = ENOTSUP;
break;
default:
status = EINVAL;
break;
}
return (status);
}
static int
vdc_handle_attr_msg(vdc_t *vdc, vd_attr_msg_t *attr_msg)
{
int status = 0;
vd_disk_type_t old_type;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
if (attr_msg->tag.vio_subtype_env != VIO_ATTR_INFO) {
return (EPROTO);
}
switch (attr_msg->tag.vio_subtype) {
case VIO_SUBTYPE_ACK:
if (attr_msg->vdisk_size == 0) {
DMSG(vdc, 0, "[%d] Invalid disk size from vds",
vdc->instance);
status = EINVAL;
break;
}
if (attr_msg->max_xfer_sz == 0) {
DMSG(vdc, 0, "[%d] Invalid transfer size from vds",
vdc->instance);
status = EINVAL;
break;
}
if (attr_msg->vdisk_size == VD_SIZE_UNKNOWN) {
DMSG(vdc, 0, "[%d] Unknown disk size from vds",
vdc->instance);
attr_msg->vdisk_size = 0;
}
if (attr_msg->vdisk_block_size > 0 &&
vdc_update_vio_bsize(vdc,
attr_msg->vdisk_block_size) != 0) {
DMSG(vdc, 0, "[%d] Invalid block size (%u) from vds",
vdc->instance, attr_msg->vdisk_block_size);
status = EINVAL;
break;
}
old_type = vdc->vdisk_type;
vdc_update_size(vdc, attr_msg->vdisk_size,
attr_msg->vdisk_block_size, attr_msg->max_xfer_sz);
vdc->vdisk_type = attr_msg->vdisk_type;
vdc->operations = attr_msg->operations;
if (vio_ver_is_supported(vdc->ver, 1, 1))
vdc->vdisk_media = attr_msg->vdisk_media;
else
vdc->vdisk_media = 0;
DMSG(vdc, 0, "[%d] max_xfer_sz: sent %lx acked %lx\n",
vdc->instance, vdc->max_xfer_sz, attr_msg->max_xfer_sz);
DMSG(vdc, 0, "[%d] vdisk_block_size: sent %lx acked %x\n",
vdc->instance, vdc->vdisk_bsize,
attr_msg->vdisk_block_size);
if ((attr_msg->xfer_mode != VIO_DRING_MODE_V1_0) ||
(attr_msg->vdisk_size > INT64_MAX) ||
(attr_msg->operations == 0) ||
(attr_msg->vdisk_type > VD_DISK_TYPE_DISK)) {
DMSG(vdc, 0, "[%d] Invalid attributes from vds",
vdc->instance);
status = EINVAL;
break;
}
vdc_create_fake_geometry(vdc);
if (old_type == VD_DISK_TYPE_UNK &&
(vdc->initialized & VDC_MINOR) &&
vdc->vdisk_type == VD_DISK_TYPE_SLICE) {
ddi_remove_minor_node(vdc->dip, NULL);
(void) devfs_clean(ddi_get_parent(vdc->dip),
NULL, DV_CLEAN_FORCE);
if (vdc_create_device_nodes(vdc) != 0) {
DMSG(vdc, 0, "![%d] Failed to update "
"device nodes", vdc->instance);
}
}
break;
case VIO_SUBTYPE_NACK:
status = EPROTO;
break;
case VIO_SUBTYPE_INFO:
status = ENOTSUP;
break;
default:
status = ENOTSUP;
break;
}
return (status);
}
static int
vdc_handle_dring_reg_msg(vdc_t *vdc, vio_dring_reg_msg_t *dring_msg)
{
int status = 0;
ASSERT(vdc != NULL);
ASSERT(mutex_owned(&vdc->lock));
if (dring_msg->tag.vio_subtype_env != VIO_DRING_REG) {
return (EPROTO);
}
switch (dring_msg->tag.vio_subtype) {
case VIO_SUBTYPE_ACK:
vdc->dring_ident = dring_msg->dring_ident;
DMSG(vdc, 0, "[%d] Received dring ident=0x%lx\n",
vdc->instance, vdc->dring_ident);
break;
case VIO_SUBTYPE_NACK:
DMSG(vdc, 0, "[%d] server could not register DRing\n",
vdc->instance);
status = EPROTO;
break;
case VIO_SUBTYPE_INFO:
status = ENOTSUP;
break;
default:
status = ENOTSUP;
}
return (status);
}
static int
vdc_verify_seq_num(vdc_t *vdc, vio_dring_msg_t *dring_msg)
{
ASSERT(vdc != NULL);
ASSERT(dring_msg != NULL);
ASSERT(mutex_owned(&vdc->lock));
if ((dring_msg->seq_num <= vdc->seq_num_reply) ||
(dring_msg->seq_num > vdc->seq_num)) {
DMSG(vdc, 0, "?[%d] Bogus sequence_number %lu: "
"%lu > expected <= %lu (last proc req %lu sent %lu)\n",
vdc->instance, dring_msg->seq_num,
vdc->seq_num_reply, vdc->seq_num,
vdc->req_id_proc, vdc->req_id);
return (VDC_SEQ_NUM_INVALID);
}
vdc->seq_num_reply = dring_msg->seq_num;
if (vdc->req_id_proc < vdc->req_id)
return (VDC_SEQ_NUM_TODO);
else
return (VDC_SEQ_NUM_SKIP);
}
static boolean_t
vdc_is_supported_version(vio_ver_msg_t *ver_msg)
{
int vdc_num_versions = sizeof (vdc_version) / sizeof (vdc_version[0]);
for (int i = 0; i < vdc_num_versions; i++) {
ASSERT(vdc_version[i].major > 0);
ASSERT((i == 0) ||
(vdc_version[i].major < vdc_version[i-1].major));
if (ver_msg->ver_major == vdc_version[i].major) {
if (ver_msg->ver_minor > vdc_version[i].minor) {
DMSGX(0,
"Adjusting minor version from %u to %u",
ver_msg->ver_minor, vdc_version[i].minor);
ver_msg->ver_minor = vdc_version[i].minor;
}
return (B_TRUE);
}
if (ver_msg->ver_major > vdc_version[i].major) {
ver_msg->ver_major = vdc_version[i].major;
ver_msg->ver_minor = vdc_version[i].minor;
DMSGX(0, "Suggesting major/minor (0x%x/0x%x)\n",
ver_msg->ver_major, ver_msg->ver_minor);
return (B_FALSE);
}
}
ver_msg->ver_major = 0;
ver_msg->ver_minor = 0;
return (B_FALSE);
}
typedef struct vdc_dk_arg {
struct dk_callback dkc;
int mode;
dev_t dev;
vdc_t *vdc;
} vdc_dk_arg_t;
void
vdc_dkio_flush_cb(void *arg)
{
struct vdc_dk_arg *dk_arg = (struct vdc_dk_arg *)arg;
struct dk_callback *dkc = NULL;
vdc_t *vdc = NULL;
int rv;
if (dk_arg == NULL) {
cmn_err(CE_NOTE, "?[Unk] DKIOCFLUSHWRITECACHE arg is NULL\n");
return;
}
dkc = &dk_arg->dkc;
vdc = dk_arg->vdc;
ASSERT(vdc != NULL);
rv = vdc_do_sync_op(vdc, VD_OP_FLUSH, NULL, 0,
VDCPART(dk_arg->dev), 0, VIO_both_dir, B_TRUE);
if (rv != 0) {
DMSG(vdc, 0, "[%d] DKIOCFLUSHWRITECACHE failed %d : model %x\n",
vdc->instance, rv,
ddi_model_convert_from(dk_arg->mode & FMODELS));
}
if ((dk_arg->mode & FKIOCTL) &&
(dkc != NULL) &&
(dkc->dkc_callback != NULL)) {
ASSERT(dkc->dkc_cookie != NULL);
(*dkc->dkc_callback)(dkc->dkc_cookie, rv);
}
mutex_enter(&vdc->lock);
vdc->dkio_flush_pending--;
ASSERT(vdc->dkio_flush_pending >= 0);
mutex_exit(&vdc->lock);
kmem_free(arg, sizeof (vdc_dk_arg_t));
}
static int
vdc_dkio_gapart(vdc_t *vdc, caddr_t arg, int flag)
{
struct dk_geom *geom;
struct extvtoc *vtoc;
union {
struct dk_map map[NDKMAP];
struct dk_map32 map32[NDKMAP];
} data;
int i, rv, size;
mutex_enter(&vdc->lock);
if ((rv = vdc_validate_geometry(vdc)) != 0) {
mutex_exit(&vdc->lock);
return (rv);
}
if (vdc->vdisk_size > VD_OLDVTOC_LIMIT) {
mutex_exit(&vdc->lock);
return (EOVERFLOW);
}
vtoc = vdc->vtoc;
geom = vdc->geom;
if (ddi_model_convert_from(flag & FMODELS) == DDI_MODEL_ILP32) {
for (i = 0; i < vtoc->v_nparts; i++) {
data.map32[i].dkl_cylno = vtoc->v_part[i].p_start /
(geom->dkg_nhead * geom->dkg_nsect);
data.map32[i].dkl_nblk = vtoc->v_part[i].p_size;
}
size = NDKMAP * sizeof (struct dk_map32);
} else {
for (i = 0; i < vtoc->v_nparts; i++) {
data.map[i].dkl_cylno = vtoc->v_part[i].p_start /
(geom->dkg_nhead * geom->dkg_nsect);
data.map[i].dkl_nblk = vtoc->v_part[i].p_size;
}
size = NDKMAP * sizeof (struct dk_map);
}
mutex_exit(&vdc->lock);
if (ddi_copyout(&data, arg, size, flag) != 0)
return (EFAULT);
return (0);
}
static int
vdc_dkio_partition(vdc_t *vdc, caddr_t arg, int flag)
{
struct partition64 p64;
efi_gpt_t *gpt;
efi_gpe_t *gpe;
vd_efi_dev_t edev;
uint_t partno;
int rv;
if (ddi_copyin(arg, &p64, sizeof (struct partition64), flag)) {
return (EFAULT);
}
VDC_EFI_DEV_SET(edev, vdc, vd_process_efi_ioctl);
if ((rv = vd_efi_alloc_and_read(&edev, &gpt, &gpe)) != 0) {
return (rv);
}
partno = p64.p_partno;
if (partno >= gpt->efi_gpt_NumberOfPartitionEntries) {
vd_efi_free(&edev, gpt, gpe);
return (ESRCH);
}
bcopy(&gpe[partno].efi_gpe_PartitionTypeGUID, &p64.p_type,
sizeof (struct uuid));
p64.p_start = gpe[partno].efi_gpe_StartingLBA;
p64.p_size = gpe[partno].efi_gpe_EndingLBA - p64.p_start + 1;
if (ddi_copyout(&p64, arg, sizeof (struct partition64), flag)) {
vd_efi_free(&edev, gpt, gpe);
return (EFAULT);
}
vd_efi_free(&edev, gpt, gpe);
return (0);
}
static int
vdc_dioctl_rwcmd(vdc_t *vdc, caddr_t arg, int flag)
{
struct dadkio_rwcmd32 rwcmd32;
struct dadkio_rwcmd rwcmd;
struct iovec aiov;
struct uio auio;
int rw, status;
struct buf *buf;
if (ddi_model_convert_from(flag & FMODELS) == DDI_MODEL_ILP32) {
if (ddi_copyin((caddr_t)arg, (caddr_t)&rwcmd32,
sizeof (struct dadkio_rwcmd32), flag)) {
return (EFAULT);
}
rwcmd.cmd = rwcmd32.cmd;
rwcmd.flags = rwcmd32.flags;
rwcmd.blkaddr = (daddr_t)rwcmd32.blkaddr;
rwcmd.buflen = rwcmd32.buflen;
rwcmd.bufaddr = (caddr_t)(uintptr_t)rwcmd32.bufaddr;
} else {
if (ddi_copyin((caddr_t)arg, (caddr_t)&rwcmd,
sizeof (struct dadkio_rwcmd), flag)) {
return (EFAULT);
}
}
switch (rwcmd.cmd) {
case DADKIO_RWCMD_READ:
rw = B_READ;
break;
case DADKIO_RWCMD_WRITE:
rw = B_WRITE;
break;
default:
return (EINVAL);
}
bzero((caddr_t)&aiov, sizeof (struct iovec));
aiov.iov_base = rwcmd.bufaddr;
aiov.iov_len = rwcmd.buflen;
bzero((caddr_t)&auio, sizeof (struct uio));
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_loffset = rwcmd.blkaddr * vdc->vdisk_bsize;
auio.uio_resid = rwcmd.buflen;
auio.uio_segflg = flag & FKIOCTL ? UIO_SYSSPACE : UIO_USERSPACE;
buf = kmem_alloc(sizeof (buf_t), KM_SLEEP);
bioinit(buf);
buf->b_private = (void *)VD_SLICE_NONE;
status = physio(vdc_strategy, buf, VD_MAKE_DEV(vdc->instance, 0),
rw, vdc_min, &auio);
biofini(buf);
kmem_free(buf, sizeof (buf_t));
return (status);
}
static vd_scsi_t *
vdc_scsi_alloc(int cdb_len, int sense_len, int datain_len, int dataout_len,
int *alloc_len)
{
vd_scsi_t *vd_scsi;
int vd_scsi_len = VD_SCSI_SIZE;
vd_scsi_len += P2ROUNDUP(cdb_len, sizeof (uint64_t));
vd_scsi_len += P2ROUNDUP(sense_len, sizeof (uint64_t));
vd_scsi_len += P2ROUNDUP(datain_len, sizeof (uint64_t));
vd_scsi_len += P2ROUNDUP(dataout_len, sizeof (uint64_t));
ASSERT(vd_scsi_len % sizeof (uint64_t) == 0);
vd_scsi = kmem_zalloc(vd_scsi_len, KM_SLEEP);
vd_scsi->cdb_len = cdb_len;
vd_scsi->sense_len = sense_len;
vd_scsi->datain_len = datain_len;
vd_scsi->dataout_len = dataout_len;
*alloc_len = vd_scsi_len;
return (vd_scsi);
}
static int
vdc_scsi_status(vdc_t *vdc, vd_scsi_t *vd_scsi, boolean_t log_error)
{
int rv;
char path_str[MAXPATHLEN];
char panic_str[VDC_RESV_CONFLICT_FMT_LEN + MAXPATHLEN];
union scsi_cdb *cdb;
struct scsi_extended_sense *sense;
if (vd_scsi->cmd_status == STATUS_GOOD)
return (0);
if (vdc_scsi_log_error)
log_error = B_TRUE;
if (log_error) {
cmn_err(CE_WARN, "%s (vdc%d):\tError for Command: 0x%x)\n",
ddi_pathname(vdc->dip, path_str), vdc->instance,
GETCMD(VD_SCSI_DATA_CDB(vd_scsi)));
}
rv = EIO;
switch (vd_scsi->cmd_status) {
case STATUS_CHECK:
case STATUS_TERMINATED:
if (log_error)
cmn_err(CE_CONT, "\tCheck Condition Error\n");
if (vd_scsi->sense_len == 0 ||
vd_scsi->sense_status != STATUS_GOOD) {
if (log_error)
cmn_err(CE_CONT, "\tNo Sense Data Available\n");
break;
}
sense = VD_SCSI_DATA_SENSE(vd_scsi);
if (log_error) {
cmn_err(CE_CONT, "\tSense Key: 0x%x\n"
"\tASC: 0x%x, ASCQ: 0x%x\n",
scsi_sense_key((uint8_t *)sense),
scsi_sense_asc((uint8_t *)sense),
scsi_sense_ascq((uint8_t *)sense));
}
if (scsi_sense_key((uint8_t *)sense) == KEY_ILLEGAL_REQUEST)
rv = ENOTSUP;
break;
case STATUS_BUSY:
if (log_error)
cmn_err(CE_NOTE, "\tDevice Busy\n");
break;
case STATUS_RESERVATION_CONFLICT:
cdb = VD_SCSI_DATA_CDB(vd_scsi);
if (vdc->failfast_interval != 0 &&
cdb->scc_cmd != SCMD_PERSISTENT_RESERVE_IN &&
cdb->scc_cmd != SCMD_PERSISTENT_RESERVE_OUT) {
(void) snprintf(panic_str, sizeof (panic_str),
VDC_RESV_CONFLICT_FMT_STR "%s",
ddi_pathname(vdc->dip, path_str));
panic(panic_str);
}
if (log_error)
cmn_err(CE_NOTE, "\tReservation Conflict\n");
rv = EACCES;
break;
case STATUS_QFULL:
if (log_error)
cmn_err(CE_NOTE, "\tQueue Full\n");
break;
case STATUS_MET:
case STATUS_INTERMEDIATE:
case STATUS_SCSI2:
case STATUS_INTERMEDIATE_MET:
case STATUS_ACA_ACTIVE:
if (log_error)
cmn_err(CE_CONT,
"\tUnexpected SCSI status received: 0x%x\n",
vd_scsi->cmd_status);
break;
default:
if (log_error)
cmn_err(CE_CONT,
"\tInvalid SCSI status received: 0x%x\n",
vd_scsi->cmd_status);
break;
}
return (rv);
}
static int
vdc_uscsi_cmd(vdc_t *vdc, caddr_t arg, int mode)
{
struct uscsi_cmd uscsi;
struct uscsi_cmd32 uscsi32;
vd_scsi_t *vd_scsi;
int vd_scsi_len;
union scsi_cdb *cdb;
struct scsi_extended_sense *sense;
char *datain, *dataout;
size_t cdb_len, datain_len, dataout_len, sense_len;
int rv;
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
if (ddi_copyin(arg, &uscsi32, sizeof (struct uscsi_cmd32),
mode) != 0)
return (EFAULT);
uscsi_cmd32touscsi_cmd((&uscsi32), (&uscsi));
} else {
if (ddi_copyin(arg, &uscsi, sizeof (struct uscsi_cmd),
mode) != 0)
return (EFAULT);
}
if (uscsi.uscsi_flags & (USCSI_RESET | USCSI_RESET_LUN |
USCSI_RESET_ALL)) {
rv = vdc_do_sync_op(vdc, VD_OP_RESET, NULL, 0, 0, 0,
VIO_both_dir, B_TRUE);
return (rv);
}
cdb_len = uscsi.uscsi_cdblen;
if (uscsi.uscsi_flags & USCSI_READ) {
datain_len = uscsi.uscsi_buflen;
dataout_len = 0;
} else {
datain_len = 0;
dataout_len = uscsi.uscsi_buflen;
}
if (uscsi.uscsi_flags & USCSI_RQENABLE)
sense_len = uscsi.uscsi_rqlen;
else
sense_len = 0;
vd_scsi = vdc_scsi_alloc(cdb_len, sense_len, datain_len, dataout_len,
&vd_scsi_len);
if ((uscsi.uscsi_flags & USCSI_ISOLATE) ||
(uscsi.uscsi_flags & USCSI_DIAGNOSE))
vd_scsi->options |= VD_SCSI_OPT_NORETRY;
if (uscsi.uscsi_flags & USCSI_NOTAG) {
vd_scsi->task_attribute = 0;
} else {
if (uscsi.uscsi_flags & USCSI_HEAD)
vd_scsi->task_attribute = VD_SCSI_TASK_ACA;
else if (uscsi.uscsi_flags & USCSI_HTAG)
vd_scsi->task_attribute = VD_SCSI_TASK_HQUEUE;
else if (uscsi.uscsi_flags & USCSI_OTAG)
vd_scsi->task_attribute = VD_SCSI_TASK_ORDERED;
else
vd_scsi->task_attribute = 0;
}
vd_scsi->timeout = uscsi.uscsi_timeout;
cdb = VD_SCSI_DATA_CDB(vd_scsi);
if (ddi_copyin(uscsi.uscsi_cdb, cdb, cdb_len, mode) != 0) {
rv = EFAULT;
goto done;
}
sense = VD_SCSI_DATA_SENSE(vd_scsi);
datain = (char *)VD_SCSI_DATA_IN(vd_scsi);
dataout = (char *)VD_SCSI_DATA_OUT(vd_scsi);
if (!(uscsi.uscsi_flags & USCSI_READ)) {
if (ddi_copyin(uscsi.uscsi_bufaddr, dataout, dataout_len,
mode)) {
rv = EFAULT;
goto done;
}
}
rv = vdc_do_sync_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, VIO_both_dir, B_FALSE);
if (rv != 0)
goto done;
uscsi.uscsi_status = vd_scsi->cmd_status;
if ((uscsi.uscsi_flags & USCSI_RQENABLE) &&
(uscsi.uscsi_status == STATUS_CHECK ||
uscsi.uscsi_status == STATUS_TERMINATED)) {
uscsi.uscsi_rqstatus = vd_scsi->sense_status;
if (uscsi.uscsi_rqstatus == STATUS_GOOD) {
uscsi.uscsi_rqresid = uscsi.uscsi_rqlen -
vd_scsi->sense_len;
if (ddi_copyout(sense, uscsi.uscsi_rqbuf,
vd_scsi->sense_len, mode) != 0) {
rv = EFAULT;
goto done;
}
}
}
if (uscsi.uscsi_status == STATUS_GOOD) {
if (uscsi.uscsi_flags & USCSI_READ) {
uscsi.uscsi_resid = uscsi.uscsi_buflen -
vd_scsi->datain_len;
if (ddi_copyout(datain, uscsi.uscsi_bufaddr,
vd_scsi->datain_len, mode) != 0) {
rv = EFAULT;
goto done;
}
} else {
uscsi.uscsi_resid = uscsi.uscsi_buflen -
vd_scsi->dataout_len;
}
}
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
uscsi_cmdtouscsi_cmd32((&uscsi), (&uscsi32));
if (ddi_copyout(&uscsi32, arg, sizeof (struct uscsi_cmd32),
mode) != 0) {
rv = EFAULT;
goto done;
}
} else {
if (ddi_copyout(&uscsi, arg, sizeof (struct uscsi_cmd),
mode) != 0) {
rv = EFAULT;
goto done;
}
}
rv = vdc_scsi_status(vdc, vd_scsi,
!(uscsi.uscsi_flags & USCSI_SILENT));
done:
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static vd_scsi_t *
vdc_scsi_alloc_persistent_in(uchar_t cmd, int len, int *vd_scsi_len)
{
int cdb_len, sense_len, datain_len, dataout_len;
vd_scsi_t *vd_scsi;
union scsi_cdb *cdb;
cdb_len = CDB_GROUP1;
sense_len = sizeof (struct scsi_extended_sense);
datain_len = len;
dataout_len = 0;
vd_scsi = vdc_scsi_alloc(cdb_len, sense_len, datain_len, dataout_len,
vd_scsi_len);
cdb = VD_SCSI_DATA_CDB(vd_scsi);
cdb->scc_cmd = SCMD_PERSISTENT_RESERVE_IN;
cdb->cdb_opaque[1] = cmd;
FORMG1COUNT(cdb, datain_len);
vd_scsi->timeout = vdc_scsi_timeout;
return (vd_scsi);
}
static vd_scsi_t *
vdc_scsi_alloc_persistent_out(uchar_t cmd, int len, int *vd_scsi_len)
{
int cdb_len, sense_len, datain_len, dataout_len;
vd_scsi_t *vd_scsi;
union scsi_cdb *cdb;
cdb_len = CDB_GROUP1;
sense_len = sizeof (struct scsi_extended_sense);
datain_len = 0;
dataout_len = len;
vd_scsi = vdc_scsi_alloc(cdb_len, sense_len, datain_len, dataout_len,
vd_scsi_len);
cdb = VD_SCSI_DATA_CDB(vd_scsi);
cdb->scc_cmd = SCMD_PERSISTENT_RESERVE_OUT;
cdb->cdb_opaque[1] = cmd;
FORMG1COUNT(cdb, dataout_len);
vd_scsi->timeout = vdc_scsi_timeout;
return (vd_scsi);
}
static int
vdc_mhd_inkeys(vdc_t *vdc, caddr_t arg, int mode)
{
vd_scsi_t *vd_scsi;
mhioc_inkeys_t inkeys;
mhioc_key_list_t klist;
struct mhioc_inkeys32 inkeys32;
struct mhioc_key_list32 klist32;
sd_prin_readkeys_t *scsi_keys;
void *user_keys;
int vd_scsi_len;
int listsize, listlen, rv;
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
rv = ddi_copyin(arg, &inkeys32, sizeof (inkeys32), mode);
if (rv != 0)
return (EFAULT);
rv = ddi_copyin((caddr_t)(uintptr_t)inkeys32.li, &klist32,
sizeof (klist32), mode);
if (rv != 0)
return (EFAULT);
listsize = klist32.listsize;
} else {
rv = ddi_copyin(arg, &inkeys, sizeof (inkeys), mode);
if (rv != 0)
return (EFAULT);
rv = ddi_copyin(inkeys.li, &klist, sizeof (klist), mode);
if (rv != 0)
return (EFAULT);
listsize = klist.listsize;
}
vd_scsi = vdc_scsi_alloc_persistent_in(SD_READ_KEYS,
sizeof (sd_prin_readkeys_t) - sizeof (caddr_t) +
(sizeof (mhioc_resv_key_t) * listsize), &vd_scsi_len);
scsi_keys = (sd_prin_readkeys_t *)VD_SCSI_DATA_IN(vd_scsi);
rv = vdc_do_sync_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, VIO_both_dir, B_FALSE);
if (rv != 0)
goto done;
listlen = scsi_keys->len / MHIOC_RESV_KEY_SIZE;
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
inkeys32.generation = scsi_keys->generation;
rv = ddi_copyout(&inkeys32, arg, sizeof (inkeys32), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
klist32.listlen = listlen;
rv = ddi_copyout(&klist32, (caddr_t)(uintptr_t)inkeys32.li,
sizeof (klist32), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
user_keys = (caddr_t)(uintptr_t)klist32.list;
} else {
inkeys.generation = scsi_keys->generation;
rv = ddi_copyout(&inkeys, arg, sizeof (inkeys), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
klist.listlen = listlen;
rv = ddi_copyout(&klist, inkeys.li, sizeof (klist), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
user_keys = klist.list;
}
if (listlen > 0 && listsize > 0) {
if (listsize < listlen)
listlen = listsize;
rv = ddi_copyout(&scsi_keys->keylist, user_keys,
listlen * MHIOC_RESV_KEY_SIZE, mode);
if (rv != 0)
rv = EFAULT;
}
if (rv == 0)
rv = vdc_scsi_status(vdc, vd_scsi, B_FALSE);
done:
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static int
vdc_mhd_inresv(vdc_t *vdc, caddr_t arg, int mode)
{
vd_scsi_t *vd_scsi;
mhioc_inresvs_t inresv;
mhioc_resv_desc_list_t rlist;
struct mhioc_inresvs32 inresv32;
struct mhioc_resv_desc_list32 rlist32;
mhioc_resv_desc_t mhd_resv;
sd_prin_readresv_t *scsi_resv;
sd_readresv_desc_t *resv;
mhioc_resv_desc_t *user_resv;
int vd_scsi_len;
int listsize, listlen, i, rv;
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
rv = ddi_copyin(arg, &inresv32, sizeof (inresv32), mode);
if (rv != 0)
return (EFAULT);
rv = ddi_copyin((caddr_t)(uintptr_t)inresv32.li, &rlist32,
sizeof (rlist32), mode);
if (rv != 0)
return (EFAULT);
listsize = rlist32.listsize;
} else {
rv = ddi_copyin(arg, &inresv, sizeof (inresv), mode);
if (rv != 0)
return (EFAULT);
rv = ddi_copyin(inresv.li, &rlist, sizeof (rlist), mode);
if (rv != 0)
return (EFAULT);
listsize = rlist.listsize;
}
vd_scsi = vdc_scsi_alloc_persistent_in(SD_READ_RESV,
sizeof (sd_prin_readresv_t) - sizeof (caddr_t) +
(SCSI3_RESV_DESC_LEN * listsize), &vd_scsi_len);
scsi_resv = (sd_prin_readresv_t *)VD_SCSI_DATA_IN(vd_scsi);
rv = vdc_do_sync_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, VIO_both_dir, B_FALSE);
if (rv != 0)
goto done;
listlen = scsi_resv->len / SCSI3_RESV_DESC_LEN;
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
inresv32.generation = scsi_resv->generation;
rv = ddi_copyout(&inresv32, arg, sizeof (inresv32), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
rlist32.listlen = listlen;
rv = ddi_copyout(&rlist32, (caddr_t)(uintptr_t)inresv32.li,
sizeof (rlist32), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
user_resv = (mhioc_resv_desc_t *)(uintptr_t)rlist32.list;
} else {
inresv.generation = scsi_resv->generation;
rv = ddi_copyout(&inresv, arg, sizeof (inresv), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
rlist.listlen = listlen;
rv = ddi_copyout(&rlist, inresv.li, sizeof (rlist), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
user_resv = rlist.list;
}
if (listsize > 0 && listlen > 0) {
if (listsize < listlen)
listlen = listsize;
resv = (sd_readresv_desc_t *)&scsi_resv->readresv_desc;
for (i = 0; i < listlen; i++) {
mhd_resv.type = resv->type;
mhd_resv.scope = resv->scope;
mhd_resv.scope_specific_addr =
BE_32(resv->scope_specific_addr);
bcopy(&resv->resvkey, &mhd_resv.key,
MHIOC_RESV_KEY_SIZE);
rv = ddi_copyout(&mhd_resv, user_resv,
sizeof (mhd_resv), mode);
if (rv != 0) {
rv = EFAULT;
goto done;
}
resv++;
user_resv++;
}
}
if (rv == 0)
rv = vdc_scsi_status(vdc, vd_scsi, B_FALSE);
done:
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static int
vdc_mhd_register(vdc_t *vdc, caddr_t arg, int mode)
{
vd_scsi_t *vd_scsi;
sd_prout_t *scsi_prout;
mhioc_register_t mhd_reg;
int vd_scsi_len, rv;
rv = ddi_copyin(arg, &mhd_reg, sizeof (mhd_reg), mode);
if (rv != 0)
return (EFAULT);
vd_scsi = vdc_scsi_alloc_persistent_out(SD_SCSI3_REGISTER,
sizeof (sd_prout_t), &vd_scsi_len);
scsi_prout = (sd_prout_t *)VD_SCSI_DATA_OUT(vd_scsi);
bcopy(mhd_reg.oldkey.key, scsi_prout->res_key, MHIOC_RESV_KEY_SIZE);
bcopy(mhd_reg.newkey.key, scsi_prout->service_key, MHIOC_RESV_KEY_SIZE);
scsi_prout->aptpl = (uchar_t)mhd_reg.aptpl;
rv = vdc_do_sync_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, VIO_both_dir, B_FALSE);
if (rv == 0)
rv = vdc_scsi_status(vdc, vd_scsi, B_FALSE);
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static int
vdc_mhd_reserve(vdc_t *vdc, caddr_t arg, int mode)
{
union scsi_cdb *cdb;
vd_scsi_t *vd_scsi;
sd_prout_t *scsi_prout;
mhioc_resv_desc_t mhd_resv;
int vd_scsi_len, rv;
rv = ddi_copyin(arg, &mhd_resv, sizeof (mhd_resv), mode);
if (rv != 0)
return (EFAULT);
vd_scsi = vdc_scsi_alloc_persistent_out(SD_SCSI3_RESERVE,
sizeof (sd_prout_t), &vd_scsi_len);
cdb = VD_SCSI_DATA_CDB(vd_scsi);
scsi_prout = (sd_prout_t *)VD_SCSI_DATA_OUT(vd_scsi);
bcopy(mhd_resv.key.key, scsi_prout->res_key, MHIOC_RESV_KEY_SIZE);
scsi_prout->scope_address = mhd_resv.scope_specific_addr;
cdb->cdb_opaque[2] = mhd_resv.type;
rv = vdc_do_sync_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, VIO_both_dir, B_FALSE);
if (rv == 0)
rv = vdc_scsi_status(vdc, vd_scsi, B_FALSE);
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static int
vdc_mhd_preemptabort(vdc_t *vdc, caddr_t arg, int mode)
{
union scsi_cdb *cdb;
vd_scsi_t *vd_scsi;
sd_prout_t *scsi_prout;
mhioc_preemptandabort_t mhd_preempt;
int vd_scsi_len, rv;
rv = ddi_copyin(arg, &mhd_preempt, sizeof (mhd_preempt), mode);
if (rv != 0)
return (EFAULT);
vd_scsi = vdc_scsi_alloc_persistent_out(SD_SCSI3_PREEMPTANDABORT,
sizeof (sd_prout_t), &vd_scsi_len);
vd_scsi->task_attribute = VD_SCSI_TASK_ACA;
cdb = VD_SCSI_DATA_CDB(vd_scsi);
scsi_prout = (sd_prout_t *)VD_SCSI_DATA_OUT(vd_scsi);
bcopy(mhd_preempt.resvdesc.key.key, scsi_prout->res_key,
MHIOC_RESV_KEY_SIZE);
bcopy(mhd_preempt.victim_key.key, scsi_prout->service_key,
MHIOC_RESV_KEY_SIZE);
scsi_prout->scope_address = mhd_preempt.resvdesc.scope_specific_addr;
cdb->cdb_opaque[2] = mhd_preempt.resvdesc.type;
rv = vdc_do_sync_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, VIO_both_dir, B_FALSE);
if (rv == 0)
rv = vdc_scsi_status(vdc, vd_scsi, B_FALSE);
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static int
vdc_mhd_registerignore(vdc_t *vdc, caddr_t arg, int mode)
{
vd_scsi_t *vd_scsi;
sd_prout_t *scsi_prout;
mhioc_registerandignorekey_t mhd_regi;
int vd_scsi_len, rv;
rv = ddi_copyin(arg, &mhd_regi, sizeof (mhd_regi), mode);
if (rv != 0)
return (EFAULT);
vd_scsi = vdc_scsi_alloc_persistent_out(SD_SCSI3_REGISTERANDIGNOREKEY,
sizeof (sd_prout_t), &vd_scsi_len);
scsi_prout = (sd_prout_t *)VD_SCSI_DATA_OUT(vd_scsi);
bcopy(mhd_regi.newkey.key, scsi_prout->service_key,
MHIOC_RESV_KEY_SIZE);
scsi_prout->aptpl = (uchar_t)mhd_regi.aptpl;
rv = vdc_do_sync_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, VIO_both_dir, B_FALSE);
if (rv == 0)
rv = vdc_scsi_status(vdc, vd_scsi, B_FALSE);
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static int
vdc_eio_scsi_cmd(vdc_t *vdc, uchar_t scmd, int flags)
{
int cdb_len, sense_len, vd_scsi_len;
vd_scsi_t *vd_scsi;
union scsi_cdb *cdb;
int rv;
ASSERT(scmd == SCMD_TEST_UNIT_READY || scmd == SCMD_WRITE_G1);
if (scmd == SCMD_WRITE_G1)
cdb_len = CDB_GROUP1;
else
cdb_len = CDB_GROUP0;
sense_len = sizeof (struct scsi_extended_sense);
vd_scsi = vdc_scsi_alloc(cdb_len, sense_len, 0, 0, &vd_scsi_len);
cdb = VD_SCSI_DATA_CDB(vd_scsi);
cdb->scc_cmd = scmd;
vd_scsi->timeout = vdc_scsi_timeout;
ASSERT((flags & VDC_OP_ERRCHK) == 0);
rv = vdc_do_op(vdc, VD_OP_SCSICMD, (caddr_t)vd_scsi, vd_scsi_len,
0, 0, NULL, VIO_both_dir, flags);
if (rv == 0)
rv = vdc_scsi_status(vdc, vd_scsi, B_FALSE);
kmem_free(vd_scsi, vd_scsi_len);
return (rv);
}
static int
vdc_eio_scsi_check(vdc_t *vdc, int flags)
{
int failure = 0;
int rv;
rv = vdc_eio_scsi_cmd(vdc, SCMD_TEST_UNIT_READY, flags);
if (rv != 0 && rv != EACCES)
failure++;
if (vdc->failfast_interval == 0)
return (failure);
if (vdc_eio_scsi_cmd(vdc, SCMD_WRITE_G1, flags) != 0)
failure++;
return (failure);
}
static int
vdc_eio_check(vdc_t *vdc, int flags)
{
char *buffer;
diskaddr_t blkno;
int rv;
ASSERT((flags & VDC_OP_ERRCHK) == 0);
flags |= VDC_OP_DRING_RESERVED;
if (VD_OP_SUPPORTED(vdc->operations, VD_OP_SCSICMD))
return (vdc_eio_scsi_check(vdc, flags));
ASSERT(vdc->failfast_interval == 0);
buffer = kmem_alloc(vdc->vdisk_bsize, KM_SLEEP);
if (vdc->vdisk_size > 0) {
(void) random_get_pseudo_bytes((uint8_t *)&blkno,
sizeof (diskaddr_t));
blkno = blkno % vdc->vdisk_size;
rv = vdc_do_op(vdc, VD_OP_BREAD, (caddr_t)buffer,
vdc->vdisk_bsize, VD_SLICE_NONE, blkno, NULL,
VIO_read_dir, flags);
if (rv == 0)
goto done;
blkno = vdc->vdisk_size - 1;
rv = vdc_do_op(vdc, VD_OP_BREAD, (caddr_t)buffer,
vdc->vdisk_bsize, VD_SLICE_NONE, blkno, NULL,
VIO_read_dir, flags);
if (rv == 0)
goto done;
}
blkno = 0;
rv = vdc_do_op(vdc, VD_OP_BREAD, (caddr_t)buffer, vdc->vdisk_bsize,
VD_SLICE_NONE, blkno, NULL, VIO_read_dir, flags);
done:
kmem_free(buffer, vdc->vdisk_bsize);
return (rv);
}
static vdc_io_t *
vdc_eio_queue(vdc_t *vdc, int index)
{
vdc_io_t *vio;
ASSERT(MUTEX_HELD(&vdc->lock));
vio = kmem_alloc(sizeof (vdc_io_t), KM_SLEEP);
vio->vio_next = vdc->eio_queue;
vio->vio_index = index;
vio->vio_qtime = ddi_get_lbolt();
vdc->eio_queue = vio;
cv_signal(&vdc->eio_cv);
return (vio);
}
static void
vdc_eio_unqueue(vdc_t *vdc, clock_t deadline, boolean_t complete_io)
{
struct buf *buf;
vdc_io_t *vio, *vio_tmp;
int index, op;
ASSERT(MUTEX_HELD(&vdc->lock));
vio_tmp = NULL;
vio = vdc->eio_queue;
if (deadline != 0) {
while (vio != NULL && vio->vio_qtime > deadline) {
vio_tmp = vio;
vio = vio->vio_next;
}
}
if (vio == NULL)
return;
if (vio_tmp == NULL)
vdc->eio_queue = NULL;
else
vio_tmp->vio_next = NULL;
while (vio != NULL) {
vio_tmp = vio->vio_next;
if (complete_io) {
index = vio->vio_index;
op = vdc->local_dring[index].operation;
buf = vdc->local_dring[index].buf;
(void) vdc_depopulate_descriptor(vdc, index);
ASSERT(buf->b_flags & B_ERROR);
if (op == VD_OP_BREAD || op == VD_OP_BWRITE) {
VD_UPDATE_ERR_STATS(vdc, vd_softerrs);
VD_KSTAT_RUNQ_EXIT(vdc);
DTRACE_IO1(done, buf_t *, buf);
}
biodone(buf);
}
kmem_free(vio, sizeof (vdc_io_t));
vio = vio_tmp;
}
}
static void
vdc_eio_thread(void *arg)
{
int status;
vdc_t *vdc = (vdc_t *)arg;
clock_t starttime, timeout = drv_usectohz(vdc->failfast_interval);
mutex_enter(&vdc->lock);
while (vdc->failfast_interval != 0 || vdc->num_servers > 1) {
if (vdc->eio_queue == NULL || vdc->state != VDC_STATE_RUNNING) {
if (vdc->failfast_interval != 0) {
timeout = ddi_get_lbolt() +
drv_usectohz(vdc->failfast_interval);
(void) cv_timedwait(&vdc->eio_cv, &vdc->lock,
timeout);
} else {
ASSERT(vdc->num_servers > 1);
(void) cv_wait(&vdc->eio_cv, &vdc->lock);
}
if (vdc->state != VDC_STATE_RUNNING)
continue;
}
mutex_exit(&vdc->lock);
starttime = ddi_get_lbolt();
status = vdc_eio_check(vdc, VDC_OP_STATE_RUNNING);
mutex_enter(&vdc->lock);
if (vdc->failfast_interval == 0 && vdc->num_servers <= 1)
break;
if (vdc->state != VDC_STATE_RUNNING || vdc->eio_queue == NULL)
continue;
if (status == 0) {
vdc_eio_unqueue(vdc, starttime, B_TRUE);
} else if (vdc->num_servers > 1) {
mutex_enter(&vdc->read_lock);
vdc->read_state = VDC_READ_RESET;
cv_signal(&vdc->read_cv);
mutex_exit(&vdc->read_lock);
} else {
vdc_eio_unqueue(vdc, starttime, B_TRUE);
}
}
vdc_eio_unqueue(vdc, 0, B_TRUE);
vdc->eio_thread = NULL;
mutex_exit(&vdc->lock);
thread_exit();
}
static int
vdc_failfast(vdc_t *vdc, caddr_t arg, int mode)
{
unsigned int mh_time;
if (ddi_copyin((void *)arg, &mh_time, sizeof (int), mode))
return (EFAULT);
mutex_enter(&vdc->lock);
if (mh_time != 0 && vdc->eio_thread == NULL) {
vdc->eio_thread = thread_create(NULL, 0,
vdc_eio_thread, vdc, 0, &p0, TS_RUN,
v.v_maxsyspri - 2);
}
vdc->failfast_interval = ((long)mh_time) * MILLISEC;
cv_signal(&vdc->eio_cv);
mutex_exit(&vdc->lock);
return (0);
}
static int
vdc_access_set(vdc_t *vdc, uint64_t flags)
{
int rv;
rv = vdc_do_sync_op(vdc, VD_OP_SET_ACCESS, (caddr_t)&flags,
sizeof (uint64_t), 0, 0, VIO_both_dir, B_TRUE);
return (rv);
}
static int
vdc_access_get(vdc_t *vdc, uint64_t *status)
{
int rv;
rv = vdc_do_sync_op(vdc, VD_OP_GET_ACCESS, (caddr_t)status,
sizeof (uint64_t), 0, 0, VIO_both_dir, B_TRUE);
return (rv);
}
static void
vdc_ownership_thread(void *arg)
{
vdc_t *vdc = (vdc_t *)arg;
clock_t timeout;
uint64_t status;
mutex_enter(&vdc->ownership_lock);
mutex_enter(&vdc->lock);
while (vdc->ownership & VDC_OWNERSHIP_WANTED) {
if ((vdc->ownership & VDC_OWNERSHIP_RESET) ||
!(vdc->ownership & VDC_OWNERSHIP_GRANTED)) {
DMSG(vdc, 0, "[%d] Ownership lost, recovering",
vdc->instance);
vdc->ownership &= ~(VDC_OWNERSHIP_RESET |
VDC_OWNERSHIP_GRANTED);
mutex_exit(&vdc->lock);
status = vdc_access_set(vdc, VD_ACCESS_SET_EXCLUSIVE |
VD_ACCESS_SET_PRESERVE);
mutex_enter(&vdc->lock);
if (status == 0) {
DMSG(vdc, 0, "[%d] Ownership recovered",
vdc->instance);
vdc->ownership |= VDC_OWNERSHIP_GRANTED;
} else {
DMSG(vdc, 0, "[%d] Fail to recover ownership",
vdc->instance);
}
}
if (vdc->ownership & VDC_OWNERSHIP_GRANTED)
timeout = 0;
else
timeout = drv_usectohz(vdc_ownership_delay);
mutex_exit(&vdc->ownership_lock);
if (timeout == 0)
(void) cv_wait(&vdc->ownership_cv, &vdc->lock);
else
(void) cv_reltimedwait(&vdc->ownership_cv, &vdc->lock,
timeout, TR_CLOCK_TICK);
mutex_exit(&vdc->lock);
mutex_enter(&vdc->ownership_lock);
mutex_enter(&vdc->lock);
}
vdc->ownership_thread = NULL;
mutex_exit(&vdc->lock);
mutex_exit(&vdc->ownership_lock);
thread_exit();
}
static void
vdc_ownership_update(vdc_t *vdc, int ownership_flags)
{
ASSERT(MUTEX_HELD(&vdc->ownership_lock));
mutex_enter(&vdc->lock);
vdc->ownership = ownership_flags;
if ((vdc->ownership & VDC_OWNERSHIP_WANTED) &&
vdc->ownership_thread == NULL) {
vdc->ownership_thread = thread_create(NULL, 0,
vdc_ownership_thread, vdc, 0, &p0, TS_RUN,
v.v_maxsyspri - 2);
} else {
cv_signal(&vdc->ownership_cv);
}
mutex_exit(&vdc->lock);
}
static int
vdc_get_capacity(vdc_t *vdc, size_t *dsk_size, size_t *blk_size)
{
int rv = 0;
size_t alloc_len;
vd_capacity_t *vd_cap;
ASSERT(MUTEX_NOT_HELD(&vdc->lock));
alloc_len = P2ROUNDUP(sizeof (vd_capacity_t), sizeof (uint64_t));
vd_cap = kmem_zalloc(alloc_len, KM_SLEEP);
rv = vdc_do_sync_op(vdc, VD_OP_GET_CAPACITY, (caddr_t)vd_cap, alloc_len,
0, 0, VIO_both_dir, B_TRUE);
*dsk_size = vd_cap->vdisk_size;
*blk_size = vd_cap->vdisk_block_size;
kmem_free(vd_cap, alloc_len);
return (rv);
}
static int
vdc_check_capacity(vdc_t *vdc)
{
size_t dsk_size, blk_size;
int rv;
if (!VD_OP_SUPPORTED(vdc->operations, VD_OP_GET_CAPACITY))
return (0);
if ((rv = vdc_get_capacity(vdc, &dsk_size, &blk_size)) != 0)
return (rv);
if (dsk_size == VD_SIZE_UNKNOWN || dsk_size == 0 || blk_size == 0)
return (EINVAL);
mutex_enter(&vdc->lock);
rv = vdc_update_vio_bsize(vdc, blk_size);
if (rv == 0)
vdc_update_size(vdc, dsk_size, blk_size, vdc->max_xfer_sz);
mutex_exit(&vdc->lock);
return (rv);
}
typedef struct vdc_dk_ioctl {
uint8_t op;
int cmd;
size_t nbytes;
int (*convert)(vdc_t *vdc, void *vd_buf, void *ioctl_arg,
int mode, int dir);
} vdc_dk_ioctl_t;
static vdc_dk_ioctl_t dk_ioctl[] = {
{VD_OP_FLUSH, DKIOCFLUSHWRITECACHE, 0,
vdc_null_copy_func},
{VD_OP_GET_WCE, DKIOCGETWCE, sizeof (int),
vdc_get_wce_convert},
{VD_OP_SET_WCE, DKIOCSETWCE, sizeof (int),
vdc_set_wce_convert},
{VD_OP_GET_VTOC, DKIOCGVTOC, sizeof (vd_vtoc_t),
vdc_get_vtoc_convert},
{VD_OP_SET_VTOC, DKIOCSVTOC, sizeof (vd_vtoc_t),
vdc_set_vtoc_convert},
{VD_OP_GET_VTOC, DKIOCGEXTVTOC, sizeof (vd_vtoc_t),
vdc_get_extvtoc_convert},
{VD_OP_SET_VTOC, DKIOCSEXTVTOC, sizeof (vd_vtoc_t),
vdc_set_extvtoc_convert},
{VD_OP_GET_DISKGEOM, DKIOCGGEOM, sizeof (vd_geom_t),
vdc_get_geom_convert},
{VD_OP_GET_DISKGEOM, DKIOCG_PHYGEOM, sizeof (vd_geom_t),
vdc_get_geom_convert},
{VD_OP_GET_DISKGEOM, DKIOCG_VIRTGEOM, sizeof (vd_geom_t),
vdc_get_geom_convert},
{VD_OP_SET_DISKGEOM, DKIOCSGEOM, sizeof (vd_geom_t),
vdc_set_geom_convert},
{VD_OP_GET_EFI, DKIOCGETEFI, 0,
vdc_get_efi_convert},
{VD_OP_SET_EFI, DKIOCSETEFI, 0,
vdc_set_efi_convert},
{0, DIOCTL_RWCMD, sizeof (struct dadkio_rwcmd), NULL},
{0, MHIOCTKOWN, 0, vdc_null_copy_func},
{0, MHIOCRELEASE, 0, vdc_null_copy_func},
{0, MHIOCSTATUS, 0, vdc_null_copy_func},
{0, MHIOCQRESERVE, 0, vdc_null_copy_func},
{0, MHIOCGRP_INKEYS, 0, vdc_null_copy_func},
{0, MHIOCGRP_INRESV, 0, vdc_null_copy_func},
{0, MHIOCGRP_REGISTER, 0, vdc_null_copy_func},
{0, MHIOCGRP_RESERVE, 0, vdc_null_copy_func},
{0, MHIOCGRP_PREEMPTANDABORT, 0, vdc_null_copy_func},
{0, MHIOCGRP_REGISTERANDIGNOREKEY, 0, vdc_null_copy_func},
{0, MHIOCENFAILFAST, 0, vdc_null_copy_func},
{0, DKIOCINFO, sizeof (struct dk_cinfo), vdc_null_copy_func},
{0, DKIOCGMEDIAINFO, sizeof (struct dk_minfo), vdc_null_copy_func},
{0, USCSICMD, sizeof (struct uscsi_cmd), vdc_null_copy_func},
{0, DKIOCPARTITION, 0, vdc_null_copy_func },
{0, DKIOCGAPART, 0, vdc_null_copy_func },
{0, DKIOCREMOVABLE, 0, vdc_null_copy_func},
{0, CDROMREADOFFSET, 0, vdc_null_copy_func}
};
static int
vd_process_efi_ioctl(void *vdisk, int cmd, uintptr_t arg)
{
vdc_t *vdc = (vdc_t *)vdisk;
dev_t dev;
int rval;
dev = makedevice(ddi_driver_major(vdc->dip),
VD_MAKE_DEV(vdc->instance, 0));
return (vd_process_ioctl(dev, cmd, (caddr_t)arg, FKIOCTL, &rval));
}
static int
vd_process_ioctl(dev_t dev, int cmd, caddr_t arg, int mode, int *rvalp)
{
int instance = VDCUNIT(dev);
vdc_t *vdc = NULL;
int rv = -1;
int idx = 0;
size_t len = 0;
size_t alloc_len = 0;
caddr_t mem_p = NULL;
size_t nioctls = (sizeof (dk_ioctl)) / (sizeof (dk_ioctl[0]));
vdc_dk_ioctl_t *iop;
vdc = ddi_get_soft_state(vdc_state, instance);
if (vdc == NULL) {
cmn_err(CE_NOTE, "![%d] Could not get soft state structure",
instance);
return (ENXIO);
}
DMSG(vdc, 0, "[%d] Processing ioctl(%x) for dev %lx : model %x\n",
instance, cmd, dev, ddi_model_convert_from(mode & FMODELS));
if (rvalp != NULL) {
*rvalp = 0;
}
for (idx = 0; idx < nioctls; idx++) {
if (cmd == dk_ioctl[idx].cmd)
break;
}
if (idx >= nioctls) {
DMSG(vdc, 0, "[%d] Unsupported ioctl (0x%x)\n",
vdc->instance, cmd);
return (ENOTSUP);
}
iop = &(dk_ioctl[idx]);
if (cmd == DKIOCGETEFI || cmd == DKIOCSETEFI) {
dk_efi_t dk_efi;
rv = ddi_copyin(arg, &dk_efi, sizeof (dk_efi_t), mode);
if (rv != 0)
return (EFAULT);
len = sizeof (vd_efi_t) - 1 + dk_efi.dki_length;
} else {
len = iop->nbytes;
}
switch (cmd) {
case CDROMREADOFFSET:
case DKIOCREMOVABLE:
return (ENOTTY);
case USCSICMD:
case MHIOCTKOWN:
case MHIOCSTATUS:
case MHIOCQRESERVE:
case MHIOCRELEASE:
case MHIOCGRP_INKEYS:
case MHIOCGRP_INRESV:
case MHIOCGRP_REGISTER:
case MHIOCGRP_RESERVE:
case MHIOCGRP_PREEMPTANDABORT:
case MHIOCGRP_REGISTERANDIGNOREKEY:
case MHIOCENFAILFAST:
if (vdc->cinfo == NULL)
return (ENXIO);
if (vdc->cinfo->dki_ctype != DKC_SCSI_CCS)
return (ENOTTY);
break;
case DIOCTL_RWCMD:
if (vdc->cinfo == NULL)
return (ENXIO);
if (vdc->cinfo->dki_ctype != DKC_DIRECT)
return (ENOTTY);
break;
case DKIOCINFO:
if (vdc->cinfo == NULL)
return (ENXIO);
break;
case DKIOCGMEDIAINFO:
if (vdc->minfo == NULL)
return (ENXIO);
if (vdc_check_capacity(vdc) != 0)
return (EIO);
break;
}
switch (cmd) {
case USCSICMD:
{
return (vdc_uscsi_cmd(vdc, arg, mode));
}
case MHIOCTKOWN:
{
mutex_enter(&vdc->ownership_lock);
vdc_ownership_update(vdc, VDC_OWNERSHIP_WANTED);
rv = vdc_access_set(vdc, VD_ACCESS_SET_EXCLUSIVE |
VD_ACCESS_SET_PREEMPT | VD_ACCESS_SET_PRESERVE);
if (rv == 0) {
vdc_ownership_update(vdc, VDC_OWNERSHIP_WANTED |
VDC_OWNERSHIP_GRANTED);
} else {
vdc_ownership_update(vdc, VDC_OWNERSHIP_NONE);
}
mutex_exit(&vdc->ownership_lock);
return (rv);
}
case MHIOCRELEASE:
{
mutex_enter(&vdc->ownership_lock);
rv = vdc_access_set(vdc, VD_ACCESS_SET_CLEAR);
if (rv == 0) {
vdc_ownership_update(vdc, VDC_OWNERSHIP_NONE);
}
mutex_exit(&vdc->ownership_lock);
return (rv);
}
case MHIOCSTATUS:
{
uint64_t status;
rv = vdc_access_get(vdc, &status);
if (rv == 0 && rvalp != NULL)
*rvalp = (status & VD_ACCESS_ALLOWED)? 0 : 1;
return (rv);
}
case MHIOCQRESERVE:
{
rv = vdc_access_set(vdc, VD_ACCESS_SET_EXCLUSIVE);
return (rv);
}
case MHIOCGRP_INKEYS:
{
return (vdc_mhd_inkeys(vdc, arg, mode));
}
case MHIOCGRP_INRESV:
{
return (vdc_mhd_inresv(vdc, arg, mode));
}
case MHIOCGRP_REGISTER:
{
return (vdc_mhd_register(vdc, arg, mode));
}
case MHIOCGRP_RESERVE:
{
return (vdc_mhd_reserve(vdc, arg, mode));
}
case MHIOCGRP_PREEMPTANDABORT:
{
return (vdc_mhd_preemptabort(vdc, arg, mode));
}
case MHIOCGRP_REGISTERANDIGNOREKEY:
{
return (vdc_mhd_registerignore(vdc, arg, mode));
}
case MHIOCENFAILFAST:
{
rv = vdc_failfast(vdc, arg, mode);
return (rv);
}
case DIOCTL_RWCMD:
{
return (vdc_dioctl_rwcmd(vdc, arg, mode));
}
case DKIOCGAPART:
{
return (vdc_dkio_gapart(vdc, arg, mode));
}
case DKIOCPARTITION:
{
return (vdc_dkio_partition(vdc, arg, mode));
}
case DKIOCINFO:
{
struct dk_cinfo cinfo;
bcopy(vdc->cinfo, &cinfo, sizeof (struct dk_cinfo));
cinfo.dki_partition = VDCPART(dev);
rv = ddi_copyout(&cinfo, (void *)arg,
sizeof (struct dk_cinfo), mode);
if (rv != 0)
return (EFAULT);
return (0);
}
case DKIOCGMEDIAINFO:
{
ASSERT(vdc->vdisk_size != 0);
ASSERT(vdc->minfo->dki_capacity != 0);
rv = ddi_copyout(vdc->minfo, (void *)arg,
sizeof (struct dk_minfo), mode);
if (rv != 0)
return (EFAULT);
return (0);
}
case DKIOCFLUSHWRITECACHE:
{
struct dk_callback *dkc =
(struct dk_callback *)(uintptr_t)arg;
vdc_dk_arg_t *dkarg = NULL;
DMSG(vdc, 1, "[%d] Flush W$: mode %x\n",
instance, mode);
if (dkc == NULL)
break;
if ((mode & FKIOCTL) == 0)
return (ENOTSUP);
dkarg = kmem_zalloc(sizeof (vdc_dk_arg_t), KM_SLEEP);
dkarg->mode = mode;
dkarg->dev = dev;
bcopy(dkc, &dkarg->dkc, sizeof (*dkc));
mutex_enter(&vdc->lock);
vdc->dkio_flush_pending++;
dkarg->vdc = vdc;
mutex_exit(&vdc->lock);
rv = taskq_dispatch(system_taskq, vdc_dkio_flush_cb,
(void *)dkarg, DDI_SLEEP);
if (rv == TASKQID_INVALID) {
mutex_enter(&vdc->lock);
vdc->dkio_flush_pending--;
mutex_exit(&vdc->lock);
kmem_free(dkarg, sizeof (vdc_dk_arg_t));
return (ENOMEM);
}
return (0);
}
}
ASSERT(iop->op != 0);
if (VD_OP_SUPPORTED(vdc->operations, iop->op) == B_FALSE) {
DMSG(vdc, 0, "[%d] Unsupported VD_OP operation (0x%x)\n",
vdc->instance, iop->op);
return (ENOTSUP);
}
alloc_len = P2ROUNDUP(len, sizeof (uint64_t));
DMSG(vdc, 1, "[%d] struct size %ld alloc %ld\n",
instance, len, alloc_len);
if (alloc_len > 0)
mem_p = kmem_zalloc(alloc_len, KM_SLEEP);
ASSERT(iop->convert != NULL);
rv = (iop->convert)(vdc, arg, mem_p, mode, VD_COPYIN);
if (rv != 0) {
DMSG(vdc, 0, "[%d] convert func returned %d for ioctl 0x%x\n",
instance, rv, cmd);
if (mem_p != NULL)
kmem_free(mem_p, alloc_len);
return (rv);
}
rv = vdc_do_sync_op(vdc, iop->op, mem_p, alloc_len,
VDCPART(dev), 0, VIO_both_dir, B_TRUE);
if (rv != 0) {
DMSG(vdc, 0, "[%d] vds returned %d for ioctl 0x%x\n",
instance, rv, cmd);
if (mem_p != NULL)
kmem_free(mem_p, alloc_len);
return (rv);
}
rv = (iop->convert)(vdc, mem_p, arg, mode, VD_COPYOUT);
if (rv != 0) {
DMSG(vdc, 0, "[%d] convert func returned %d for ioctl 0x%x\n",
instance, rv, cmd);
if (mem_p != NULL)
kmem_free(mem_p, alloc_len);
return (rv);
}
if (mem_p != NULL)
kmem_free(mem_p, alloc_len);
return (rv);
}
static int
vdc_null_copy_func(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
_NOTE(ARGUNUSED(vdc))
_NOTE(ARGUNUSED(from))
_NOTE(ARGUNUSED(to))
_NOTE(ARGUNUSED(mode))
_NOTE(ARGUNUSED(dir))
return (0);
}
static int
vdc_get_wce_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir)
{
_NOTE(ARGUNUSED(vdc))
if (dir == VD_COPYIN)
return (0);
if (ddi_copyout(from, to, sizeof (int), mode) != 0)
return (EFAULT);
return (0);
}
static int
vdc_set_wce_convert(vdc_t *vdc, void *from, void *to,
int mode, int dir)
{
_NOTE(ARGUNUSED(vdc))
if (dir == VD_COPYOUT)
return (0);
if (ddi_copyin(from, to, sizeof (int), mode) != 0)
return (EFAULT);
return (0);
}
static int
vdc_get_vtoc_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
int i;
struct vtoc vtoc;
struct vtoc32 vtoc32;
struct extvtoc evtoc;
int rv;
if (dir != VD_COPYOUT)
return (0);
if ((from == NULL) || (to == NULL))
return (ENXIO);
if (vdc->vdisk_size > VD_OLDVTOC_LIMIT)
return (EOVERFLOW);
VD_VTOC2VTOC((vd_vtoc_t *)from, &evtoc);
for (i = 0; i < V_NUMPAR; i++) {
evtoc.timestamp[i] = vdc->vtoc->timestamp[i];
}
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
extvtoctovtoc32(evtoc, vtoc32);
rv = ddi_copyout(&vtoc32, to, sizeof (vtoc32), mode);
if (rv != 0)
rv = EFAULT;
} else {
extvtoctovtoc(evtoc, vtoc);
rv = ddi_copyout(&vtoc, to, sizeof (vtoc), mode);
if (rv != 0)
rv = EFAULT;
}
return (rv);
}
static int
vdc_set_vtoc_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
void *uvtoc;
struct vtoc vtoc;
struct vtoc32 vtoc32;
struct extvtoc evtoc;
int i, rv;
if ((from == NULL) || (to == NULL))
return (ENXIO);
if (vdc->vdisk_size > VD_OLDVTOC_LIMIT)
return (EOVERFLOW);
uvtoc = (dir == VD_COPYIN)? from : to;
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
rv = ddi_copyin(uvtoc, &vtoc32, sizeof (vtoc32), mode);
if (rv != 0)
return (EFAULT);
vtoc32toextvtoc(vtoc32, evtoc);
} else {
rv = ddi_copyin(uvtoc, &vtoc, sizeof (vtoc), mode);
if (rv != 0)
return (EFAULT);
vtoctoextvtoc(vtoc, evtoc);
}
if (dir == VD_COPYOUT) {
vdc_validate(vdc);
for (i = 0; i < V_NUMPAR; i++) {
vdc->vtoc->timestamp[i] = evtoc.timestamp[i];
}
} else {
VTOC2VD_VTOC(&evtoc, (vd_vtoc_t *)to);
}
return (0);
}
static int
vdc_get_extvtoc_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
int i, rv;
struct extvtoc evtoc;
if (dir != VD_COPYOUT)
return (0);
if ((from == NULL) || (to == NULL))
return (ENXIO);
VD_VTOC2VTOC((vd_vtoc_t *)from, &evtoc);
for (i = 0; i < V_NUMPAR; i++) {
evtoc.timestamp[i] = vdc->vtoc->timestamp[i];
}
rv = ddi_copyout(&evtoc, to, sizeof (struct extvtoc), mode);
if (rv != 0)
rv = EFAULT;
return (rv);
}
static int
vdc_set_extvtoc_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
void *uvtoc;
struct extvtoc evtoc;
int i, rv;
if ((from == NULL) || (to == NULL))
return (ENXIO);
uvtoc = (dir == VD_COPYIN)? from : to;
rv = ddi_copyin(uvtoc, &evtoc, sizeof (struct extvtoc), mode);
if (rv != 0)
return (EFAULT);
if (dir == VD_COPYOUT) {
vdc_validate(vdc);
for (i = 0; i < V_NUMPAR; i++) {
vdc->vtoc->timestamp[i] = evtoc.timestamp[i];
}
} else {
VTOC2VD_VTOC(&evtoc, (vd_vtoc_t *)to);
}
return (0);
}
static int
vdc_get_geom_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
_NOTE(ARGUNUSED(vdc))
struct dk_geom geom;
int copy_len = sizeof (struct dk_geom);
int rv = 0;
if (dir != VD_COPYOUT)
return (0);
if ((from == NULL) || (to == NULL))
return (ENXIO);
VD_GEOM2DK_GEOM((vd_geom_t *)from, &geom);
rv = ddi_copyout(&geom, to, copy_len, mode);
if (rv != 0)
rv = EFAULT;
return (rv);
}
static int
vdc_set_geom_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
_NOTE(ARGUNUSED(vdc))
vd_geom_t vdgeom;
void *tmp_mem = NULL;
int copy_len = sizeof (struct dk_geom);
int rv = 0;
if (dir != VD_COPYIN)
return (0);
if ((from == NULL) || (to == NULL))
return (ENXIO);
tmp_mem = kmem_alloc(copy_len, KM_SLEEP);
rv = ddi_copyin(from, tmp_mem, copy_len, mode);
if (rv != 0) {
kmem_free(tmp_mem, copy_len);
return (EFAULT);
}
DK_GEOM2VD_GEOM((struct dk_geom *)tmp_mem, &vdgeom);
bcopy(&vdgeom, to, sizeof (vdgeom));
kmem_free(tmp_mem, copy_len);
return (0);
}
static int
vdc_get_efi_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
_NOTE(ARGUNUSED(vdc))
vd_efi_t *vd_efi;
dk_efi_t dk_efi;
int rv = 0;
void *uaddr;
if ((from == NULL) || (to == NULL))
return (ENXIO);
if (dir == VD_COPYIN) {
vd_efi = (vd_efi_t *)to;
rv = ddi_copyin(from, &dk_efi, sizeof (dk_efi_t), mode);
if (rv != 0)
return (EFAULT);
vd_efi->lba = dk_efi.dki_lba;
vd_efi->length = dk_efi.dki_length;
bzero(vd_efi->data, vd_efi->length);
} else {
rv = ddi_copyin(to, &dk_efi, sizeof (dk_efi_t), mode);
if (rv != 0)
return (EFAULT);
uaddr = dk_efi.dki_data;
dk_efi.dki_data = kmem_alloc(dk_efi.dki_length, KM_SLEEP);
VD_EFI2DK_EFI((vd_efi_t *)from, &dk_efi);
rv = ddi_copyout(dk_efi.dki_data, uaddr, dk_efi.dki_length,
mode);
if (rv != 0)
return (EFAULT);
kmem_free(dk_efi.dki_data, dk_efi.dki_length);
}
return (0);
}
static int
vdc_set_efi_convert(vdc_t *vdc, void *from, void *to, int mode, int dir)
{
_NOTE(ARGUNUSED(vdc))
dk_efi_t dk_efi;
void *uaddr;
if (dir == VD_COPYOUT) {
vdc_validate(vdc);
return (0);
}
if ((from == NULL) || (to == NULL))
return (ENXIO);
if (ddi_copyin(from, &dk_efi, sizeof (dk_efi_t), mode) != 0)
return (EFAULT);
uaddr = dk_efi.dki_data;
dk_efi.dki_data = kmem_alloc(dk_efi.dki_length, KM_SLEEP);
if (ddi_copyin(uaddr, dk_efi.dki_data, dk_efi.dki_length, mode) != 0)
return (EFAULT);
DK_EFI2VD_EFI(&dk_efi, (vd_efi_t *)to);
kmem_free(dk_efi.dki_data, dk_efi.dki_length);
return (0);
}
static void
vdc_create_fake_geometry(vdc_t *vdc)
{
ASSERT(vdc != NULL);
ASSERT(vdc->max_xfer_sz != 0);
if (vdc->cinfo == NULL)
vdc->cinfo = kmem_zalloc(sizeof (struct dk_cinfo), KM_SLEEP);
(void) strcpy(vdc->cinfo->dki_cname, VDC_DRIVER_NAME);
(void) strcpy(vdc->cinfo->dki_dname, VDC_DRIVER_NAME);
vdc->cinfo->dki_maxtransfer = vdc->max_xfer_sz;
switch (vdc->vdisk_media) {
case VD_MEDIA_CD:
case VD_MEDIA_DVD:
vdc->cinfo->dki_ctype = DKC_CDROM;
break;
case VD_MEDIA_FIXED:
if (VD_OP_SUPPORTED(vdc->operations, VD_OP_SCSICMD))
vdc->cinfo->dki_ctype = DKC_SCSI_CCS;
else
vdc->cinfo->dki_ctype = DKC_DIRECT;
break;
default:
vdc->cinfo->dki_ctype = DKC_DIRECT;
break;
}
vdc->cinfo->dki_flags = DKI_FMTVOL;
vdc->cinfo->dki_cnum = 0;
vdc->cinfo->dki_addr = 0;
vdc->cinfo->dki_space = 0;
vdc->cinfo->dki_prio = 0;
vdc->cinfo->dki_vec = 0;
vdc->cinfo->dki_unit = vdc->instance;
vdc->cinfo->dki_slave = 0;
vdc->cinfo->dki_partition = 0;
if (vdc->minfo == NULL)
vdc->minfo = kmem_zalloc(sizeof (struct dk_minfo), KM_SLEEP);
if (vio_ver_is_supported(vdc->ver, 1, 1)) {
vdc->minfo->dki_media_type =
VD_MEDIATYPE2DK_MEDIATYPE(vdc->vdisk_media);
} else {
vdc->minfo->dki_media_type = DK_FIXED_DISK;
}
vdc->minfo->dki_capacity = vdc->vdisk_size;
vdc->minfo->dki_lbsize = vdc->vdisk_bsize;
}
static ushort_t
vdc_lbl2cksum(struct dk_label *label)
{
int count;
ushort_t sum, *sp;
count = (sizeof (struct dk_label)) / (sizeof (short)) - 1;
sp = (ushort_t *)label;
sum = 0;
while (count--) {
sum ^= *sp++;
}
return (sum);
}
static void
vdc_update_size(vdc_t *vdc, size_t dsk_size, size_t blk_size, size_t xfr_size)
{
vd_err_stats_t *stp;
ASSERT(MUTEX_HELD(&vdc->lock));
ASSERT(xfr_size != 0);
if (dsk_size == VD_SIZE_UNKNOWN || dsk_size == 0 ||
(blk_size == vdc->vdisk_bsize && dsk_size == vdc->vdisk_size &&
xfr_size == vdc->max_xfer_sz))
return;
if ((xfr_size * blk_size) > (PAGESIZE * DEV_BSIZE)) {
DMSG(vdc, 0, "[%d] vds block transfer size too big;"
" using max supported by vdc", vdc->instance);
xfr_size = maxphys / blk_size;
}
vdc->max_xfer_sz = xfr_size;
vdc->vdisk_bsize = blk_size;
vdc->vdisk_size = dsk_size;
stp = (vd_err_stats_t *)vdc->err_stats->ks_data;
stp->vd_capacity.value.ui64 = dsk_size * blk_size;
vdc->minfo->dki_capacity = dsk_size;
vdc->minfo->dki_lbsize = (uint_t)blk_size;
}
static int
vdc_update_vio_bsize(vdc_t *vdc, uint32_t blk_size)
{
uint32_t ratio, n;
int nshift = 0;
vdc->vio_bmask = 0;
vdc->vio_bshift = 0;
ASSERT(blk_size > 0);
if ((blk_size % DEV_BSIZE) != 0)
return (EINVAL);
ratio = blk_size / DEV_BSIZE;
for (n = ratio; n > 1; n >>= 1) {
if ((n & 0x1) != 0) {
return (EINVAL);
}
nshift++;
}
vdc->vio_bshift = nshift;
vdc->vio_bmask = ratio - 1;
return (0);
}
static int
vdc_validate_geometry(vdc_t *vdc)
{
dev_t dev;
int rv, rval;
struct dk_label *label;
struct dk_geom geom;
struct extvtoc vtoc;
efi_gpt_t *gpt;
efi_gpe_t *gpe;
vd_efi_dev_t edev;
ASSERT(vdc != NULL);
ASSERT(vdc->vtoc != NULL && vdc->geom != NULL);
ASSERT(MUTEX_HELD(&vdc->lock));
mutex_exit(&vdc->lock);
(void) vdc_check_capacity(vdc);
dev = makedevice(ddi_driver_major(vdc->dip),
VD_MAKE_DEV(vdc->instance, 0));
rv = vd_process_ioctl(dev, DKIOCGGEOM, (caddr_t)&geom, FKIOCTL, &rval);
if (rv == 0)
rv = vd_process_ioctl(dev, DKIOCGEXTVTOC, (caddr_t)&vtoc,
FKIOCTL, &rval);
if (rv == ENOTSUP) {
if (vdc->vdisk_size == 0) {
mutex_enter(&vdc->lock);
vdc_store_label_unk(vdc);
return (EIO);
}
VDC_EFI_DEV_SET(edev, vdc, vd_process_efi_ioctl);
rv = vd_efi_alloc_and_read(&edev, &gpt, &gpe);
if (rv) {
DMSG(vdc, 0, "[%d] Failed to get EFI (err=%d)",
vdc->instance, rv);
mutex_enter(&vdc->lock);
vdc_store_label_unk(vdc);
return (EIO);
}
mutex_enter(&vdc->lock);
vdc_store_label_efi(vdc, gpt, gpe);
vd_efi_free(&edev, gpt, gpe);
return (ENOTSUP);
}
if (rv != 0) {
DMSG(vdc, 0, "[%d] Failed to get VTOC (err=%d)",
vdc->instance, rv);
mutex_enter(&vdc->lock);
vdc_store_label_unk(vdc);
if (rv != EINVAL)
rv = EIO;
return (rv);
}
if (geom.dkg_nhead == 0 || geom.dkg_nsect == 0 ||
vtoc.v_sanity != VTOC_SANE) {
mutex_enter(&vdc->lock);
vdc_store_label_unk(vdc);
return (EINVAL);
}
if (vdc->vdisk_type == VD_DISK_TYPE_SLICE) {
mutex_enter(&vdc->lock);
if (vtoc.v_nparts != 1) {
vdc_store_label_unk(vdc);
return (EINVAL);
}
vdc_store_label_vtoc(vdc, &geom, &vtoc);
return (0);
}
if (vtoc.v_nparts != V_NUMPAR) {
mutex_enter(&vdc->lock);
vdc_store_label_unk(vdc);
return (EINVAL);
}
if (vdc->vdisk_media == VD_MEDIA_CD ||
vdc->vdisk_media == VD_MEDIA_DVD) {
mutex_enter(&vdc->lock);
vdc_store_label_vtoc(vdc, &geom, &vtoc);
return (0);
}
label = kmem_alloc(vdc->vdisk_bsize, KM_SLEEP);
rv = vdc_do_op(vdc, VD_OP_BREAD, (caddr_t)label, vdc->vdisk_bsize,
VD_SLICE_NONE, 0, NULL, VIO_read_dir, VDC_OP_NORMAL);
if (rv != 0 || label->dkl_magic != DKL_MAGIC ||
label->dkl_cksum != vdc_lbl2cksum(label)) {
DMSG(vdc, 1, "[%d] Got VTOC with invalid label\n",
vdc->instance);
kmem_free(label, vdc->vdisk_bsize);
mutex_enter(&vdc->lock);
vdc_store_label_unk(vdc);
return (EINVAL);
}
kmem_free(label, vdc->vdisk_bsize);
mutex_enter(&vdc->lock);
vdc_store_label_vtoc(vdc, &geom, &vtoc);
return (0);
}
static void
vdc_validate(vdc_t *vdc)
{
vd_disk_label_t old_label;
vd_slice_t old_slice[V_NUMPAR];
int rv;
ASSERT(!MUTEX_HELD(&vdc->lock));
mutex_enter(&vdc->lock);
old_label = vdc->vdisk_label;
bcopy(vdc->slice, &old_slice, sizeof (vd_slice_t) * V_NUMPAR);
(void) vdc_validate_geometry(vdc);
if (vdc->vdisk_type == VD_DISK_TYPE_DISK &&
vdc->vdisk_label != old_label) {
if (vdc->vdisk_label == VD_DISK_LABEL_EFI)
rv = vdc_create_device_nodes_efi(vdc);
else
rv = vdc_create_device_nodes_vtoc(vdc);
if (rv != 0) {
DMSG(vdc, 0, "![%d] Failed to update device nodes",
vdc->instance);
}
}
mutex_exit(&vdc->lock);
}
static void
vdc_validate_task(void *arg)
{
vdc_t *vdc = (vdc_t *)arg;
vdc_validate(vdc);
mutex_enter(&vdc->lock);
ASSERT(vdc->validate_pending > 0);
vdc->validate_pending--;
mutex_exit(&vdc->lock);
}
static int
vdc_setup_devid(vdc_t *vdc)
{
int rv;
vd_devid_t *vd_devid;
size_t bufsize, bufid_len;
ddi_devid_t vdisk_devid;
char *devid_str;
bufsize = P2ROUNDUP(VD_DEVID_SIZE(VD_DEVID_DEFAULT_LEN),
sizeof (uint64_t));
vd_devid = kmem_zalloc(bufsize, KM_SLEEP);
bufid_len = bufsize - sizeof (vd_efi_t) - 1;
rv = vdc_do_op(vdc, VD_OP_GET_DEVID, (caddr_t)vd_devid,
bufsize, 0, 0, NULL, VIO_both_dir, 0);
DMSG(vdc, 2, "do_op returned %d\n", rv);
if (rv) {
kmem_free(vd_devid, bufsize);
return (rv);
}
if (vd_devid->length > bufid_len) {
kmem_free(vd_devid, bufsize);
bufsize = P2ROUNDUP(VD_DEVID_SIZE(vd_devid->length),
sizeof (uint64_t));
vd_devid = kmem_zalloc(bufsize, KM_SLEEP);
bufid_len = bufsize - sizeof (vd_efi_t) - 1;
rv = vdc_do_sync_op(vdc, VD_OP_GET_DEVID, (caddr_t)vd_devid,
bufsize, 0, 0, VIO_both_dir, B_TRUE);
if (rv) {
kmem_free(vd_devid, bufsize);
return (rv);
}
}
DMSG(vdc, 2, ": devid length = %d\n", vd_devid->length);
if (ddi_devid_init(vdc->dip, DEVID_ENCAP, vd_devid->length,
vd_devid->id, &vdisk_devid) != DDI_SUCCESS) {
DMSG(vdc, 1, "[%d] Fail to created devid\n", vdc->instance);
kmem_free(vd_devid, bufsize);
return (1);
}
DEVID_FORMTYPE((impl_devid_t *)vdisk_devid, vd_devid->type);
ASSERT(ddi_devid_valid(vdisk_devid) == DDI_SUCCESS);
kmem_free(vd_devid, bufsize);
if (vdc->devid != NULL) {
if (ddi_devid_compare(vdisk_devid, vdc->devid) == 0) {
ddi_devid_free(vdisk_devid);
return (0);
}
cmn_err(CE_WARN, "vdisk@%d backend devid has changed",
vdc->instance);
devid_str = ddi_devid_str_encode(vdc->devid, NULL);
cmn_err(CE_CONT, "vdisk@%d backend initial devid: %s",
vdc->instance,
(devid_str)? devid_str : "<encoding error>");
if (devid_str)
ddi_devid_str_free(devid_str);
devid_str = ddi_devid_str_encode(vdisk_devid, NULL);
cmn_err(CE_CONT, "vdisk@%d backend current devid: %s",
vdc->instance,
(devid_str)? devid_str : "<encoding error>");
if (devid_str)
ddi_devid_str_free(devid_str);
ddi_devid_free(vdisk_devid);
return (1);
}
if (ddi_devid_register(vdc->dip, vdisk_devid) != DDI_SUCCESS) {
DMSG(vdc, 1, "[%d] Fail to register devid\n", vdc->instance);
ddi_devid_free(vdisk_devid);
return (1);
}
vdc->devid = vdisk_devid;
return (0);
}
static void
vdc_store_label_efi(vdc_t *vdc, efi_gpt_t *gpt, efi_gpe_t *gpe)
{
int i, nparts;
ASSERT(MUTEX_HELD(&vdc->lock));
vdc->vdisk_label = VD_DISK_LABEL_EFI;
bzero(vdc->vtoc, sizeof (struct extvtoc));
bzero(vdc->geom, sizeof (struct dk_geom));
bzero(vdc->slice, sizeof (vd_slice_t) * V_NUMPAR);
nparts = gpt->efi_gpt_NumberOfPartitionEntries;
for (i = 0; i < nparts && i < VD_EFI_WD_SLICE; i++) {
if (gpe[i].efi_gpe_StartingLBA == 0 &&
gpe[i].efi_gpe_EndingLBA == 0) {
continue;
}
vdc->slice[i].start = gpe[i].efi_gpe_StartingLBA;
vdc->slice[i].nblocks = gpe[i].efi_gpe_EndingLBA -
gpe[i].efi_gpe_StartingLBA + 1;
}
ASSERT(vdc->vdisk_size != 0);
vdc->slice[VD_EFI_WD_SLICE].start = 0;
vdc->slice[VD_EFI_WD_SLICE].nblocks = vdc->vdisk_size;
}
static void
vdc_store_label_vtoc(vdc_t *vdc, struct dk_geom *geom, struct extvtoc *vtoc)
{
int i;
ASSERT(MUTEX_HELD(&vdc->lock));
ASSERT(vdc->vdisk_bsize == vtoc->v_sectorsz);
vdc->vdisk_label = VD_DISK_LABEL_VTOC;
bcopy(vtoc, vdc->vtoc, sizeof (struct extvtoc));
bcopy(geom, vdc->geom, sizeof (struct dk_geom));
bzero(vdc->slice, sizeof (vd_slice_t) * V_NUMPAR);
for (i = 0; i < vtoc->v_nparts; i++) {
vdc->slice[i].start = vtoc->v_part[i].p_start;
vdc->slice[i].nblocks = vtoc->v_part[i].p_size;
}
}
static void
vdc_store_label_unk(vdc_t *vdc)
{
ASSERT(MUTEX_HELD(&vdc->lock));
vdc->vdisk_label = VD_DISK_LABEL_UNK;
bzero(vdc->vtoc, sizeof (struct extvtoc));
bzero(vdc->geom, sizeof (struct dk_geom));
bzero(vdc->slice, sizeof (vd_slice_t) * V_NUMPAR);
}