#include <sys/mutex.h>
#include <sys/debug.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/thread.h>
#include <sys/id_space.h>
#include <sys/avl.h>
#include <sys/list.h>
#include <sys/sysmacros.h>
#include <sys/proc.h>
#include <sys/contract.h>
#include <sys/contract_impl.h>
#include <sys/contract/device.h>
#include <sys/contract/device_impl.h>
#include <sys/cmn_err.h>
#include <sys/nvpair.h>
#include <sys/policy.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_implfuncs.h>
#include <sys/systm.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/esunddi.h>
#include <sys/ddi.h>
#include <sys/fs/dv_node.h>
#include <sys/sunndi.h>
#undef ct_lock
static cont_device_t *contract_device_create(ctmpl_device_t *dtmpl, dev_t dev,
int spec_type, proc_t *owner, int *errorp);
static void ct_barrier_acquire(dev_info_t *dip);
static void ct_barrier_release(dev_info_t *dip);
static int ct_barrier_held(dev_info_t *dip);
static int ct_barrier_empty(dev_info_t *dip);
static void ct_barrier_wait_for_release(dev_info_t *dip);
static int ct_barrier_wait_for_empty(dev_info_t *dip, int secs);
static void ct_barrier_decr(dev_info_t *dip);
static void ct_barrier_incr(dev_info_t *dip);
ct_type_t *device_type;
#define EVSENDP(ctd, flag) \
((ctd->cond_contract.ct_ev_info | ctd->cond_contract.ct_ev_crit) & flag)
#define EVINFOP(ctd, flag) \
((ctd->cond_contract.ct_ev_crit & flag) == 0)
struct ct_dev_negtable {
uint_t st_old;
uint_t st_new;
uint_t st_neg;
} ct_dev_negtable[] = {
{CT_DEV_EV_ONLINE, CT_DEV_EV_OFFLINE, 1},
{CT_DEV_EV_ONLINE, CT_DEV_EV_DEGRADED, 0},
{CT_DEV_EV_DEGRADED, CT_DEV_EV_ONLINE, 0},
{CT_DEV_EV_DEGRADED, CT_DEV_EV_OFFLINE, 1},
{0}
};
static struct ct_template *
ctmpl_device_dup(struct ct_template *template)
{
ctmpl_device_t *new;
ctmpl_device_t *old = template->ctmpl_data;
char *buf;
char *minor;
new = kmem_zalloc(sizeof (ctmpl_device_t), KM_SLEEP);
buf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
ctmpl_copy(&new->ctd_ctmpl, template);
new->ctd_ctmpl.ctmpl_data = new;
new->ctd_aset = old->ctd_aset;
new->ctd_minor = NULL;
new->ctd_noneg = old->ctd_noneg;
if (old->ctd_minor) {
ASSERT(strlen(old->ctd_minor) + 1 <= MAXPATHLEN);
bcopy(old->ctd_minor, buf, strlen(old->ctd_minor) + 1);
} else {
kmem_free(buf, MAXPATHLEN);
buf = NULL;
}
mutex_exit(&template->ctmpl_lock);
if (buf) {
minor = i_ddi_strdup(buf, KM_SLEEP);
kmem_free(buf, MAXPATHLEN);
buf = NULL;
} else {
minor = NULL;
}
mutex_enter(&template->ctmpl_lock);
if (minor) {
new->ctd_minor = minor;
}
ASSERT(buf == NULL);
return (&new->ctd_ctmpl);
}
static void
ctmpl_device_free(struct ct_template *template)
{
ctmpl_device_t *dtmpl = template->ctmpl_data;
if (dtmpl->ctd_minor)
kmem_free(dtmpl->ctd_minor, strlen(dtmpl->ctd_minor) + 1);
kmem_free(dtmpl, sizeof (ctmpl_device_t));
}
#define SAFE_EV (CT_DEV_ALLEVENT)
#define EXCESS(value) ((value) & ~SAFE_EV)
static int
ctmpl_device_set(struct ct_template *tmpl, ct_kparam_t *kparam,
const cred_t *cr)
{
ctmpl_device_t *dtmpl = tmpl->ctmpl_data;
ct_param_t *param = &kparam->param;
int error;
dev_info_t *dip;
int spec_type;
uint64_t param_value;
char *str_value;
ASSERT(MUTEX_HELD(&tmpl->ctmpl_lock));
param_value = SAFE_EV;
if (param->ctpm_id == CTDP_MINOR) {
str_value = (char *)kparam->ctpm_kbuf;
str_value[param->ctpm_size - 1] = '\0';
} else {
if (param->ctpm_size < sizeof (uint64_t))
return (EINVAL);
param_value = *(uint64_t *)kparam->ctpm_kbuf;
}
switch (param->ctpm_id) {
case CTDP_ACCEPT:
if (param_value & ~CT_DEV_ALLEVENT)
return (EINVAL);
if (param_value == 0)
return (EINVAL);
if (param_value == CT_DEV_ALLEVENT)
return (EINVAL);
dtmpl->ctd_aset = param_value;
break;
case CTDP_NONEG:
if (param_value != CTDP_NONEG_SET &&
param_value != CTDP_NONEG_CLEAR)
return (EINVAL);
if (param_value == CTDP_NONEG_SET &&
(error = secpolicy_sys_devices(cr)) != 0) {
return (error);
}
dtmpl->ctd_noneg = param_value;
break;
case CTDP_MINOR:
if (*str_value != '/' ||
strncmp(str_value, "/devices/",
strlen("/devices/")) == 0 ||
strstr(str_value, "../devices/") != NULL ||
strchr(str_value, ':') == NULL) {
return (EINVAL);
}
spec_type = 0;
dip = NULL;
if (resolve_pathname(str_value, &dip, NULL, &spec_type) != 0) {
return (ERANGE);
}
ddi_release_devi(dip);
if (spec_type != S_IFCHR && spec_type != S_IFBLK) {
return (EINVAL);
}
if (dtmpl->ctd_minor != NULL) {
kmem_free(dtmpl->ctd_minor,
strlen(dtmpl->ctd_minor) + 1);
}
dtmpl->ctd_minor = i_ddi_strdup(str_value, KM_SLEEP);
break;
case CTP_EV_CRITICAL:
if (EXCESS(param_value) &&
(error = secpolicy_contract_event(cr)) != 0)
return (error);
tmpl->ctmpl_ev_crit = param_value;
break;
default:
return (EINVAL);
}
return (0);
}
static int
ctmpl_device_get(struct ct_template *template, ct_kparam_t *kparam)
{
ctmpl_device_t *dtmpl = template->ctmpl_data;
ct_param_t *param = &kparam->param;
uint64_t *param_value = kparam->ctpm_kbuf;
ASSERT(MUTEX_HELD(&template->ctmpl_lock));
if (param->ctpm_id == CTDP_ACCEPT ||
param->ctpm_id == CTDP_NONEG) {
if (param->ctpm_size < sizeof (uint64_t))
return (EINVAL);
kparam->ret_size = sizeof (uint64_t);
}
switch (param->ctpm_id) {
case CTDP_ACCEPT:
*param_value = dtmpl->ctd_aset;
break;
case CTDP_NONEG:
*param_value = dtmpl->ctd_noneg;
break;
case CTDP_MINOR:
if (dtmpl->ctd_minor) {
kparam->ret_size = strlcpy((char *)kparam->ctpm_kbuf,
dtmpl->ctd_minor, param->ctpm_size);
kparam->ret_size++;
} else {
return (ENOENT);
}
break;
default:
return (EINVAL);
}
return (0);
}
int
ctmpl_device_create(ct_template_t *template, ctid_t *ctidp)
{
ctmpl_device_t *dtmpl;
char *buf;
dev_t dev;
int spec_type;
int error;
cont_device_t *ctd;
if (ctidp == NULL)
return (EINVAL);
buf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
dtmpl = template->ctmpl_data;
mutex_enter(&template->ctmpl_lock);
if (dtmpl->ctd_minor == NULL) {
mutex_exit(&template->ctmpl_lock);
kmem_free(buf, MAXPATHLEN);
return (EINVAL);
} else {
ASSERT(strlen(dtmpl->ctd_minor) < MAXPATHLEN);
bcopy(dtmpl->ctd_minor, buf, strlen(dtmpl->ctd_minor) + 1);
}
mutex_exit(&template->ctmpl_lock);
spec_type = 0;
dev = NODEV;
if (resolve_pathname(buf, NULL, &dev, &spec_type) != 0 ||
dev == NODEV || dev == DDI_DEV_T_ANY || dev == DDI_DEV_T_NONE ||
(spec_type != S_IFCHR && spec_type != S_IFBLK)) {
CT_DEBUG((CE_WARN,
"tmpl_create: failed to find device: %s", buf));
kmem_free(buf, MAXPATHLEN);
return (ERANGE);
}
kmem_free(buf, MAXPATHLEN);
ctd = contract_device_create(template->ctmpl_data,
dev, spec_type, curproc, &error);
if (ctd == NULL) {
CT_DEBUG((CE_WARN, "Failed to create device contract for "
"process (%d) with device (devt = %lu, spec_type = %s)",
curproc->p_pid, dev,
spec_type == S_IFCHR ? "S_IFCHR" : "S_IFBLK"));
return (error);
}
mutex_enter(&ctd->cond_contract.ct_lock);
*ctidp = ctd->cond_contract.ct_id;
mutex_exit(&ctd->cond_contract.ct_lock);
return (0);
}
static ctmplops_t ctmpl_device_ops = {
ctmpl_device_dup,
ctmpl_device_free,
ctmpl_device_set,
ctmpl_device_get,
ctmpl_device_create,
CT_DEV_ALLEVENT
};
static ct_template_t *
contract_device_default(void)
{
ctmpl_device_t *new;
new = kmem_zalloc(sizeof (ctmpl_device_t), KM_SLEEP);
ctmpl_init(&new->ctd_ctmpl, &ctmpl_device_ops, device_type, new);
new->ctd_aset = CT_DEV_EV_ONLINE | CT_DEV_EV_DEGRADED;
new->ctd_noneg = 0;
new->ctd_ctmpl.ctmpl_ev_info = CT_DEV_EV_DEGRADED;
new->ctd_ctmpl.ctmpl_ev_crit = CT_DEV_EV_OFFLINE;
return (&new->ctd_ctmpl);
}
static void
contract_device_free(contract_t *ct)
{
cont_device_t *ctd = ct->ct_data;
ASSERT(ctd->cond_minor);
ASSERT(strlen(ctd->cond_minor) < MAXPATHLEN);
kmem_free(ctd->cond_minor, strlen(ctd->cond_minor) + 1);
ASSERT(ctd->cond_devt != DDI_DEV_T_ANY &&
ctd->cond_devt != DDI_DEV_T_NONE && ctd->cond_devt != NODEV);
ASSERT(ctd->cond_spec == S_IFBLK || ctd->cond_spec == S_IFCHR);
ASSERT(!(ctd->cond_aset & ~CT_DEV_ALLEVENT));
ASSERT(ctd->cond_noneg == 0 || ctd->cond_noneg == 1);
ASSERT(!(ctd->cond_currev_type & ~CT_DEV_ALLEVENT));
ASSERT(!(ctd->cond_currev_ack & ~(CT_ACK | CT_NACK)));
ASSERT((ctd->cond_currev_id > 0) ^ (ctd->cond_currev_type == 0));
ASSERT((ctd->cond_currev_id > 0) || (ctd->cond_currev_ack == 0));
ASSERT(!list_link_active(&ctd->cond_next));
kmem_free(ctd, sizeof (cont_device_t));
}
static void
contract_device_abandon(contract_t *ct)
{
ASSERT(MUTEX_HELD(&ct->ct_lock));
contract_destroy(ct);
}
static void
contract_device_destroy(contract_t *ct)
{
cont_device_t *ctd;
dev_info_t *dip;
ASSERT(MUTEX_HELD(&ct->ct_lock));
for (;;) {
ctd = ct->ct_data;
dip = ctd->cond_dip;
if (dip == NULL) {
ASSERT(!list_link_active(&ctd->cond_next));
CT_DEBUG((CE_NOTE, "contract_device_destroy:"
" contract has no devinfo node. contract ctid : %d",
ct->ct_id));
return;
}
if (mutex_tryenter(&(DEVI(dip)->devi_ct_lock)) != 0)
break;
mutex_exit(&ct->ct_lock);
delay(drv_usectohz(1000));
mutex_enter(&ct->ct_lock);
}
mutex_exit(&ct->ct_lock);
ct_barrier_wait_for_release(dip);
mutex_enter(&ct->ct_lock);
list_remove(&(DEVI(dip)->devi_ct), ctd);
ctd->cond_dip = NULL;
contract_rele(ct);
mutex_exit(&ct->ct_lock);
mutex_exit(&(DEVI(dip)->devi_ct_lock));
mutex_enter(&ct->ct_lock);
}
static void
contract_device_status(contract_t *ct, zone_t *zone, int detail, nvlist_t *nvl,
void *status, model_t model)
{
cont_device_t *ctd = ct->ct_data;
ASSERT(detail == CTD_FIXED || detail == CTD_ALL);
mutex_enter(&ct->ct_lock);
contract_status_common(ct, zone, status, model);
VERIFY(nvlist_add_uint32(nvl, CTDS_STATE, ctd->cond_state) == 0);
VERIFY(nvlist_add_uint32(nvl, CTDS_ASET, ctd->cond_aset) == 0);
VERIFY(nvlist_add_uint32(nvl, CTDS_NONEG, ctd->cond_noneg) == 0);
if (detail == CTD_FIXED) {
mutex_exit(&ct->ct_lock);
return;
}
ASSERT(ctd->cond_minor);
VERIFY(nvlist_add_string(nvl, CTDS_MINOR, ctd->cond_minor) == 0);
mutex_exit(&ct->ct_lock);
}
static char *
result_str(uint_t result)
{
switch (result) {
case CT_ACK:
return ("CT_ACK");
case CT_NACK:
return ("CT_NACK");
case CT_NONE:
return ("CT_NONE");
default:
return ("UNKNOWN");
}
}
static char *
state_str(uint_t state)
{
switch (state) {
case CT_DEV_EV_ONLINE:
return ("ONLINE");
case CT_DEV_EV_DEGRADED:
return ("DEGRADED");
case CT_DEV_EV_OFFLINE:
return ("OFFLINE");
default:
return ("UNKNOWN");
}
}
static int
is_sync_neg(uint_t old, uint_t new)
{
int i;
ASSERT(old & CT_DEV_ALLEVENT);
ASSERT(new & CT_DEV_ALLEVENT);
if (old == new) {
CT_DEBUG((CE_WARN, "is_sync_neg: transition to same state: %s",
state_str(new)));
return (-2);
}
for (i = 0; ct_dev_negtable[i].st_new != 0; i++) {
if (old == ct_dev_negtable[i].st_old &&
new == ct_dev_negtable[i].st_new) {
return (ct_dev_negtable[i].st_neg);
}
}
CT_DEBUG((CE_WARN, "is_sync_neg: Unsupported state transition: "
"old = %s -> new = %s", state_str(old), state_str(new)));
return (-1);
}
static int
contract_device_dvclean(dev_info_t *dip)
{
char *devnm;
dev_info_t *pdip;
ASSERT(dip);
pdip = ddi_get_parent(dip);
if (pdip && DEVI_BUSY_OWNED(pdip) || !pdip && DEVI_BUSY_OWNED(dip)) {
char *path;
path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
(void) ddi_pathname(dip, path);
CT_DEBUG((CE_WARN, "ct_dv_clean: Parent node is busy owned, "
"device=%s", path));
kmem_free(path, MAXPATHLEN);
return (EDEADLOCK);
}
if (pdip) {
devnm = kmem_alloc(MAXNAMELEN + 1, KM_SLEEP);
(void) ddi_deviname(dip, devnm);
(void) devfs_clean(pdip, devnm + 1, DV_CLEAN_FORCE);
kmem_free(devnm, MAXNAMELEN + 1);
} else {
(void) devfs_clean(dip, NULL, DV_CLEAN_FORCE);
}
return (0);
}
static int
contract_device_ack_nack(contract_t *ct, uint_t evtype, uint64_t evid,
uint_t cmd)
{
cont_device_t *ctd = ct->ct_data;
dev_info_t *dip;
ctid_t ctid;
int error;
ctid = ct->ct_id;
CT_DEBUG((CE_NOTE, "ack_nack: entered: ctid %d", ctid));
mutex_enter(&ct->ct_lock);
CT_DEBUG((CE_NOTE, "ack_nack: contract lock acquired: %d", ctid));
dip = ctd->cond_dip;
ASSERT(ctd->cond_minor);
ASSERT(strlen(ctd->cond_minor) < MAXPATHLEN);
ASSERT(!(ctd->cond_aset & evtype));
ASSERT(is_sync_neg(ctd->cond_state, evtype));
ASSERT(!ctd->cond_noneg);
if (dip)
ndi_hold_devi(dip);
mutex_exit(&ct->ct_lock);
if (cmd != CT_NACK && evtype == CT_DEV_EV_OFFLINE && dip) {
CT_DEBUG((CE_NOTE, "ack_nack: dv_clean: %d", ctid));
error = contract_device_dvclean(dip);
if (error != 0) {
CT_DEBUG((CE_NOTE, "ack_nack: dv_clean: failed: %d",
ctid));
ddi_release_devi(dip);
}
}
mutex_enter(&ct->ct_lock);
if (dip)
ddi_release_devi(dip);
if (dip == NULL) {
if (ctd->cond_currev_id != evid) {
CT_DEBUG((CE_WARN, "%sACK for non-current event "
"(type=%s, id=%llu) on removed device",
cmd == CT_NACK ? "N" : "",
state_str(evtype), (unsigned long long)evid));
CT_DEBUG((CE_NOTE, "ack_nack: error: ESRCH, ctid: %d",
ctid));
} else {
ASSERT(ctd->cond_currev_type == evtype);
CT_DEBUG((CE_WARN, "contract_ack: no such device: "
"ctid: %d", ctid));
}
error = (ct->ct_state == CTS_DEAD) ? ESRCH :
((cmd == CT_NACK) ? ETIMEDOUT : 0);
mutex_exit(&ct->ct_lock);
return (error);
}
mutex_exit(&ct->ct_lock);
mutex_enter(&DEVI(dip)->devi_ct_lock);
mutex_enter(&ct->ct_lock);
if (ctd->cond_currev_id != evid) {
char *buf;
mutex_exit(&ct->ct_lock);
mutex_exit(&DEVI(dip)->devi_ct_lock);
ndi_hold_devi(dip);
buf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
(void) ddi_pathname(dip, buf);
ddi_release_devi(dip);
CT_DEBUG((CE_WARN, "%sACK for non-current event"
"(type=%s, id=%llu) on device %s",
cmd == CT_NACK ? "N" : "",
state_str(evtype), (unsigned long long)evid, buf));
kmem_free(buf, MAXPATHLEN);
CT_DEBUG((CE_NOTE, "ack_nack: error: %d, ctid: %d",
cmd == CT_NACK ? ETIMEDOUT : 0, ctid));
return (cmd == CT_ACK ? 0 : ETIMEDOUT);
}
ASSERT(ctd->cond_currev_type == evtype);
ASSERT(cmd == CT_ACK || cmd == CT_NACK);
CT_DEBUG((CE_NOTE, "ack_nack: setting %sACK for ctid: %d",
cmd == CT_NACK ? "N" : "", ctid));
ctd->cond_currev_ack = cmd;
mutex_exit(&ct->ct_lock);
ct_barrier_decr(dip);
mutex_exit(&DEVI(dip)->devi_ct_lock);
CT_DEBUG((CE_NOTE, "ack_nack: normal exit: ctid: %d", ctid));
return (0);
}
static int
contract_device_ack(contract_t *ct, uint_t evtype, uint64_t evid)
{
return (contract_device_ack_nack(ct, evtype, evid, CT_ACK));
}
static int
contract_device_nack(contract_t *ct, uint_t evtype, uint64_t evid)
{
return (contract_device_ack_nack(ct, evtype, evid, CT_NACK));
}
static int
contract_device_newct(contract_t *ct)
{
return (ENOTSUP);
}
static contops_t contract_device_ops = {
contract_device_free,
contract_device_abandon,
contract_device_destroy,
contract_device_status,
contract_device_ack,
contract_device_nack,
contract_qack_notsup,
contract_device_newct
};
void
contract_device_init(void)
{
device_type = contract_type_init(CTT_DEVICE, "device",
&contract_device_ops, contract_device_default);
}
static cont_device_t *
contract_device_create(ctmpl_device_t *dtmpl, dev_t dev, int spec_type,
proc_t *owner, int *errorp)
{
cont_device_t *ctd;
char *minor;
char *path;
dev_info_t *dip;
ASSERT(dtmpl != NULL);
ASSERT(dev != NODEV && dev != DDI_DEV_T_ANY && dev != DDI_DEV_T_NONE);
ASSERT(spec_type == S_IFCHR || spec_type == S_IFBLK);
ASSERT(errorp);
*errorp = 0;
path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
mutex_enter(&dtmpl->ctd_ctmpl.ctmpl_lock);
ASSERT(strlen(dtmpl->ctd_minor) < MAXPATHLEN);
bcopy(dtmpl->ctd_minor, path, strlen(dtmpl->ctd_minor) + 1);
mutex_exit(&dtmpl->ctd_ctmpl.ctmpl_lock);
dip = e_ddi_hold_devi_by_path(path, 0);
if (dip == NULL) {
cmn_err(CE_WARN, "contract_create: Cannot find devinfo node "
"for device path (%s)", path);
kmem_free(path, MAXPATHLEN);
*errorp = ERANGE;
return (NULL);
}
mutex_enter(&(DEVI(dip)->devi_ct_lock));
ct_barrier_acquire(dip);
mutex_exit(&(DEVI(dip)->devi_ct_lock));
minor = i_ddi_strdup(path, KM_SLEEP);
kmem_free(path, MAXPATHLEN);
(void) contract_type_pbundle(device_type, owner);
ctd = kmem_zalloc(sizeof (cont_device_t), KM_SLEEP);
ctd->cond_minor = minor;
ctd->cond_dip = dip;
ctd->cond_devt = dev;
ctd->cond_spec = spec_type;
ctd->cond_state = DEVI_IS_DEVICE_DEGRADED(dip) ?
CT_DEV_EV_DEGRADED : CT_DEV_EV_ONLINE;
mutex_enter(&dtmpl->ctd_ctmpl.ctmpl_lock);
ctd->cond_aset = dtmpl->ctd_aset;
ctd->cond_noneg = dtmpl->ctd_noneg;
if (contract_ctor(&ctd->cond_contract, device_type, &dtmpl->ctd_ctmpl,
ctd, 0, owner, B_TRUE)) {
mutex_exit(&dtmpl->ctd_ctmpl.ctmpl_lock);
contract_device_free(&ctd->cond_contract);
mutex_enter(&(DEVI(dip)->devi_ct_lock));
ct_barrier_release(dip);
mutex_exit(&(DEVI(dip)->devi_ct_lock));
ddi_release_devi(dip);
*errorp = EAGAIN;
return (NULL);
}
mutex_exit(&dtmpl->ctd_ctmpl.ctmpl_lock);
mutex_enter(&ctd->cond_contract.ct_lock);
ctd->cond_contract.ct_ntime.ctm_total = CT_DEV_ACKTIME;
ctd->cond_contract.ct_qtime.ctm_total = CT_DEV_ACKTIME;
ctd->cond_contract.ct_ntime.ctm_start = -1;
ctd->cond_contract.ct_qtime.ctm_start = -1;
mutex_exit(&ctd->cond_contract.ct_lock);
contract_hold(&ctd->cond_contract);
mutex_enter(&(DEVI(dip)->devi_ct_lock));
list_insert_tail(&(DEVI(dip)->devi_ct), ctd);
ct_barrier_release(dip);
mutex_exit(&(DEVI(dip)->devi_ct_lock));
ddi_release_devi(dip);
return (ctd);
}
int
contract_device_open(dev_t dev, int spec_type, contract_t **ctpp)
{
ctmpl_device_t *dtmpl;
ct_template_t *tmpl;
cont_device_t *ctd;
char *path;
klwp_t *lwp;
int error;
if (ctpp)
*ctpp = NULL;
lwp = ttolwp(curthread);
if (lwp == NULL) {
CT_DEBUG((CE_NOTE, "contract_open: Not user-context"));
return (0);
}
tmpl = ctmpl_dup(lwp->lwp_ct_active[device_type->ct_type_index]);
if (tmpl == NULL) {
return (0);
}
dtmpl = tmpl->ctmpl_data;
mutex_enter(&tmpl->ctmpl_lock);
if (dtmpl->ctd_minor != NULL) {
CT_DEBUG((CE_NOTE, "contract_device_open(): Process %d: "
"ignoring device minor path in active template: %s",
curproc->p_pid, dtmpl->ctd_minor));
kmem_free(dtmpl->ctd_minor, strlen(dtmpl->ctd_minor) + 1);
dtmpl->ctd_minor = NULL;
}
mutex_exit(&tmpl->ctmpl_lock);
path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
if (ddi_dev_pathname(dev, spec_type, path) != DDI_SUCCESS) {
CT_DEBUG((CE_NOTE, "contract_device_open(): Failed to derive "
"minor path from dev_t,spec {%lu, %d} for process (%d)",
dev, spec_type, curproc->p_pid));
ctmpl_free(tmpl);
kmem_free(path, MAXPATHLEN);
return (1);
}
mutex_enter(&tmpl->ctmpl_lock);
ASSERT(dtmpl->ctd_minor == NULL);
dtmpl->ctd_minor = path;
mutex_exit(&tmpl->ctmpl_lock);
ctd = contract_device_create(dtmpl, dev, spec_type, curproc, &error);
mutex_enter(&tmpl->ctmpl_lock);
ASSERT(dtmpl->ctd_minor);
dtmpl->ctd_minor = NULL;
mutex_exit(&tmpl->ctmpl_lock);
ctmpl_free(tmpl);
kmem_free(path, MAXPATHLEN);
if (ctd == NULL) {
cmn_err(CE_NOTE, "contract_device_open(): Failed to "
"create device contract for process (%d) holding "
"device (devt = %lu, spec_type = %d)",
curproc->p_pid, dev, spec_type);
return (1);
}
if (ctpp) {
mutex_enter(&ctd->cond_contract.ct_lock);
*ctpp = &ctd->cond_contract;
mutex_exit(&ctd->cond_contract.ct_lock);
}
return (0);
}
static uint_t
wait_for_acks(dev_info_t *dip, dev_t dev, int spec_type, uint_t evtype)
{
cont_device_t *ctd;
int timed_out = 0;
int result = CT_NONE;
int ack;
char *f = "wait_for_acks";
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
ASSERT(dip);
ASSERT(evtype & CT_DEV_ALLEVENT);
ASSERT(dev != NODEV && dev != DDI_DEV_T_NONE);
ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) ||
(spec_type == S_IFBLK || spec_type == S_IFCHR));
CT_DEBUG((CE_NOTE, "%s: entered: dip: %p", f, (void *)dip));
if (ct_barrier_wait_for_empty(dip, CT_DEV_ACKTIME) == -1) {
CT_DEBUG((CE_NOTE, "%s: timed out: %p", f, (void *)dip));
timed_out = 1;
}
ack = 0;
for (ctd = list_head(&(DEVI(dip)->devi_ct)); ctd != NULL;
ctd = list_next(&(DEVI(dip)->devi_ct), ctd)) {
mutex_enter(&ctd->cond_contract.ct_lock);
ASSERT(ctd->cond_dip == dip);
if (dev != DDI_DEV_T_ANY && dev != ctd->cond_devt) {
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
if (dev != DDI_DEV_T_ANY && spec_type != ctd->cond_spec) {
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
if (ctd->cond_noneg) {
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
ASSERT(ctd->cond_currev_type == evtype);
if (ctd->cond_currev_ack == CT_NACK) {
CT_DEBUG((CE_NOTE, "%s: found a NACK,result = NACK: %p",
f, (void *)dip));
mutex_exit(&ctd->cond_contract.ct_lock);
return (CT_NACK);
} else if (ctd->cond_currev_ack == CT_ACK) {
ack = 1;
CT_DEBUG((CE_NOTE, "%s: found a ACK: %p",
f, (void *)dip));
}
mutex_exit(&ctd->cond_contract.ct_lock);
}
if (ack) {
result = CT_ACK;
CT_DEBUG((CE_NOTE, "%s: result = ACK, dip=%p", f, (void *)dip));
} else if (timed_out) {
result = CT_NONE;
CT_DEBUG((CE_NOTE, "%s: result = NONE (timed-out), dip=%p",
f, (void *)dip));
} else {
CT_DEBUG((CE_NOTE, "%s: result = NONE, dip=%p",
f, (void *)dip));
}
return (result);
}
static int
get_state(dev_info_t *dip)
{
if (DEVI_IS_DEVICE_OFFLINE(dip) || DEVI_IS_DEVICE_DOWN(dip))
return (CT_DEV_EV_OFFLINE);
else if (DEVI_IS_DEVICE_DEGRADED(dip))
return (CT_DEV_EV_DEGRADED);
else
return (CT_DEV_EV_ONLINE);
}
static void
set_cond_state(dev_info_t *dip)
{
uint_t state = get_state(dip);
cont_device_t *ctd;
ASSERT(ct_barrier_held(dip));
for (ctd = list_head(&(DEVI(dip)->devi_ct)); ctd != NULL;
ctd = list_next(&(DEVI(dip)->devi_ct), ctd)) {
mutex_enter(&ctd->cond_contract.ct_lock);
ASSERT(ctd->cond_dip == dip);
ctd->cond_state = state;
mutex_exit(&ctd->cond_contract.ct_lock);
}
}
static uint_t
contract_device_publish(dev_info_t *dip, dev_t dev, int spec_type,
uint_t evtype, nvlist_t *tnvl)
{
cont_device_t *ctd;
uint_t result = CT_NONE;
uint64_t evid = 0;
uint64_t nevid = 0;
char *path = NULL;
int negend;
int match;
int sync = 0;
contract_t *ct;
ct_kevent_t *event;
nvlist_t *nvl;
int broken = 0;
ASSERT(dip);
ASSERT(dev != NODEV && dev != DDI_DEV_T_NONE);
ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) ||
(spec_type == S_IFBLK || spec_type == S_IFCHR));
ASSERT(evtype == 0 || (evtype & CT_DEV_ALLEVENT));
if (evtype != CT_EV_NEGEND) {
sync = is_sync_neg(get_state(dip), evtype);
if (sync == -2 || sync == -1) {
DEVI(dip)->devi_flags |= DEVI_CT_NOP;
result = (sync == -2) ? CT_ACK : CT_NONE;
goto out;
}
CT_DEBUG((CE_NOTE, "publish: is%s sync state change",
sync ? "" : " not"));
} else if (DEVI(dip)->devi_flags & DEVI_CT_NOP) {
DEVI(dip)->devi_flags &= ~DEVI_CT_NOP;
result = CT_ACK;
goto out;
}
path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
(void) ddi_pathname(dip, path);
mutex_enter(&(DEVI(dip)->devi_ct_lock));
if (evtype == CT_EV_NEGEND) {
CT_DEBUG((CE_NOTE, "publish: negend: setting cond state"));
set_cond_state(dip);
}
negend = 0;
if (evtype == CT_EV_NEGEND && !DEVI(dip)->devi_ct_neg) {
CT_DEBUG((CE_NOTE, "publish: no negend reqd. release barrier"));
ct_barrier_release(dip);
mutex_exit(&(DEVI(dip)->devi_ct_lock));
result = CT_ACK;
goto out;
} else if (evtype == CT_EV_NEGEND) {
ASSERT(ct_barrier_held(dip));
negend = 1;
CT_DEBUG((CE_NOTE, "publish: setting negend flag"));
} else {
ct_barrier_acquire(dip);
}
match = 0;
for (ctd = list_head(&(DEVI(dip)->devi_ct)); ctd != NULL;
ctd = list_next(&(DEVI(dip)->devi_ct), ctd)) {
ctid_t ctid;
size_t len = strlen(path);
mutex_enter(&ctd->cond_contract.ct_lock);
ASSERT(ctd->cond_dip == dip);
ASSERT(ctd->cond_minor);
ASSERT(strncmp(ctd->cond_minor, path, len) == 0 &&
ctd->cond_minor[len] == ':');
if (dev != DDI_DEV_T_ANY && dev != ctd->cond_devt) {
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
if (dev != DDI_DEV_T_ANY && spec_type != ctd->cond_spec) {
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
match = 1;
ctid = ctd->cond_contract.ct_id;
CT_DEBUG((CE_NOTE, "publish: found matching contract: %d",
ctid));
broken = 0;
if (!negend && !(evtype & ctd->cond_aset)) {
broken = 1;
CT_DEBUG((CE_NOTE, "publish: Contract broken: %d",
ctid));
}
if (!broken && !EVSENDP(ctd, evtype) &&
!ctd->cond_neg) {
CT_DEBUG((CE_NOTE, "contract_device_publish(): "
"contract (%d): no publish reqd: event %d",
ctd->cond_contract.ct_id, evtype));
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
ct = &ctd->cond_contract;
event = kmem_zalloc(sizeof (ct_kevent_t), KM_SLEEP);
event->cte_type = evtype;
if (broken && sync) {
CT_DEBUG((CE_NOTE, "publish: broken + sync: "
"ctid: %d", ctid));
ASSERT(!negend);
ASSERT(ctd->cond_currev_id == 0);
ASSERT(ctd->cond_currev_type == 0);
ASSERT(ctd->cond_currev_ack == 0);
ASSERT(ctd->cond_neg == 0);
if (ctd->cond_noneg) {
CT_DEBUG((CE_NOTE, "publish: sync and noneg:"
"not publishing blocked ev: ctid: %d",
ctid));
result = CT_NACK;
kmem_free(event, sizeof (ct_kevent_t));
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
event->cte_flags = CTE_NEG;
ctd->cond_currev_type = event->cte_type;
ct_barrier_incr(dip);
DEVI(dip)->devi_ct_neg = 1;
ctd->cond_neg = 1;
} else if (broken && !sync) {
CT_DEBUG((CE_NOTE, "publish: broken + async: ctid: %d",
ctid));
ASSERT(!negend);
ASSERT(ctd->cond_currev_id == 0);
ASSERT(ctd->cond_currev_type == 0);
ASSERT(ctd->cond_currev_ack == 0);
ASSERT(ctd->cond_neg == 0);
event->cte_flags = 0;
} else if (EVSENDP(ctd, event->cte_type)) {
CT_DEBUG((CE_NOTE, "publish: event suscrib: ctid: %d",
ctid));
ASSERT(!negend);
ASSERT(ctd->cond_currev_id == 0);
ASSERT(ctd->cond_currev_type == 0);
ASSERT(ctd->cond_currev_ack == 0);
ASSERT(ctd->cond_neg == 0);
event->cte_flags = EVINFOP(ctd, event->cte_type) ?
CTE_INFO : 0;
} else if (ctd->cond_neg) {
CT_DEBUG((CE_NOTE, "publish: NEGEND: ctid: %d", ctid));
ASSERT(negend);
ASSERT(ctd->cond_noneg == 0);
nevid = ctd->cond_contract.ct_nevent ?
ctd->cond_contract.ct_nevent->cte_id : 0;
ASSERT(ctd->cond_currev_id == nevid);
event->cte_flags = 0;
ctd->cond_currev_id = 0;
ctd->cond_currev_type = 0;
ctd->cond_currev_ack = 0;
ctd->cond_neg = 0;
} else {
CT_DEBUG((CE_NOTE, "publish: not publishing event for "
"ctid: %d, evtype: %d",
ctd->cond_contract.ct_id, event->cte_type));
ASSERT(!negend);
ASSERT(ctd->cond_currev_id == 0);
ASSERT(ctd->cond_currev_type == 0);
ASSERT(ctd->cond_currev_ack == 0);
ASSERT(ctd->cond_neg == 0);
kmem_free(event, sizeof (ct_kevent_t));
mutex_exit(&ctd->cond_contract.ct_lock);
continue;
}
nvl = NULL;
if (tnvl) {
VERIFY(nvlist_dup(tnvl, &nvl, 0) == 0);
if (negend) {
int32_t newct = 0;
ASSERT(ctd->cond_noneg == 0);
VERIFY(nvlist_add_uint64(nvl, CTS_NEVID, nevid)
== 0);
VERIFY(nvlist_lookup_int32(nvl, CTS_NEWCT,
&newct) == 0);
VERIFY(nvlist_add_int32(nvl, CTS_NEWCT,
newct == 1 ? 0 :
ctd->cond_contract.ct_id) == 0);
CT_DEBUG((CE_NOTE, "publish: negend: ctid: %d "
"CTS_NEVID: %llu, CTS_NEWCT: %s",
ctid, (unsigned long long)nevid,
newct ? "success" : "failure"));
}
}
if (ctd->cond_neg) {
ASSERT(ctd->cond_contract.ct_ntime.ctm_start == -1);
ASSERT(ctd->cond_contract.ct_qtime.ctm_start == -1);
ctd->cond_contract.ct_ntime.ctm_start = ddi_get_lbolt();
ctd->cond_contract.ct_qtime.ctm_start =
ctd->cond_contract.ct_ntime.ctm_start;
}
mutex_exit(&ctd->cond_contract.ct_lock);
evid = cte_publish_all(ct, event, nvl, NULL);
mutex_enter(&ctd->cond_contract.ct_lock);
if (ctd->cond_neg) {
ASSERT(!negend);
ASSERT(broken);
ASSERT(sync);
ASSERT(!ctd->cond_noneg);
CT_DEBUG((CE_NOTE, "publish: sync break, setting evid"
": %d", ctid));
ctd->cond_currev_id = evid;
} else if (negend) {
ctd->cond_contract.ct_ntime.ctm_start = -1;
ctd->cond_contract.ct_qtime.ctm_start = -1;
}
mutex_exit(&ctd->cond_contract.ct_lock);
}
if (negend) {
ct_barrier_release(dip);
DEVI(dip)->devi_ct_neg = 0;
CT_DEBUG((CE_NOTE, "publish: negend: reset dip state: dip=%p",
(void *)dip));
} else if (DEVI(dip)->devi_ct_neg) {
ASSERT(match);
ASSERT(!ct_barrier_empty(dip));
CT_DEBUG((CE_NOTE, "publish: sync count=%d, dip=%p",
DEVI(dip)->devi_ct_count, (void *)dip));
} else {
ASSERT(ct_barrier_empty(dip));
ASSERT(DEVI(dip)->devi_ct_neg == 0);
CT_DEBUG((CE_NOTE, "publish: async/non-nego/subscrib/no-match: "
"dip=%p", (void *)dip));
}
if (!match) {
CT_DEBUG((CE_NOTE, "publish: No matching contract"));
result = CT_NONE;
} else if (result == CT_NACK) {
CT_DEBUG((CE_NOTE, "publish: found 1 or more NONEG contract"));
(void) wait_for_acks(dip, dev, spec_type, evtype);
} else if (DEVI(dip)->devi_ct_neg) {
CT_DEBUG((CE_NOTE, "publish: sync contract: waiting"));
result = wait_for_acks(dip, dev, spec_type, evtype);
} else {
CT_DEBUG((CE_NOTE, "publish: async/no-break/negend"));
result = CT_ACK;
}
CT_DEBUG((CE_NOTE, "publish: dropping devi_ct_lock"));
mutex_exit(&(DEVI(dip)->devi_ct_lock));
out:
nvlist_free(tnvl);
if (path)
kmem_free(path, MAXPATHLEN);
CT_DEBUG((CE_NOTE, "publish: result = %s", result_str(result)));
return (result);
}
ct_ack_t
contract_device_offline(dev_info_t *dip, dev_t dev, int spec_type)
{
nvlist_t *nvl;
uint_t result;
uint_t evtype;
VERIFY(nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP) == 0);
evtype = CT_DEV_EV_OFFLINE;
result = contract_device_publish(dip, dev, spec_type, evtype, nvl);
if (result == CT_NACK) {
contract_device_negend(dip, dev, spec_type, CT_EV_FAILURE);
}
return (result);
}
void
contract_device_degrade(dev_info_t *dip, dev_t dev, int spec_type)
{
nvlist_t *nvl;
uint_t evtype;
VERIFY(nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP) == 0);
evtype = CT_DEV_EV_DEGRADED;
(void) contract_device_publish(dip, dev, spec_type, evtype, nvl);
}
void
contract_device_undegrade(dev_info_t *dip, dev_t dev, int spec_type)
{
nvlist_t *nvl;
uint_t evtype;
VERIFY(nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP) == 0);
evtype = CT_DEV_EV_ONLINE;
(void) contract_device_publish(dip, dev, spec_type, evtype, nvl);
}
void
contract_device_negend(dev_info_t *dip, dev_t dev, int spec_type, int result)
{
nvlist_t *nvl;
uint_t evtype;
ASSERT(result == CT_EV_SUCCESS || result == CT_EV_FAILURE);
CT_DEBUG((CE_NOTE, "contract_device_negend(): entered: result: %d, "
"dip: %p", result, (void *)dip));
VERIFY(nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP) == 0);
VERIFY(nvlist_add_int32(nvl, CTS_NEWCT,
result == CT_EV_SUCCESS ? 1 : 0) == 0);
evtype = CT_EV_NEGEND;
(void) contract_device_publish(dip, dev, spec_type, evtype, nvl);
CT_DEBUG((CE_NOTE, "contract_device_negend(): exit dip: %p",
(void *)dip));
}
ct_ack_t
contract_device_negotiate(dev_info_t *dip, dev_t dev, int spec_type,
uint_t evtype)
{
int result;
ASSERT(dip);
ASSERT(dev != NODEV);
ASSERT(dev != DDI_DEV_T_ANY);
ASSERT(dev != DDI_DEV_T_NONE);
ASSERT(spec_type == S_IFBLK || spec_type == S_IFCHR);
result = CT_NACK;
switch (evtype) {
case CT_DEV_EV_OFFLINE:
result = contract_device_offline(dip, dev, spec_type);
break;
default:
cmn_err(CE_PANIC, "contract_device_negotiate(): Negotiation "
"not supported: event (%d) for dev_t (%lu) and spec (%d), "
"dip (%p)", evtype, dev, spec_type, (void *)dip);
break;
}
return (result);
}
void
contract_device_finalize(dev_info_t *dip, dev_t dev, int spec_type,
uint_t evtype, int ct_result)
{
ASSERT(dip);
ASSERT(dev != NODEV);
ASSERT(dev != DDI_DEV_T_ANY);
ASSERT(dev != DDI_DEV_T_NONE);
ASSERT(spec_type == S_IFBLK || spec_type == S_IFCHR);
switch (evtype) {
case CT_DEV_EV_OFFLINE:
contract_device_negend(dip, dev, spec_type, ct_result);
break;
case CT_DEV_EV_DEGRADED:
contract_device_degrade(dip, dev, spec_type);
contract_device_negend(dip, dev, spec_type, ct_result);
break;
case CT_DEV_EV_ONLINE:
contract_device_undegrade(dip, dev, spec_type);
contract_device_negend(dip, dev, spec_type, ct_result);
break;
default:
cmn_err(CE_PANIC, "contract_device_finalize(): Unsupported "
"event (%d) for dev_t (%lu) and spec (%d), dip (%p)",
evtype, dev, spec_type, (void *)dip);
break;
}
}
void
contract_device_remove_dip(dev_info_t *dip)
{
cont_device_t *ctd;
cont_device_t *next;
contract_t *ct;
mutex_enter(&(DEVI(dip)->devi_ct_lock));
ct_barrier_wait_for_release(dip);
for (ctd = list_head(&(DEVI(dip)->devi_ct)); ctd != NULL; ctd = next) {
next = list_next(&(DEVI(dip)->devi_ct), ctd);
list_remove(&(DEVI(dip)->devi_ct), ctd);
ct = &ctd->cond_contract;
mutex_enter(&ct->ct_lock);
ASSERT(ctd->cond_dip == dip);
ctd->cond_dip = NULL;
contract_rele(ct);
CT_DEBUG((CE_NOTE, "ct: remove_dip: removed dip from contract: "
"ctid: %d", ct->ct_id));
mutex_exit(&ct->ct_lock);
}
ASSERT(list_is_empty(&(DEVI(dip)->devi_ct)));
mutex_exit(&(DEVI(dip)->devi_ct_lock));
}
static void
ct_barrier_acquire(dev_info_t *dip)
{
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
CT_DEBUG((CE_NOTE, "ct_barrier_acquire: waiting for barrier"));
while (DEVI(dip)->devi_ct_count != -1)
cv_wait(&(DEVI(dip)->devi_ct_cv), &(DEVI(dip)->devi_ct_lock));
DEVI(dip)->devi_ct_count = 0;
CT_DEBUG((CE_NOTE, "ct_barrier_acquire: thread owns barrier"));
}
static void
ct_barrier_release(dev_info_t *dip)
{
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
ASSERT(DEVI(dip)->devi_ct_count != -1);
DEVI(dip)->devi_ct_count = -1;
cv_broadcast(&(DEVI(dip)->devi_ct_cv));
CT_DEBUG((CE_NOTE, "ct_barrier_release: Released barrier"));
}
static int
ct_barrier_held(dev_info_t *dip)
{
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
return (DEVI(dip)->devi_ct_count != -1);
}
static int
ct_barrier_empty(dev_info_t *dip)
{
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
ASSERT(DEVI(dip)->devi_ct_count != -1);
return (DEVI(dip)->devi_ct_count == 0);
}
static void
ct_barrier_wait_for_release(dev_info_t *dip)
{
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
while (DEVI(dip)->devi_ct_count != -1)
cv_wait(&(DEVI(dip)->devi_ct_cv), &(DEVI(dip)->devi_ct_lock));
}
static void
ct_barrier_decr(dev_info_t *dip)
{
CT_DEBUG((CE_NOTE, "barrier_decr: ct_count before decr: %d",
DEVI(dip)->devi_ct_count));
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
ASSERT(DEVI(dip)->devi_ct_count > 0);
DEVI(dip)->devi_ct_count--;
if (DEVI(dip)->devi_ct_count == 0) {
cv_broadcast(&DEVI(dip)->devi_ct_cv);
CT_DEBUG((CE_NOTE, "barrier_decr: cv_broadcast"));
}
}
static void
ct_barrier_incr(dev_info_t *dip)
{
ASSERT(ct_barrier_held(dip));
DEVI(dip)->devi_ct_count++;
}
static int
ct_barrier_wait_for_empty(dev_info_t *dip, int secs)
{
clock_t abstime;
ASSERT(MUTEX_HELD(&(DEVI(dip)->devi_ct_lock)));
abstime = ddi_get_lbolt() + drv_usectohz(secs*1000000);
while (DEVI(dip)->devi_ct_count) {
if (cv_timedwait(&(DEVI(dip)->devi_ct_cv),
&(DEVI(dip)->devi_ct_lock), abstime) == -1) {
return (-1);
}
}
return (0);
}