#include <sys/note.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/avintr.h>
#include <sys/autoconf.h>
#include <sys/sunndi.h>
#include <sys/ndi_impldefs.h>
#include <sys/ddi.h>
#include <sys/disp.h>
#include <sys/stat.h>
#include <sys/callb.h>
#include <sys/sysevent.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sysevent/dr.h>
#include <sys/taskq.h>
static void ddihp_cn_run_event(void *arg);
static int ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp,
ddi_hp_cn_state_t target_state);
int
ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p)
{
ddi_hp_cn_handle_t *hdlp;
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, info_p %p\n",
(void *)dip, (void *)info_p));
ASSERT(!servicing_interrupt());
if (servicing_interrupt())
return (NDI_FAILURE);
if ((dip == NULL) || (info_p == NULL))
return (NDI_EINVAL);
if (!NEXUS_HAS_HP_OP(dip)) {
return (NDI_ENOTSUP);
}
ndi_devi_enter(dip);
hdlp = ddihp_cn_name_to_handle(dip, info_p->cn_name);
if (hdlp) {
ndi_devi_exit(dip);
return (NDI_SUCCESS);
}
hdlp = (ddi_hp_cn_handle_t *)kmem_zalloc(
(sizeof (ddi_hp_cn_handle_t)), KM_SLEEP);
hdlp->cn_dip = dip;
bcopy(info_p, &(hdlp->cn_info), sizeof (*info_p));
hdlp->cn_info.cn_name = ddi_strdup(info_p->cn_name, KM_SLEEP);
if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) {
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, hdlp %p"
"ddi_cn_getstate failed\n", (void *)dip, (void *)hdlp));
goto fail;
}
DDIHP_LIST_APPEND(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp),
hdlp);
ndi_devi_exit(dip);
return (NDI_SUCCESS);
fail:
kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1);
kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t));
ndi_devi_exit(dip);
return (NDI_FAILURE);
}
int
ndi_hp_unregister(dev_info_t *dip, char *cn_name)
{
ddi_hp_cn_handle_t *hdlp;
int ret;
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_unregister: dip %p, cn name %s\n",
(void *)dip, cn_name));
ASSERT(!servicing_interrupt());
if (servicing_interrupt())
return (NDI_FAILURE);
if ((dip == NULL) || (cn_name == NULL))
return (NDI_EINVAL);
ndi_devi_enter(dip);
hdlp = ddihp_cn_name_to_handle(dip, cn_name);
if (hdlp == NULL) {
ndi_devi_exit(dip);
return (NDI_EINVAL);
}
switch (ddihp_cn_unregister(hdlp)) {
case DDI_SUCCESS:
ret = NDI_SUCCESS;
break;
case DDI_EINVAL:
ret = NDI_EINVAL;
break;
case DDI_EBUSY:
ret = NDI_BUSY;
break;
default:
ret = NDI_FAILURE;
break;
}
ndi_devi_exit(dip);
return (ret);
}
int
ndi_hp_state_change_req(dev_info_t *dip, char *cn_name,
ddi_hp_cn_state_t state, uint_t flag)
{
ddi_hp_cn_async_event_entry_t *eventp;
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: dip %p "
"cn_name: %s, state %x, flag %x\n",
(void *)dip, cn_name, state, flag));
if (dip == NULL || cn_name == NULL)
return (NDI_EINVAL);
if (!NEXUS_HAS_HP_OP(dip)) {
return (NDI_ENOTSUP);
}
if (flag & DDI_HP_REQ_SYNC) {
dev_info_t *pdip;
ddi_hp_cn_handle_t *hdlp;
int ret;
ASSERT(!servicing_interrupt());
if (servicing_interrupt())
return (NDI_FAILURE);
pdip = ddi_get_parent(dip);
if (pdip != NULL)
ndi_devi_enter(pdip);
ndi_devi_enter(dip);
hdlp = ddihp_cn_name_to_handle(dip, cn_name);
if (hdlp == NULL) {
ndi_devi_exit(dip);
if (pdip != NULL)
ndi_devi_exit(pdip);
return (NDI_EINVAL);
}
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: hdlp %p "
"calling ddihp_cn_req_handler() directly to handle "
"target_state %x\n", (void *)hdlp, state));
ret = ddihp_cn_req_handler(hdlp, state);
ndi_devi_exit(dip);
if (pdip != NULL)
ndi_devi_exit(pdip);
return (ret);
}
eventp = kmem_zalloc(sizeof (ddi_hp_cn_async_event_entry_t),
KM_NOSLEEP);
if (eventp == NULL)
return (NDI_NOMEM);
eventp->cn_name = ddi_strdup(cn_name, KM_NOSLEEP);
if (eventp->cn_name == NULL) {
kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t));
return (NDI_NOMEM);
}
eventp->dip = dip;
eventp->target_state = state;
ndi_hold_devi(dip);
if (taskq_dispatch(system_taskq, ddihp_cn_run_event, eventp,
TQ_NOSLEEP) == TASKQID_INVALID) {
ndi_rele_devi(dip);
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: "
"taskq_dispatch failed! dip %p "
"target_state %x\n", (void *)dip, state));
return (NDI_NOMEM);
}
return (NDI_CLAIMED);
}
void
ndi_hp_walk_cn(dev_info_t *dip, int (*f)(ddi_hp_cn_info_t *,
void *), void *arg)
{
ddi_hp_cn_handle_t *head, *curr, *prev;
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n",
(void *)dip, arg));
ASSERT(!servicing_interrupt());
if (servicing_interrupt())
return;
if (dip == NULL)
return;
ndi_devi_enter(dip);
head = DEVI(dip)->devi_hp_hdlp;
curr = head;
prev = NULL;
while (curr != NULL) {
DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p "
"current cn_name: %s\n",
(void *)dip, curr->cn_info.cn_name));
switch ((*f)(&(curr->cn_info), arg)) {
case DDI_WALK_TERMINATE:
ndi_devi_exit(dip);
return;
case DDI_WALK_CONTINUE:
default:
if (DEVI(dip)->devi_hp_hdlp != head) {
head = DEVI(dip)->devi_hp_hdlp;
curr = head;
prev = NULL;
} else if (prev && prev->next != curr) {
curr = prev->next;
} else {
prev = curr;
curr = curr->next;
}
}
}
ndi_devi_exit(dip);
}
static void
ddihp_cn_run_event(void *arg)
{
ddi_hp_cn_async_event_entry_t *eventp =
(ddi_hp_cn_async_event_entry_t *)arg;
dev_info_t *dip = eventp->dip;
dev_info_t *pdip;
ddi_hp_cn_handle_t *hdlp;
pdip = ddi_get_parent(dip);
if (pdip != NULL)
ndi_devi_enter(pdip);
ndi_devi_enter(dip);
hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name);
if (hdlp) {
(void) ddihp_cn_req_handler(hdlp, eventp->target_state);
} else {
DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for "
"cn_name: %s dip %p. Request for target_state %x is"
" dropped. \n",
eventp->cn_name, (void *)dip, eventp->target_state));
}
ndi_devi_exit(dip);
if (pdip != NULL)
ndi_devi_exit(pdip);
ndi_rele_devi((dev_info_t *)DEVI(dip));
kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1);
kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t));
}
static int
ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp,
ddi_hp_cn_state_t target_state)
{
dev_info_t *dip = hdlp->cn_dip;
int ret = DDI_SUCCESS;
DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:"
" hdlp %p, target_state %x\n",
(void *)hdlp, target_state));
ASSERT(DEVI_BUSY_OWNED(dip));
if (hdlp->cn_info.cn_state != target_state) {
ddi_hp_cn_state_t result_state = 0;
DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE,
(void *)&target_state, (void *)&result_state, ret);
DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, "
"hdlp %p changed state to %x, ret=%x\n",
(void *)dip, (void *)hdlp, result_state, ret));
}
if (ret == DDI_SUCCESS)
return (NDI_CLAIMED);
else
return (NDI_UNCLAIMED);
}