#include <sys/types.h>
#include <sys/param.h>
#include <sys/debug.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/ddi.h>
#include <sys/ndi_impldefs.h>
#include <sys/devctl.h>
#include <sys/nvpair.h>
#include <sys/ddifm.h>
#include <sys/ndifm.h>
#include <sys/spl.h>
#include <sys/sysmacros.h>
#include <sys/devops.h>
#include <sys/atomic.h>
#include <sys/kmem.h>
#include <sys/fm/io/ddi.h>
kmem_cache_t *ndi_fm_entry_cache;
void
ndi_fm_init(void)
{
ndi_fm_entry_cache = kmem_cache_create("ndi_fm_entry_cache",
sizeof (ndi_fmcentry_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
}
void
i_ndi_fmc_create(ndi_fmc_t **fcpp, int qlen, ddi_iblock_cookie_t ibc)
{
ndi_fmc_t *fcp;
fcp = kmem_zalloc(sizeof (ndi_fmc_t), KM_SLEEP);
mutex_init(&fcp->fc_lock, NULL, MUTEX_DRIVER, ibc);
*fcpp = fcp;
}
void
i_ndi_fmc_destroy(ndi_fmc_t *fcp)
{
ndi_fmcentry_t *fep, *pp;
if (fcp == NULL)
return;
mutex_enter(&fcp->fc_lock);
for (fep = fcp->fc_head; fep != NULL; fep = pp) {
pp = fep->fce_next;
kmem_cache_free(ndi_fm_entry_cache, fep);
}
mutex_exit(&fcp->fc_lock);
mutex_destroy(&fcp->fc_lock);
kmem_free(fcp, sizeof (ndi_fmc_t));
}
void
ndi_fmc_insert(dev_info_t *dip, int flag, void *resource, void *bus_specific)
{
struct dev_info *devi = DEVI(dip);
ndi_fmc_t *fcp;
ndi_fmcentry_t *fep, **fpp;
struct i_ddi_fmhdl *fmhdl;
ASSERT(devi);
fmhdl = devi->devi_fmhdl;
if (fmhdl == NULL) {
return;
}
switch (flag) {
case DMA_HANDLE:
if (!DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
return;
}
fcp = fmhdl->fh_dma_cache;
fpp = &((ddi_dma_impl_t *)resource)->dmai_error.err_fep;
break;
case ACC_HANDLE:
if (!DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL,
DDI_NOSLEEP);
return;
}
fcp = fmhdl->fh_acc_cache;
fpp = &((ddi_acc_impl_t *)resource)->ahi_err->err_fep;
break;
default:
ASSERT(0);
return;
}
fep = kmem_cache_alloc(ndi_fm_entry_cache, KM_NOSLEEP);
if (fep == NULL) {
atomic_inc_64(&fmhdl->fh_kstat.fek_fmc_full.value.ui64);
return;
}
fep->fce_bus_specific = bus_specific;
fep->fce_resource = resource;
fep->fce_next = NULL;
mutex_enter(&fcp->fc_lock);
ASSERT(*fpp == NULL);
*fpp = fep;
fep->fce_prev = fcp->fc_tail;
if (fcp->fc_tail != NULL)
fcp->fc_tail->fce_next = fep;
else
fcp->fc_head = fep;
fcp->fc_tail = fep;
mutex_exit(&fcp->fc_lock);
}
void
ndi_fmc_remove(dev_info_t *dip, int flag, const void *resource)
{
ndi_fmc_t *fcp;
ndi_fmcentry_t *fep;
struct dev_info *devi = DEVI(dip);
struct i_ddi_fmhdl *fmhdl;
ASSERT(devi);
ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
fmhdl = devi->devi_fmhdl;
if (fmhdl == NULL) {
return;
}
if (flag == DMA_HANDLE) {
if (!DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
return;
}
fcp = fmhdl->fh_dma_cache;
ASSERT(fcp);
mutex_enter(&fcp->fc_lock);
fep = ((ddi_dma_impl_t *)resource)->dmai_error.err_fep;
((ddi_dma_impl_t *)resource)->dmai_error.err_fep = NULL;
} else if (flag == ACC_HANDLE) {
if (!DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
i_ddi_drv_ereport_post(dip, DVR_EFMCAP, NULL,
DDI_NOSLEEP);
return;
}
fcp = fmhdl->fh_acc_cache;
ASSERT(fcp);
mutex_enter(&fcp->fc_lock);
fep = ((ddi_acc_impl_t *)resource)->ahi_err->err_fep;
((ddi_acc_impl_t *)resource)->ahi_err->err_fep = NULL;
} else {
return;
}
if (fep == NULL) {
mutex_exit(&fcp->fc_lock);
atomic_inc_64(&fmhdl->fh_kstat.fek_fmc_miss.value.ui64);
return;
}
if (fep == fcp->fc_head)
fcp->fc_head = fep->fce_next;
else
fep->fce_prev->fce_next = fep->fce_next;
if (fep == fcp->fc_tail)
fcp->fc_tail = fep->fce_prev;
else
fep->fce_next->fce_prev = fep->fce_prev;
mutex_exit(&fcp->fc_lock);
kmem_cache_free(ndi_fm_entry_cache, fep);
}
int
ndi_fmc_entry_error(dev_info_t *dip, int flag, ddi_fm_error_t *derr,
const void *bus_err_state)
{
int status, fatal = 0, nonfatal = 0;
ndi_fmc_t *fcp = NULL;
ndi_fmcentry_t *fep;
struct i_ddi_fmhdl *fmhdl;
ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
fmhdl = DEVI(dip)->devi_fmhdl;
ASSERT(fmhdl);
status = DDI_FM_UNKNOWN;
if (flag == DMA_HANDLE && DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
fcp = fmhdl->fh_dma_cache;
ASSERT(fcp);
} else if (flag == ACC_HANDLE && DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
fcp = fmhdl->fh_acc_cache;
ASSERT(fcp);
}
if (fcp != NULL) {
mutex_enter(&fcp->fc_lock);
for (fep = fcp->fc_head; fep != NULL; fep = fep->fce_next) {
ddi_fmcompare_t compare_func;
compare_func = (flag == ACC_HANDLE) ?
i_ddi_fm_acc_err_cf_get((ddi_acc_handle_t)
fep->fce_resource) :
i_ddi_fm_dma_err_cf_get((ddi_dma_handle_t)
fep->fce_resource);
if (compare_func == NULL)
continue;
status = compare_func(dip, fep->fce_resource,
bus_err_state, fep->fce_bus_specific);
if (status == DDI_FM_UNKNOWN || status == DDI_FM_OK)
continue;
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
if (flag == ACC_HANDLE) {
ddi_acc_handle_t ap = fep->fce_resource;
i_ddi_fm_acc_err_set(ap, derr->fme_ena, status,
DDI_FM_ERR_UNEXPECTED);
ddi_fm_acc_err_get(ap, derr, DDI_FME_VERSION);
derr->fme_acc_handle = ap;
} else {
ddi_dma_handle_t dp = fep->fce_resource;
i_ddi_fm_dma_err_set(dp, derr->fme_ena, status,
DDI_FM_ERR_UNEXPECTED);
ddi_fm_dma_err_get(dp, derr, DDI_FME_VERSION);
derr->fme_dma_handle = dp;
}
}
mutex_exit(&fcp->fc_lock);
}
return (fatal ? DDI_FM_FATAL : nonfatal ? DDI_FM_NONFATAL :
DDI_FM_UNKNOWN);
}
int
ndi_fmc_error(dev_info_t *dip, dev_info_t *tdip, int flag, uint64_t ena,
const void *bus_err_state)
{
int status, fatal = 0, nonfatal = 0;
ddi_fm_error_t derr;
struct i_ddi_fmhdl *fmhdl;
struct i_ddi_fmtgt *tgt;
ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
i_ddi_fm_handler_enter(dip);
fmhdl = DEVI(dip)->devi_fmhdl;
ASSERT(fmhdl);
bzero(&derr, sizeof (ddi_fm_error_t));
derr.fme_version = DDI_FME_VERSION;
derr.fme_flag = DDI_FM_ERR_UNEXPECTED;
derr.fme_ena = ena;
for (tgt = fmhdl->fh_tgts; tgt != NULL; tgt = tgt->ft_next) {
if (tdip != NULL && tdip != tgt->ft_dip)
continue;
status = ndi_fmc_entry_error(tgt->ft_dip, flag, &derr,
bus_err_state);
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
else
continue;
status = tgt->ft_errhdl->eh_func(tgt->ft_dip, &derr,
tgt->ft_errhdl->eh_impl);
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
}
i_ddi_fm_handler_exit(dip);
if (fatal)
return (DDI_FM_FATAL);
else if (nonfatal)
return (DDI_FM_NONFATAL);
return (DDI_FM_UNKNOWN);
}
int
ndi_fmc_entry_error_all(dev_info_t *dip, int flag, ddi_fm_error_t *derr)
{
ndi_fmc_t *fcp = NULL;
ndi_fmcentry_t *fep;
struct i_ddi_fmhdl *fmhdl;
int nonfatal = 0;
ASSERT(flag == DMA_HANDLE || flag == ACC_HANDLE);
fmhdl = DEVI(dip)->devi_fmhdl;
ASSERT(fmhdl);
if (flag == DMA_HANDLE && DDI_FM_DMA_ERR_CAP(fmhdl->fh_cap)) {
fcp = fmhdl->fh_dma_cache;
ASSERT(fcp);
} else if (flag == ACC_HANDLE && DDI_FM_ACC_ERR_CAP(fmhdl->fh_cap)) {
fcp = fmhdl->fh_acc_cache;
ASSERT(fcp);
}
if (fcp != NULL) {
mutex_enter(&fcp->fc_lock);
for (fep = fcp->fc_head; fep != NULL; fep = fep->fce_next) {
ddi_fmcompare_t compare_func;
compare_func = (flag == ACC_HANDLE) ?
i_ddi_fm_acc_err_cf_get((ddi_acc_handle_t)
fep->fce_resource) :
i_ddi_fm_dma_err_cf_get((ddi_dma_handle_t)
fep->fce_resource);
if (compare_func == NULL)
continue;
nonfatal++;
if (flag == ACC_HANDLE) {
ddi_acc_handle_t ap = fep->fce_resource;
i_ddi_fm_acc_err_set(ap, derr->fme_ena,
DDI_FM_NONFATAL, DDI_FM_ERR_UNEXPECTED);
ddi_fm_acc_err_get(ap, derr, DDI_FME_VERSION);
derr->fme_acc_handle = ap;
} else {
ddi_dma_handle_t dp = fep->fce_resource;
i_ddi_fm_dma_err_set(dp, derr->fme_ena,
DDI_FM_NONFATAL, DDI_FM_ERR_UNEXPECTED);
ddi_fm_dma_err_get(dp, derr, DDI_FME_VERSION);
derr->fme_dma_handle = dp;
}
}
mutex_exit(&fcp->fc_lock);
}
return (nonfatal ? DDI_FM_NONFATAL : DDI_FM_UNKNOWN);
}
int
ndi_fm_handler_dispatch(dev_info_t *dip, dev_info_t *tdip,
const ddi_fm_error_t *nerr)
{
int status;
int unknown = 0, fatal = 0, nonfatal = 0;
struct i_ddi_fmhdl *hdl;
struct i_ddi_fmtgt *tgt;
status = DDI_FM_UNKNOWN;
i_ddi_fm_handler_enter(dip);
hdl = DEVI(dip)->devi_fmhdl;
tgt = hdl->fh_tgts;
while (tgt != NULL) {
if (tdip == NULL || tdip == tgt->ft_dip) {
struct i_ddi_errhdl *errhdl;
errhdl = tgt->ft_errhdl;
status = errhdl->eh_func(tgt->ft_dip, nerr,
errhdl->eh_impl);
if (status == DDI_FM_FATAL)
++fatal;
else if (status == DDI_FM_NONFATAL)
++nonfatal;
else if (status == DDI_FM_UNKNOWN)
++unknown;
if (tdip != NULL)
break;
}
tgt = tgt->ft_next;
}
i_ddi_fm_handler_exit(dip);
if (fatal)
return (DDI_FM_FATAL);
else if (nonfatal)
return (DDI_FM_NONFATAL);
else if (unknown)
return (DDI_FM_UNKNOWN);
else
return (DDI_FM_OK);
}
void
ndi_fm_acc_err_set(ddi_acc_handle_t handle, ddi_fm_error_t *dfe)
{
i_ddi_fm_acc_err_set(handle, dfe->fme_ena, dfe->fme_status,
dfe->fme_flag);
}
void
ndi_fm_dma_err_set(ddi_dma_handle_t handle, ddi_fm_error_t *dfe)
{
i_ddi_fm_dma_err_set(handle, dfe->fme_ena, dfe->fme_status,
dfe->fme_flag);
}
int
i_ndi_busop_fm_init(dev_info_t *dip, int tcap, ddi_iblock_cookie_t *ibc)
{
int pcap;
dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
if (dip == ddi_root_node())
return (ddi_system_fmcap | DDI_FM_EREPORT_CAPABLE);
if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
return (DDI_FM_NOT_CAPABLE);
if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_init == NULL)
return (DDI_FM_NOT_CAPABLE);
pcap = (*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_init)
(pdip, dip, tcap, ibc);
return (pcap);
}
void
i_ndi_busop_fm_fini(dev_info_t *dip)
{
dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
if (dip == ddi_root_node())
return;
if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
return;
if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_fini == NULL)
return;
(*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_fini)(pdip, dip);
}
void
i_ndi_busop_access_enter(dev_info_t *dip, ddi_acc_handle_t handle)
{
dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
return;
if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_enter == NULL)
return;
(*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_enter)
(pdip, handle);
}
void
i_ndi_busop_access_exit(dev_info_t *dip, ddi_acc_handle_t handle)
{
dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
if (DEVI(pdip)->devi_ops->devo_bus_ops->busops_rev < BUSO_REV_6)
return;
if (DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_exit == NULL)
return;
(*DEVI(pdip)->devi_ops->devo_bus_ops->bus_fm_access_exit)(pdip, handle);
}