#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/atomic.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/ethernet.h>
#include <sys/pci.h>
#include <sys/pcie.h>
#include "sfxge.h"
#include "efx.h"
static ddi_device_acc_attr_t sfxge_intr_devacc = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
static ddi_dma_attr_t sfxge_intr_dma_attr = {
DMA_ATTR_V0,
0,
0xffffffffffffffffull,
0xffffffffffffffffull,
EFX_INTR_SIZE,
0xffffffff,
1,
0xffffffffffffffffull,
0xffffffffffffffffull,
1,
1,
0
};
static unsigned int
sfxge_intr_line(caddr_t arg1, caddr_t arg2)
{
sfxge_t *sp = (void *)arg1;
efx_nic_t *enp = sp->s_enp;
sfxge_intr_t *sip = &(sp->s_intr);
unsigned int index;
boolean_t fatal;
uint32_t qmask;
int rc;
_NOTE(ARGUNUSED(arg2))
ASSERT3U(sip->si_type, ==, EFX_INTR_LINE);
if (sip->si_state != SFXGE_INTR_STARTED &&
sip->si_state != SFXGE_INTR_TESTING) {
rc = DDI_INTR_UNCLAIMED;
goto done;
}
if (sip->si_state == SFXGE_INTR_TESTING) {
sip->si_mask |= 1;
rc = DDI_INTR_CLAIMED;
goto done;
}
efx_intr_status_line(enp, &fatal, &qmask);
if (fatal) {
sfxge_intr_fatal(sp);
rc = DDI_INTR_CLAIMED;
goto done;
}
if (qmask != 0) {
for (index = 0; index < EFX_INTR_NEVQS; index++) {
if (qmask & (1 << index))
(void) sfxge_ev_qpoll(sp, index);
}
sip->si_zero_count = 0;
sfxge_gld_rx_push(sp);
rc = DDI_INTR_CLAIMED;
goto done;
}
if (sip->si_zero_count++ == 0) {
for (index = 0; index < EFX_INTR_NEVQS; index++) {
if (sp->s_sep[index] != NULL)
(void) sfxge_ev_qpoll(sp, index);
}
rc = DDI_INTR_CLAIMED;
} else {
for (index = 0; index < EFX_INTR_NEVQS; index++) {
if (sp->s_sep[index] != NULL)
(void) sfxge_ev_qprime(sp, index);
}
rc = DDI_INTR_UNCLAIMED;
}
done:
return (rc);
}
static unsigned int
sfxge_intr_message(caddr_t arg1, caddr_t arg2)
{
sfxge_t *sp = (void *)arg1;
efx_nic_t *enp = sp->s_enp;
sfxge_intr_t *sip = &(sp->s_intr);
unsigned int index = (unsigned int)(uintptr_t)arg2;
boolean_t fatal;
int rc;
ASSERT3U(sip->si_type, ==, EFX_INTR_MESSAGE);
if (sip->si_state != SFXGE_INTR_STARTED &&
sip->si_state != SFXGE_INTR_TESTING) {
rc = DDI_INTR_UNCLAIMED;
goto done;
}
if (sip->si_state == SFXGE_INTR_TESTING) {
uint64_t mask;
do {
mask = sip->si_mask;
} while (atomic_cas_64(&(sip->si_mask), mask,
mask | (1 << index)) != mask);
rc = DDI_INTR_CLAIMED;
goto done;
}
efx_intr_status_message(enp, index, &fatal);
if (fatal) {
sfxge_intr_fatal(sp);
rc = DDI_INTR_CLAIMED;
goto done;
}
(void) sfxge_ev_qpoll(sp, index);
sfxge_gld_rx_push(sp);
rc = DDI_INTR_CLAIMED;
done:
return (rc);
}
static int
sfxge_intr_bus_enable(sfxge_t *sp)
{
sfxge_intr_t *sip = &(sp->s_intr);
ddi_intr_handler_t *handler;
int add_index;
int en_index;
int err;
int rc;
mutex_enter(&sfxge_global_lock);
switch (sip->si_type) {
case EFX_INTR_MESSAGE:
handler = sfxge_intr_message;
break;
case EFX_INTR_LINE:
handler = sfxge_intr_line;
break;
default:
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_enable: unknown intr type (si_type=%d nalloc=%d)",
sip->si_type, sip->si_nalloc);
ASSERT(B_FALSE);
rc = EINVAL;
goto fail1;
}
for (add_index = 0; add_index < sip->si_nalloc; add_index++) {
unsigned int pri;
err = ddi_intr_get_pri(sip->si_table[add_index], &pri);
ASSERT(err == DDI_SUCCESS);
DTRACE_PROBE2(pri, unsigned int, add_index, unsigned int, pri);
err = ddi_intr_add_handler(sip->si_table[add_index], handler,
(caddr_t)sp, (caddr_t)(uintptr_t)add_index);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_enable: ddi_intr_add_handler failed"
" err=%d (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[add_index], add_index,
sip->si_nalloc);
rc = (err == DDI_EINVAL) ? EINVAL : EFAULT;
goto fail2;
}
}
err = ddi_intr_get_cap(sip->si_table[0], &(sip->si_cap));
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_enable: ddi_intr_get_cap failed"
" err=%d (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[0], 0, sip->si_nalloc);
if (err == DDI_EINVAL)
rc = EINVAL;
else if (err == DDI_ENOTSUP)
rc = ENOTSUP;
else
rc = EFAULT;
goto fail3;
}
if (sip->si_cap & DDI_INTR_FLAG_BLOCK) {
en_index = 0;
err = ddi_intr_block_enable(sip->si_table, sip->si_nalloc);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_enable: ddi_intr_block_enable failed"
" err=%d (table=%p nalloc=%d)",
err, (void *)sip->si_table, sip->si_nalloc);
rc = (err == DDI_EINVAL) ? EINVAL : EFAULT;
goto fail4;
}
} else {
for (en_index = 0; en_index < sip->si_nalloc; en_index++) {
err = ddi_intr_enable(sip->si_table[en_index]);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_enable: ddi_intr_enable failed"
" err=%d (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[en_index],
en_index, sip->si_nalloc);
rc = (err == DDI_EINVAL) ? EINVAL : EFAULT;
goto fail4;
}
}
}
mutex_exit(&sfxge_global_lock);
return (0);
fail4:
DTRACE_PROBE(fail4);
if (!(sip->si_cap & DDI_INTR_FLAG_BLOCK)) {
while (--en_index >= 0) {
err = ddi_intr_disable(sip->si_table[en_index]);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_enable: ddi_intr_disable"
" failed err=%d (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[en_index],
en_index, sip->si_nalloc);
}
}
}
fail3:
DTRACE_PROBE(fail3);
add_index = sip->si_nalloc;
fail2:
DTRACE_PROBE(fail2);
while (--add_index >= 0) {
err = ddi_intr_remove_handler(sip->si_table[add_index]);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_enable: ddi_intr_remove_handler"
" failed err=%d (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[add_index], add_index,
sip->si_nalloc);
}
}
fail1:
DTRACE_PROBE1(fail1, int, rc);
mutex_exit(&sfxge_global_lock);
return (rc);
}
static void
sfxge_intr_bus_disable(sfxge_t *sp)
{
sfxge_intr_t *sip = &(sp->s_intr);
int index;
int err;
mutex_enter(&sfxge_global_lock);
if (sip->si_cap & DDI_INTR_FLAG_BLOCK) {
err = ddi_intr_block_disable(sip->si_table, sip->si_nalloc);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_disable: ddi_intr_block_disable"
" failed err=%d (table=%p nalloc=%d)",
err, (void *)sip->si_table, sip->si_nalloc);
}
} else {
index = sip->si_nalloc;
while (--index >= 0) {
err = ddi_intr_disable(sip->si_table[index]);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_disable: ddi_intr_disable"
" failed err=%d (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[index], index,
sip->si_nalloc);
}
}
}
sip->si_cap = 0;
index = sip->si_nalloc;
while (--index >= 0) {
err = ddi_intr_remove_handler(sip->si_table[index]);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"bus_disable: ddi_intr_remove_handler"
" failed err=%d (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[index], index,
sip->si_nalloc);
}
}
mutex_exit(&sfxge_global_lock);
}
static int
sfxge_intr_nic_enable(sfxge_t *sp)
{
sfxge_intr_t *sip = &(sp->s_intr);
efsys_mem_t *esmp = &(sip->si_mem);
efx_nic_t *enp = sp->s_enp;
unsigned int index;
uint64_t mask;
unsigned int count;
int rc;
bzero(esmp->esm_base, EFX_INTR_SIZE);
if ((rc = efx_intr_init(enp, sip->si_type, esmp)) != 0)
goto fail1;
efx_intr_enable(enp);
if (sp->s_family == EFX_FAMILY_HUNTINGTON) {
return (0);
}
mask = 0;
for (index = 0; index < sip->si_nalloc; index++) {
mask |= (1 << index);
rc = efx_intr_trigger(enp, index);
ASSERT3U(rc, ==, 0);
}
count = 0;
do {
DTRACE_PROBE1(wait, unsigned int, count);
drv_usecwait(1000);
if ((mask & sip->si_mask) == mask)
goto done;
} while (++count < 20);
rc = ETIMEDOUT;
goto fail2;
done:
return (0);
fail2:
DTRACE_PROBE(fail2);
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"Interrupt test failed (mask=%"PRIx64" got=%"
PRIx64"). NIC is disabled",
mask, sip->si_mask);
DTRACE_PROBE2(int_test_fail, uint64_t, mask, uint64_t, sip->si_mask);
sip->si_mask = 0;
efx_intr_disable(enp);
efx_intr_fini(enp);
fail1:
DTRACE_PROBE1(fail1, int, rc);
return (rc);
}
static void
sfxge_intr_nic_disable(sfxge_t *sp)
{
sfxge_intr_t *sip = &(sp->s_intr);
efx_nic_t *enp = sp->s_enp;
sip->si_mask = 0;
efx_intr_disable(enp);
efx_intr_fini(enp);
}
static inline unsigned
pow2_le(unsigned long n)
{
unsigned int order = 1;
ASSERT3U(n, >, 0);
while ((1ul << order) <= n) ++order;
return (1ul << (order - 1));
}
int
sfxge_intr_init(sfxge_t *sp)
{
dev_info_t *dip = sp->s_dip;
sfxge_intr_t *sip = &(sp->s_intr);
efsys_mem_t *esmp = &(sip->si_mem);
sfxge_dma_buffer_attr_t dma_attr;
int err;
int rc;
int types;
int type;
int index;
unsigned int nalloc;
int navail;
SFXGE_OBJ_CHECK(sip, sfxge_intr_t);
ASSERT3U(sip->si_state, ==, SFXGE_INTR_UNINITIALIZED);
#ifdef __sparc
(void) ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
"#msix-request", NULL, 0);
#endif
err = ddi_intr_get_supported_types(dip, &types);
if (err != DDI_SUCCESS) {
dev_err(dip, CE_WARN, SFXGE_CMN_ERR
"intr_init: ddi_intr_get_supported_types failed err=%d",
err);
if (err == DDI_EINVAL)
rc = EINVAL;
else if (err == DDI_INTR_NOTFOUND)
rc = ENOENT;
else
rc = EFAULT;
goto fail1;
}
if (types & DDI_INTR_TYPE_MSIX) {
DTRACE_PROBE(msix);
type = DDI_INTR_TYPE_MSIX;
sip->si_type = EFX_INTR_MESSAGE;
} else {
DTRACE_PROBE(fixed);
ASSERT(types & DDI_INTR_TYPE_FIXED);
type = DDI_INTR_TYPE_FIXED;
sip->si_type = EFX_INTR_LINE;
}
navail = 0;
err = ddi_intr_get_navail(dip, type, &navail);
if (err != DDI_SUCCESS) {
dev_err(dip, CE_WARN, SFXGE_CMN_ERR
"intr_init: ddi_intr_get_navail failed err=%d", err);
if (err == DDI_EINVAL)
rc = EINVAL;
else if (err == DDI_INTR_NOTFOUND)
rc = ENOENT;
else
rc = EFAULT;
goto fail2;
}
if (navail == 0) {
rc = ENOENT;
goto fail2;
}
if (type != DDI_INTR_TYPE_MSIX)
navail = 1;
else
navail = min(navail, sfxge_rx_scale_prop_get(sp));
DTRACE_PROBE1(navail, unsigned int, navail);
sip->si_table_size = navail * sizeof (ddi_intr_handle_t);
sip->si_table = kmem_zalloc(sip->si_table_size, KM_SLEEP);
mutex_enter(&sfxge_global_lock);
err = ddi_intr_alloc(dip, sip->si_table, type, 0,
navail, &(sip->si_nalloc), DDI_INTR_ALLOC_NORMAL);
mutex_exit(&sfxge_global_lock);
if (err != DDI_SUCCESS) {
dev_err(dip, CE_WARN, SFXGE_CMN_ERR
"intr_init: ddi_intr_alloc failed err=%d"
" (navail=%d nalloc=%d)",
err, navail, sip->si_nalloc);
if (err == DDI_EINVAL)
rc = EINVAL;
else if (err == DDI_EAGAIN)
rc = EAGAIN;
else if (err == DDI_INTR_NOTFOUND)
rc = ENOENT;
else
rc = EFAULT;
goto fail3;
}
if (sip->si_nalloc == 0) {
rc = ENOENT;
goto fail3;
}
nalloc = pow2_le(sip->si_nalloc);
mutex_enter(&sfxge_global_lock);
index = sip->si_nalloc;
while (--index >= nalloc) {
(void) ddi_intr_free(sip->si_table[index]);
sip->si_table[index] = NULL;
}
mutex_exit(&sfxge_global_lock);
sip->si_nalloc = nalloc;
DTRACE_PROBE1(nalloc, unsigned int, sip->si_nalloc);
dma_attr.sdba_dip = sp->s_dip;
dma_attr.sdba_dattrp = &sfxge_intr_dma_attr;
dma_attr.sdba_callback = DDI_DMA_SLEEP;
dma_attr.sdba_length = EFX_INTR_SIZE;
dma_attr.sdba_memflags = DDI_DMA_CONSISTENT;
dma_attr.sdba_devaccp = &sfxge_intr_devacc;
dma_attr.sdba_bindflags = DDI_DMA_RDWR | DDI_DMA_CONSISTENT;
dma_attr.sdba_maxcookies = 1;
dma_attr.sdba_zeroinit = B_TRUE;
if ((rc = sfxge_dma_buffer_create(esmp, &dma_attr)) != 0)
goto fail4;
sip->si_intr_pri = 0;
for (index = 0; index < sip->si_nalloc; index++) {
uint_t pri;
if ((rc = ddi_intr_get_pri(sip->si_table[index], &pri)) != 0)
goto fail5;
if (pri > sip->si_intr_pri)
sip->si_intr_pri = pri;
}
sip->si_state = SFXGE_INTR_INITIALIZED;
return (0);
fail5:
DTRACE_PROBE(fail5);
fail4:
DTRACE_PROBE(fail4);
mutex_exit(&sfxge_global_lock);
index = sip->si_nalloc;
while (--index >= 0) {
err = ddi_intr_free(sip->si_table[index]);
if (err != DDI_SUCCESS) {
dev_err(dip, CE_WARN, SFXGE_CMN_ERR
"intr_init: ddi_intr_free failed err=%d"
" (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[index], index,
sip->si_nalloc);
}
sip->si_table[index] = NULL;
}
sip->si_nalloc = 0;
mutex_exit(&sfxge_global_lock);
fail3:
DTRACE_PROBE(fail3);
kmem_free(sip->si_table, sip->si_table_size);
sip->si_table = NULL;
sip->si_table_size = 0;
fail2:
DTRACE_PROBE(fail2);
sip->si_type = EFX_INTR_INVALID;
fail1:
DTRACE_PROBE1(fail1, int, rc);
SFXGE_OBJ_CHECK(sip, sfxge_intr_t);
return (rc);
}
int
sfxge_intr_start(sfxge_t *sp)
{
sfxge_intr_t *sip = &(sp->s_intr);
int rc;
ASSERT3U(sip->si_state, ==, SFXGE_INTR_INITIALIZED);
if ((rc = sfxge_intr_bus_enable(sp)) != 0)
goto fail1;
sip->si_state = SFXGE_INTR_TESTING;
if ((rc = sfxge_intr_nic_enable(sp)) != 0)
goto fail2;
sip->si_state = SFXGE_INTR_STARTED;
return (0);
fail2:
DTRACE_PROBE(fail2);
sfxge_intr_bus_disable(sp);
fail1:
DTRACE_PROBE1(fail1, int, rc);
sip->si_state = SFXGE_INTR_INITIALIZED;
return (rc);
}
void
sfxge_intr_stop(sfxge_t *sp)
{
sfxge_intr_t *sip = &(sp->s_intr);
ASSERT3U(sip->si_state, ==, SFXGE_INTR_STARTED);
sip->si_state = SFXGE_INTR_INITIALIZED;
sfxge_intr_nic_disable(sp);
sfxge_intr_bus_disable(sp);
}
void
sfxge_intr_fini(sfxge_t *sp)
{
sfxge_intr_t *sip = &(sp->s_intr);
efsys_mem_t *esmp = &(sip->si_mem);
int index;
int err;
ASSERT3U(sip->si_state, ==, SFXGE_INTR_INITIALIZED);
sip->si_state = SFXGE_INTR_UNINITIALIZED;
sfxge_dma_buffer_destroy(esmp);
mutex_enter(&sfxge_global_lock);
index = sip->si_nalloc;
while (--index >= 0) {
err = ddi_intr_free(sip->si_table[index]);
if (err != DDI_SUCCESS) {
dev_err(sp->s_dip, CE_WARN, SFXGE_CMN_ERR
"intr_fini: ddi_intr_free failed err=%d"
" (h=%p idx=%d nalloc=%d)",
err, (void *)sip->si_table[index],
index, sip->si_nalloc);
}
sip->si_table[index] = NULL;
}
sip->si_nalloc = 0;
mutex_exit(&sfxge_global_lock);
kmem_free(sip->si_table, sip->si_table_size);
sip->si_table = NULL;
sip->si_table_size = 0;
sip->si_type = EFX_INTR_INVALID;
SFXGE_OBJ_CHECK(sip, sfxge_intr_t);
}