#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/sysmacros.h>
#include <sys/time.h>
#include <sys/pci.h>
#include <sys/mac_provider.h>
#include <mlxcx.h>
CTASSERT((1 << MLXCX_RX_HASH_FT_SIZE_SHIFT) >= MLXCX_TIRS_PER_GROUP);
#define MLXCX_MODULE_NAME "mlxcx"
#define MLXCX_DRIVER_VERSION "illumos,mlxcx,1.0.0,1,000,000000"
clock_t mlxcx_reclaim_delay = 1000 * 50;
uint_t mlxcx_reclaim_tries = 100;
static void *mlxcx_softstate;
uint_t mlxcx_doorbell_tries = MLXCX_DOORBELL_TRIES_DFLT;
uint_t mlxcx_stuck_intr_count = MLXCX_STUCK_INTR_COUNT_DFLT;
static void
mlxcx_load_prop_defaults(mlxcx_t *mlxp)
{
mlxcx_drv_props_t *p = &mlxp->mlx_props;
mlxcx_port_t *port = &mlxp->mlx_ports[0];
VERIFY((mlxp->mlx_attach & MLXCX_ATTACH_PORTS) != 0);
VERIFY((mlxp->mlx_attach & (MLXCX_ATTACH_CQS | MLXCX_ATTACH_WQS)) == 0);
if ((port->mlp_max_proto & (MLXCX_PROTO_25G | MLXCX_PROTO_40G |
MLXCX_PROTO_50G | MLXCX_PROTO_100G)) != 0 ||
(port->mlp_ext_max_proto & (MLXCX_EXTPROTO_25G |
MLXCX_EXTPROTO_40G | MLXCX_EXTPROTO_50G | MLXCX_EXTPROTO_100G |
MLXCX_EXTPROTO_200G | MLXCX_EXTPROTO_400G)) != 0) {
p->mldp_cq_size_shift_default = MLXCX_CQ_SIZE_SHIFT_25G;
p->mldp_rq_size_shift_default = MLXCX_RQ_SIZE_SHIFT_25G;
p->mldp_sq_size_shift_default = MLXCX_SQ_SIZE_SHIFT_25G;
} else if ((port->mlp_max_proto & (MLXCX_PROTO_100M | MLXCX_PROTO_1G |
MLXCX_PROTO_10G)) != 0 ||
(port->mlp_ext_max_proto & (MLXCX_EXTPROTO_100M |
MLXCX_EXTPROTO_5G | MLXCX_EXTPROTO_1G | MLXCX_EXTPROTO_10G)) != 0) {
p->mldp_cq_size_shift_default = MLXCX_CQ_SIZE_SHIFT_DFLT;
p->mldp_rq_size_shift_default = MLXCX_RQ_SIZE_SHIFT_DFLT;
p->mldp_sq_size_shift_default = MLXCX_SQ_SIZE_SHIFT_DFLT;
} else {
mlxcx_warn(mlxp, "Encountered a port with a speed we don't "
"recognize. Proto: 0x%x", port->mlp_max_proto);
p->mldp_cq_size_shift_default = MLXCX_CQ_SIZE_SHIFT_DFLT;
p->mldp_rq_size_shift_default = MLXCX_RQ_SIZE_SHIFT_DFLT;
p->mldp_sq_size_shift_default = MLXCX_SQ_SIZE_SHIFT_DFLT;
}
}
static void
mlxcx_load_model_props(mlxcx_t *mlxp)
{
mlxcx_drv_props_t *p = &mlxp->mlx_props;
mlxcx_load_prop_defaults(mlxp);
p->mldp_cq_size_shift = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "cq_size_shift",
p->mldp_cq_size_shift_default);
p->mldp_sq_size_shift = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "sq_size_shift",
p->mldp_sq_size_shift_default);
p->mldp_rq_size_shift = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "rq_size_shift",
p->mldp_rq_size_shift_default);
}
static void
mlxcx_load_props(mlxcx_t *mlxp)
{
mlxcx_drv_props_t *p = &mlxp->mlx_props;
p->mldp_eq_size_shift = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "eq_size_shift",
MLXCX_EQ_SIZE_SHIFT_DFLT);
p->mldp_cqemod_period_usec = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "cqemod_period_usec",
MLXCX_CQEMOD_PERIOD_USEC_DFLT);
p->mldp_cqemod_count = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "cqemod_count",
MLXCX_CQEMOD_COUNT_DFLT);
p->mldp_intrmod_period_usec = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "intrmod_period_usec",
MLXCX_INTRMOD_PERIOD_USEC_DFLT);
p->mldp_tx_ngroups = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "tx_ngroups",
MLXCX_TX_NGROUPS_DFLT);
p->mldp_tx_nrings_per_group = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "tx_nrings_per_group",
MLXCX_TX_NRINGS_PER_GROUP_DFLT);
p->mldp_rx_ngroups_large = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "rx_ngroups_large",
MLXCX_RX_NGROUPS_LARGE_DFLT);
p->mldp_rx_ngroups_small = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "rx_ngroups_small",
MLXCX_RX_NGROUPS_SMALL_DFLT);
p->mldp_rx_nrings_per_large_group = ddi_getprop(DDI_DEV_T_ANY,
mlxp->mlx_dip, DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS,
"rx_nrings_per_large_group", MLXCX_RX_NRINGS_PER_LARGE_GROUP_DFLT);
p->mldp_rx_nrings_per_small_group = ddi_getprop(DDI_DEV_T_ANY,
mlxp->mlx_dip, DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS,
"rx_nrings_per_small_group", MLXCX_RX_NRINGS_PER_SMALL_GROUP_DFLT);
p->mldp_ftbl_root_size_shift = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "ftbl_root_size_shift",
MLXCX_FTBL_ROOT_SIZE_SHIFT_DFLT);
p->mldp_tx_bind_threshold = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "tx_bind_threshold",
MLXCX_TX_BIND_THRESHOLD_DFLT);
p->mldp_ftbl_vlan_size_shift = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "ftbl_vlan_size_shift",
MLXCX_FTBL_VLAN_SIZE_SHIFT_DFLT);
p->mldp_eq_check_interval_sec = ddi_getprop(DDI_DEV_T_ANY,
mlxp->mlx_dip, DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS,
"eq_check_interval_sec", MLXCX_EQ_CHECK_INTERVAL_SEC_DFLT);
p->mldp_cq_check_interval_sec = ddi_getprop(DDI_DEV_T_ANY,
mlxp->mlx_dip, DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS,
"cq_check_interval_sec", MLXCX_CQ_CHECK_INTERVAL_SEC_DFLT);
p->mldp_wq_check_interval_sec = ddi_getprop(DDI_DEV_T_ANY,
mlxp->mlx_dip, DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS,
"wq_check_interval_sec", MLXCX_WQ_CHECK_INTERVAL_SEC_DFLT);
p->mldp_rx_per_cq = ddi_getprop(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "rx_limit_per_completion",
MLXCX_RX_PER_CQ_DEFAULT);
if (p->mldp_rx_per_cq < MLXCX_RX_PER_CQ_MIN ||
p->mldp_rx_per_cq > MLXCX_RX_PER_CQ_MAX) {
mlxcx_warn(mlxp, "!rx_limit_per_completion = %u is "
"out of range. Defaulting to: %d. Valid values are from "
"%d to %d", p->mldp_rx_per_cq, MLXCX_RX_PER_CQ_DEFAULT,
MLXCX_RX_PER_CQ_MIN, MLXCX_RX_PER_CQ_MAX);
p->mldp_rx_per_cq = MLXCX_RX_PER_CQ_DEFAULT;
}
p->mldp_rx_p50_loan_min_size = ddi_getprop(DDI_DEV_T_ANY,
mlxp->mlx_dip, DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS,
"rx_p50_loan_min_size", MLXCX_P50_LOAN_MIN_SIZE_DFLT);
}
void
mlxcx_note(mlxcx_t *mlxp, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (mlxp != NULL && mlxp->mlx_dip != NULL) {
vdev_err(mlxp->mlx_dip, CE_NOTE, fmt, ap);
} else {
vcmn_err(CE_NOTE, fmt, ap);
}
va_end(ap);
}
void
mlxcx_warn(mlxcx_t *mlxp, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (mlxp != NULL && mlxp->mlx_dip != NULL) {
vdev_err(mlxp->mlx_dip, CE_WARN, fmt, ap);
} else {
vcmn_err(CE_WARN, fmt, ap);
}
va_end(ap);
}
void
mlxcx_panic(mlxcx_t *mlxp, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (mlxp != NULL && mlxp->mlx_dip != NULL) {
vdev_err(mlxp->mlx_dip, CE_PANIC, fmt, ap);
} else {
vcmn_err(CE_PANIC, fmt, ap);
}
va_end(ap);
}
uint16_t
mlxcx_get16(mlxcx_t *mlxp, uintptr_t off)
{
uintptr_t addr = off + (uintptr_t)mlxp->mlx_regs_base;
return (ddi_get16(mlxp->mlx_regs_handle, (void *)addr));
}
uint32_t
mlxcx_get32(mlxcx_t *mlxp, uintptr_t off)
{
uintptr_t addr = off + (uintptr_t)mlxp->mlx_regs_base;
return (ddi_get32(mlxp->mlx_regs_handle, (void *)addr));
}
uint64_t
mlxcx_get64(mlxcx_t *mlxp, uintptr_t off)
{
uintptr_t addr = off + (uintptr_t)mlxp->mlx_regs_base;
return (ddi_get64(mlxp->mlx_regs_handle, (void *)addr));
}
void
mlxcx_put32(mlxcx_t *mlxp, uintptr_t off, uint32_t val)
{
uintptr_t addr = off + (uintptr_t)mlxp->mlx_regs_base;
ddi_put32(mlxp->mlx_regs_handle, (void *)addr, val);
}
void
mlxcx_put64(mlxcx_t *mlxp, uintptr_t off, uint64_t val)
{
uintptr_t addr = off + (uintptr_t)mlxp->mlx_regs_base;
ddi_put64(mlxp->mlx_regs_handle, (void *)addr, val);
}
void
mlxcx_uar_put32(mlxcx_t *mlxp, mlxcx_uar_t *mlu, uintptr_t off, uint32_t val)
{
uintptr_t addr = off + (uintptr_t)mlu->mlu_base +
(uintptr_t)mlxp->mlx_regs_base;
ddi_put32(mlxp->mlx_regs_handle, (void *)addr, val);
}
void
mlxcx_uar_put64(mlxcx_t *mlxp, mlxcx_uar_t *mlu, uintptr_t off, uint64_t val)
{
uintptr_t addr = off + (uintptr_t)mlu->mlu_base +
(uintptr_t)mlxp->mlx_regs_base;
ddi_put64(mlxp->mlx_regs_handle, (void *)addr, val);
}
static void
mlxcx_fm_fini(mlxcx_t *mlxp)
{
if (mlxp->mlx_fm_caps == 0)
return;
if (DDI_FM_ERRCB_CAP(mlxp->mlx_fm_caps))
ddi_fm_handler_unregister(mlxp->mlx_dip);
if (DDI_FM_EREPORT_CAP(mlxp->mlx_fm_caps) ||
DDI_FM_ERRCB_CAP(mlxp->mlx_fm_caps))
pci_ereport_teardown(mlxp->mlx_dip);
ddi_fm_fini(mlxp->mlx_dip);
mlxp->mlx_fm_caps = 0;
}
void
mlxcx_fm_ereport(mlxcx_t *mlxp, const char *detail)
{
uint64_t ena;
char buf[FM_MAX_CLASS];
if (!DDI_FM_EREPORT_CAP(mlxp->mlx_fm_caps))
return;
(void) snprintf(buf, FM_MAX_CLASS, "%s.%s", DDI_FM_DEVICE, detail);
ena = fm_ena_generate(0, FM_ENA_FMT1);
ddi_fm_ereport_post(mlxp->mlx_dip, buf, ena, DDI_NOSLEEP,
FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0,
NULL);
}
static int
mlxcx_fm_errcb(dev_info_t *dip, ddi_fm_error_t *err, const void *arg)
{
pci_ereport_post(dip, err, NULL);
return (err->fme_status);
}
static void
mlxcx_fm_init(mlxcx_t *mlxp)
{
ddi_iblock_cookie_t iblk;
int def = DDI_FM_EREPORT_CAPABLE | DDI_FM_ACCCHK_CAPABLE |
DDI_FM_DMACHK_CAPABLE | DDI_FM_ERRCB_CAPABLE;
mlxp->mlx_fm_caps = ddi_prop_get_int(DDI_DEV_T_ANY, mlxp->mlx_dip,
DDI_PROP_DONTPASS, "fm_capable", def);
if (mlxp->mlx_fm_caps < 0) {
mlxp->mlx_fm_caps = 0;
}
mlxp->mlx_fm_caps &= def;
if (mlxp->mlx_fm_caps == 0)
return;
ddi_fm_init(mlxp->mlx_dip, &mlxp->mlx_fm_caps, &iblk);
if (DDI_FM_EREPORT_CAP(mlxp->mlx_fm_caps) ||
DDI_FM_ERRCB_CAP(mlxp->mlx_fm_caps)) {
pci_ereport_setup(mlxp->mlx_dip);
}
if (DDI_FM_ERRCB_CAP(mlxp->mlx_fm_caps)) {
ddi_fm_handler_register(mlxp->mlx_dip, mlxcx_fm_errcb,
(void *)mlxp);
}
}
static void
mlxcx_mlbs_teardown(mlxcx_t *mlxp, mlxcx_buf_shard_t *s)
{
mlxcx_buffer_t *buf;
mutex_enter(&s->mlbs_mtx);
while (!list_is_empty(&s->mlbs_busy))
cv_wait(&s->mlbs_free_nonempty, &s->mlbs_mtx);
while (!list_is_empty(&s->mlbs_loaned))
cv_wait(&s->mlbs_free_nonempty, &s->mlbs_mtx);
while ((buf = list_head(&s->mlbs_free)) != NULL)
mlxcx_buf_destroy(mlxp, buf);
list_destroy(&s->mlbs_free);
list_destroy(&s->mlbs_busy);
list_destroy(&s->mlbs_loaned);
mutex_exit(&s->mlbs_mtx);
cv_destroy(&s->mlbs_free_nonempty);
mutex_destroy(&s->mlbs_mtx);
}
static void
mlxcx_teardown_bufs(mlxcx_t *mlxp)
{
mlxcx_buf_shard_t *s;
while ((s = list_remove_head(&mlxp->mlx_buf_shards)) != NULL) {
mlxcx_mlbs_teardown(mlxp, s);
kmem_free(s, sizeof (mlxcx_buf_shard_t));
}
list_destroy(&mlxp->mlx_buf_shards);
kmem_cache_destroy(mlxp->mlx_bufs_cache);
}
static void
mlxcx_teardown_pages(mlxcx_t *mlxp)
{
uint_t nzeros = 0;
uint64_t *pas;
pas = kmem_alloc(sizeof (*pas) * MLXCX_MANAGE_PAGES_MAX_PAGES,
KM_SLEEP);
mutex_enter(&mlxp->mlx_pagemtx);
while (mlxp->mlx_npages > 0) {
int32_t req, ret;
ASSERT0(avl_is_empty(&mlxp->mlx_pages));
req = MIN(mlxp->mlx_npages, MLXCX_MANAGE_PAGES_MAX_PAGES);
if (!mlxcx_cmd_return_pages(mlxp, req, pas, &ret)) {
mlxcx_warn(mlxp, "hardware refused to return pages, "
"leaking %u remaining pages", mlxp->mlx_npages);
goto out;
}
for (int32_t i = 0; i < ret; i++) {
mlxcx_dev_page_t *mdp, probe;
bzero(&probe, sizeof (probe));
probe.mxdp_pa = pas[i];
mdp = avl_find(&mlxp->mlx_pages, &probe, NULL);
if (mdp != NULL) {
avl_remove(&mlxp->mlx_pages, mdp);
mlxp->mlx_npages--;
mlxcx_dma_free(&mdp->mxdp_dma);
kmem_free(mdp, sizeof (mlxcx_dev_page_t));
} else {
mlxcx_panic(mlxp, "hardware returned a page "
"with PA 0x%" PRIx64 " but we have no "
"record of giving out such a page", pas[i]);
}
}
if (ret == 0) {
nzeros++;
if (nzeros > mlxcx_reclaim_tries) {
mlxcx_warn(mlxp, "hardware refused to return "
"pages, leaking %u remaining pages",
mlxp->mlx_npages);
goto out;
}
delay(drv_usectohz(mlxcx_reclaim_delay));
}
}
avl_destroy(&mlxp->mlx_pages);
out:
mutex_exit(&mlxp->mlx_pagemtx);
mutex_destroy(&mlxp->mlx_pagemtx);
kmem_free(pas, sizeof (*pas) * MLXCX_MANAGE_PAGES_MAX_PAGES);
}
static boolean_t
mlxcx_eq_alloc_dma(mlxcx_t *mlxp, mlxcx_event_queue_t *mleq)
{
ddi_device_acc_attr_t acc;
ddi_dma_attr_t attr;
boolean_t ret;
size_t sz, i;
VERIFY0(mleq->mleq_state & MLXCX_EQ_ALLOC);
mleq->mleq_entshift = mlxp->mlx_props.mldp_eq_size_shift;
mleq->mleq_nents = (1 << mleq->mleq_entshift);
sz = mleq->mleq_nents * sizeof (mlxcx_eventq_ent_t);
ASSERT3U(sz & (MLXCX_HW_PAGE_SIZE - 1), ==, 0);
mlxcx_dma_acc_attr(mlxp, &acc);
mlxcx_dma_queue_attr(mlxp, &attr);
ret = mlxcx_dma_alloc(mlxp, &mleq->mleq_dma, &attr, &acc,
B_TRUE, sz, B_TRUE);
if (!ret) {
mlxcx_warn(mlxp, "failed to allocate EQ memory");
return (B_FALSE);
}
mleq->mleq_ent = (mlxcx_eventq_ent_t *)mleq->mleq_dma.mxdb_va;
for (i = 0; i < mleq->mleq_nents; ++i)
mleq->mleq_ent[i].mleqe_owner = MLXCX_EQ_OWNER_INIT;
mleq->mleq_state |= MLXCX_EQ_ALLOC;
return (B_TRUE);
}
static void
mlxcx_eq_rele_dma(mlxcx_t *mlxp, mlxcx_event_queue_t *mleq)
{
VERIFY(mleq->mleq_state & MLXCX_EQ_ALLOC);
if (mleq->mleq_state & MLXCX_EQ_CREATED)
VERIFY(mleq->mleq_state & MLXCX_EQ_DESTROYED);
mlxcx_dma_free(&mleq->mleq_dma);
mleq->mleq_ent = NULL;
mleq->mleq_state &= ~MLXCX_EQ_ALLOC;
}
void
mlxcx_teardown_flow_table(mlxcx_t *mlxp, mlxcx_flow_table_t *ft)
{
mlxcx_flow_group_t *fg;
mlxcx_flow_entry_t *fe;
int i;
ASSERT(mutex_owned(&ft->mlft_mtx));
for (i = ft->mlft_nents - 1; i >= 0; --i) {
fe = &ft->mlft_ent[i];
if (fe->mlfe_state & MLXCX_FLOW_ENTRY_CREATED) {
if (!mlxcx_cmd_delete_flow_table_entry(mlxp, fe)) {
mlxcx_panic(mlxp, "failed to delete flow "
"entry %u on table %u", i,
ft->mlft_num);
}
}
}
while ((fg = list_remove_head(&ft->mlft_groups)) != NULL) {
if (fg->mlfg_state & MLXCX_FLOW_GROUP_CREATED &&
!(fg->mlfg_state & MLXCX_FLOW_GROUP_DESTROYED)) {
if (!mlxcx_cmd_destroy_flow_group(mlxp, fg)) {
mlxcx_panic(mlxp, "failed to destroy flow "
"group %u", fg->mlfg_num);
}
}
kmem_free(fg, sizeof (mlxcx_flow_group_t));
}
list_destroy(&ft->mlft_groups);
if (ft->mlft_state & MLXCX_FLOW_TABLE_CREATED &&
!(ft->mlft_state & MLXCX_FLOW_TABLE_DESTROYED)) {
if (!mlxcx_cmd_destroy_flow_table(mlxp, ft)) {
mlxcx_panic(mlxp, "failed to destroy flow table %u",
ft->mlft_num);
}
}
kmem_free(ft->mlft_ent, ft->mlft_entsize);
ft->mlft_ent = NULL;
mutex_exit(&ft->mlft_mtx);
mutex_destroy(&ft->mlft_mtx);
kmem_free(ft, sizeof (mlxcx_flow_table_t));
}
static void
mlxcx_teardown_ports(mlxcx_t *mlxp)
{
uint_t i;
mlxcx_port_t *p;
mlxcx_flow_table_t *ft;
for (i = 0; i < mlxp->mlx_nports; ++i) {
p = &mlxp->mlx_ports[i];
if (!(p->mlp_init & MLXCX_PORT_INIT))
continue;
mutex_enter(&p->mlp_mtx);
if ((ft = p->mlp_rx_flow) != NULL) {
mutex_enter(&ft->mlft_mtx);
mlxcx_teardown_flow_table(mlxp, ft);
}
mutex_exit(&p->mlp_mtx);
mutex_destroy(&p->mlp_mtx);
mutex_destroy(&p->mlx_port_event.mla_mtx);
p->mlx_port_event.mla_mlx = NULL;
p->mlx_port_event.mla_port = NULL;
p->mlp_init &= ~MLXCX_PORT_INIT;
}
kmem_free(mlxp->mlx_ports, mlxp->mlx_ports_size);
mlxp->mlx_ports = NULL;
}
static void
mlxcx_teardown_wqs(mlxcx_t *mlxp)
{
mlxcx_work_queue_t *mlwq;
while ((mlwq = list_head(&mlxp->mlx_wqs)) != NULL) {
mlxcx_wq_teardown(mlxp, mlwq);
}
list_destroy(&mlxp->mlx_wqs);
}
static void
mlxcx_teardown_cqs(mlxcx_t *mlxp)
{
mlxcx_completion_queue_t *mlcq;
while ((mlcq = list_head(&mlxp->mlx_cqs)) != NULL) {
mlxcx_cq_teardown(mlxp, mlcq);
}
list_destroy(&mlxp->mlx_cqs);
}
static void
mlxcx_teardown_eqs(mlxcx_t *mlxp)
{
mlxcx_event_queue_t *mleq;
uint_t i;
for (i = 0; i < mlxp->mlx_intr_count; ++i) {
mleq = &mlxp->mlx_eqs[i];
mutex_enter(&mleq->mleq_mtx);
if ((mleq->mleq_state & MLXCX_EQ_CREATED) &&
!(mleq->mleq_state & MLXCX_EQ_DESTROYED)) {
if (!mlxcx_cmd_destroy_eq(mlxp, mleq)) {
mlxcx_warn(mlxp, "failed to destroy "
"event queue idx %u eqn %u",
i, mleq->mleq_num);
}
}
if (mleq->mleq_state & MLXCX_EQ_ALLOC) {
mlxcx_eq_rele_dma(mlxp, mleq);
}
mutex_exit(&mleq->mleq_mtx);
}
}
static void
mlxcx_teardown_checktimers(mlxcx_t *mlxp)
{
if (mlxp->mlx_props.mldp_eq_check_interval_sec > 0)
ddi_periodic_delete(mlxp->mlx_eq_checktimer);
if (mlxp->mlx_props.mldp_cq_check_interval_sec > 0)
ddi_periodic_delete(mlxp->mlx_cq_checktimer);
if (mlxp->mlx_props.mldp_wq_check_interval_sec > 0)
ddi_periodic_delete(mlxp->mlx_wq_checktimer);
}
static void
mlxcx_teardown(mlxcx_t *mlxp)
{
uint_t i;
dev_info_t *dip = mlxp->mlx_dip;
if (mlxp->mlx_attach & MLXCX_ATTACH_INTRS) {
mlxcx_intr_disable(mlxp);
}
if (mlxp->mlx_attach & MLXCX_ATTACH_SENSORS) {
mlxcx_teardown_sensors(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_SENSORS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_CHKTIMERS) {
mlxcx_teardown_checktimers(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_CHKTIMERS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_GROUPS) {
mlxcx_teardown_groups(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_GROUPS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_WQS) {
mlxcx_teardown_wqs(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_WQS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_CQS) {
mlxcx_teardown_cqs(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_CQS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_BUFS) {
mlxcx_teardown_bufs(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_BUFS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_PORTS) {
mlxcx_teardown_ports(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_PORTS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_INTRS) {
mlxcx_teardown_eqs(mlxp);
mlxcx_intr_teardown(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_INTRS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_UAR_PD_TD) {
if (mlxp->mlx_uar.mlu_allocated) {
if (!mlxcx_cmd_dealloc_uar(mlxp, &mlxp->mlx_uar)) {
mlxcx_warn(mlxp, "failed to release UAR");
}
for (i = 0; i < MLXCX_BF_PER_UAR; ++i)
mutex_destroy(&mlxp->mlx_uar.mlu_bf[i].mbf_mtx);
}
if (mlxp->mlx_pd.mlpd_allocated &&
!mlxcx_cmd_dealloc_pd(mlxp, &mlxp->mlx_pd)) {
mlxcx_warn(mlxp, "failed to release PD");
}
if (mlxp->mlx_tdom.mltd_allocated &&
!mlxcx_cmd_dealloc_tdom(mlxp, &mlxp->mlx_tdom)) {
mlxcx_warn(mlxp, "failed to release TDOM");
}
mlxp->mlx_attach &= ~MLXCX_ATTACH_UAR_PD_TD;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_INIT_HCA) {
if (!mlxcx_cmd_teardown_hca(mlxp)) {
mlxcx_warn(mlxp, "failed to send teardown HCA "
"command during device detach");
}
mlxp->mlx_attach &= ~MLXCX_ATTACH_INIT_HCA;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_PAGE_LIST) {
mlxcx_teardown_pages(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_PAGE_LIST;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_ASYNC_TQ) {
for (i = 0; i <= MLXCX_FUNC_ID_MAX; i++) {
mlxp->mlx_npages_req[i].mla_mlx = NULL;
mutex_destroy(&mlxp->mlx_npages_req[i].mla_mtx);
}
taskq_destroy(mlxp->mlx_async_tq);
mlxp->mlx_async_tq = NULL;
mlxp->mlx_attach &= ~MLXCX_ATTACH_ASYNC_TQ;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_ENABLE_HCA) {
if (!mlxcx_cmd_disable_hca(mlxp)) {
mlxcx_warn(mlxp, "failed to send DISABLE HCA command "
"during device detach");
}
mlxp->mlx_attach &= ~MLXCX_ATTACH_ENABLE_HCA;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_CMD) {
mlxcx_cmd_queue_fini(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_CMD;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_CAPS) {
kmem_free(mlxp->mlx_caps, sizeof (mlxcx_caps_t));
mlxp->mlx_caps = NULL;
mlxp->mlx_attach &= ~MLXCX_ATTACH_CAPS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_REGS) {
ddi_regs_map_free(&mlxp->mlx_regs_handle);
mlxp->mlx_regs_handle = NULL;
mlxp->mlx_attach &= ~MLXCX_ATTACH_REGS;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_PCI_CONFIG) {
pci_config_teardown(&mlxp->mlx_cfg_handle);
mlxp->mlx_cfg_handle = NULL;
mlxp->mlx_attach &= ~MLXCX_ATTACH_PCI_CONFIG;
}
if (mlxp->mlx_attach & MLXCX_ATTACH_FM) {
mlxcx_fm_fini(mlxp);
mlxp->mlx_attach &= ~MLXCX_ATTACH_FM;
}
VERIFY3S(mlxp->mlx_attach, ==, 0);
ddi_soft_state_free(mlxcx_softstate, mlxp->mlx_inst);
ddi_set_driver_private(dip, NULL);
}
static void
mlxcx_get_model(mlxcx_t *mlxp)
{
uint16_t venid;
uint16_t devid;
venid = pci_config_get16(mlxp->mlx_cfg_handle, PCI_CONF_VENID);
if (venid != MLXCX_VENDOR_ID) {
mlxp->mlx_type = MLXCX_DEV_UNKNOWN;
return;
}
devid = pci_config_get16(mlxp->mlx_cfg_handle, PCI_CONF_DEVID);
switch (devid) {
case MLXCX_CX4_DEVID:
case MLXCX_CX4_VF_DEVID:
case MLXCX_CX4_LX_VF_DEVID:
mlxp->mlx_type = MLXCX_DEV_CX4;
break;
case MLXCX_CX5_DEVID:
case MLXCX_CX5_VF_DEVID:
case MLXCX_CX5_EX_DEVID:
case MLXCX_CX5_EX_VF_DEVID:
case MLXCX_CX5_GEN_VF_DEVID:
mlxp->mlx_type = MLXCX_DEV_CX5;
break;
case MLXCX_CX6_DEVID:
case MLXCX_CX6_VF_DEVID:
case MLXCX_CX6_DF_DEVID:
case MLXCX_CX6_LX_DEVID:
mlxp->mlx_type = MLXCX_DEV_CX6;
break;
default:
mlxp->mlx_type = MLXCX_DEV_UNKNOWN;
}
}
static boolean_t
mlxcx_regs_map(mlxcx_t *mlxp)
{
off_t memsize;
int ret;
ddi_device_acc_attr_t da;
if (ddi_dev_regsize(mlxp->mlx_dip, MLXCX_REG_NUMBER, &memsize) !=
DDI_SUCCESS) {
mlxcx_warn(mlxp, "failed to get register set size");
return (B_FALSE);
}
bzero(&da, sizeof (ddi_device_acc_attr_t));
da.devacc_attr_version = DDI_DEVICE_ATTR_V0;
da.devacc_attr_endian_flags = DDI_STRUCTURE_BE_ACC;
da.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (DDI_FM_ACC_ERR_CAP(mlxp->mlx_fm_caps)) {
da.devacc_attr_access = DDI_FLAGERR_ACC;
} else {
da.devacc_attr_access = DDI_DEFAULT_ACC;
}
ret = ddi_regs_map_setup(mlxp->mlx_dip, MLXCX_REG_NUMBER,
&mlxp->mlx_regs_base, 0, memsize, &da, &mlxp->mlx_regs_handle);
if (ret != DDI_SUCCESS) {
mlxcx_warn(mlxp, "failed to map device registers: %d", ret);
return (B_FALSE);
}
return (B_TRUE);
}
static boolean_t
mlxcx_check_issi(mlxcx_t *mlxp)
{
uint32_t issi;
if (!mlxcx_cmd_query_issi(mlxp, &issi)) {
mlxcx_warn(mlxp, "failed to get ISSI");
return (B_FALSE);
}
if ((issi & (1 << MLXCX_CURRENT_ISSI)) == 0) {
mlxcx_warn(mlxp, "hardware does not support software ISSI, "
"hw vector 0x%x, sw version %u", issi, MLXCX_CURRENT_ISSI);
return (B_FALSE);
}
if (!mlxcx_cmd_set_issi(mlxp, MLXCX_CURRENT_ISSI)) {
mlxcx_warn(mlxp, "failed to set ISSI to %u",
MLXCX_CURRENT_ISSI);
return (B_FALSE);
}
return (B_TRUE);
}
boolean_t
mlxcx_give_pages(mlxcx_t *mlxp, int32_t npages, int32_t *ngiven)
{
ddi_device_acc_attr_t acc;
ddi_dma_attr_t attr;
int32_t i;
list_t plist;
mlxcx_dev_page_t *mdp;
mlxcx_dev_page_t **pages;
const ddi_dma_cookie_t *ck;
if (npages <= 0) {
*ngiven = 0;
return (B_TRUE);
}
npages = MIN(npages, MLXCX_MANAGE_PAGES_MAX_PAGES);
pages = kmem_alloc(sizeof (*pages) * npages, KM_SLEEP);
list_create(&plist, sizeof (mlxcx_dev_page_t),
offsetof(mlxcx_dev_page_t, mxdp_list));
for (i = 0; i < npages; i++) {
mdp = kmem_zalloc(sizeof (mlxcx_dev_page_t), KM_SLEEP);
mlxcx_dma_acc_attr(mlxp, &acc);
mlxcx_dma_page_attr(mlxp, &attr);
if (!mlxcx_dma_alloc(mlxp, &mdp->mxdp_dma, &attr, &acc,
B_TRUE, MLXCX_HW_PAGE_SIZE, B_TRUE)) {
mlxcx_warn(mlxp, "failed to allocate 4k page %u/%u", i,
npages);
kmem_free(mdp, sizeof (mlxcx_dev_page_t));
goto cleanup_npages;
}
ck = mlxcx_dma_cookie_one(&mdp->mxdp_dma);
mdp->mxdp_pa = ck->dmac_laddress;
list_insert_tail(&plist, mdp);
}
for (i = 0; i < npages; i++) {
pages[i] = list_remove_head(&plist);
}
if (!mlxcx_cmd_give_pages(mlxp,
MLXCX_MANAGE_PAGES_OPMOD_GIVE_PAGES, npages, pages)) {
mlxcx_warn(mlxp, "!hardware refused our gift of %u "
"pages!", npages);
for (i = 0; i < npages; i++) {
list_insert_tail(&plist, pages[i]);
}
goto cleanup_npages;
}
mutex_enter(&mlxp->mlx_pagemtx);
for (i = 0; i < npages; i++) {
avl_add(&mlxp->mlx_pages, pages[i]);
}
mlxp->mlx_npages += npages;
mutex_exit(&mlxp->mlx_pagemtx);
list_destroy(&plist);
kmem_free(pages, sizeof (*pages) * npages);
*ngiven = npages;
return (B_TRUE);
cleanup_npages:
kmem_free(pages, sizeof (*pages) * npages);
while ((mdp = list_remove_head(&plist)) != NULL) {
mlxcx_dma_free(&mdp->mxdp_dma);
kmem_free(mdp, sizeof (mlxcx_dev_page_t));
}
list_destroy(&plist);
return (B_FALSE);
}
static boolean_t
mlxcx_init_pages(mlxcx_t *mlxp, uint_t type)
{
int32_t npages, given;
if (!mlxcx_cmd_query_pages(mlxp, type, &npages)) {
mlxcx_warn(mlxp, "failed to determine boot pages");
return (B_FALSE);
}
while (npages > 0) {
if (!mlxcx_give_pages(mlxp, npages, &given))
return (B_FALSE);
npages -= given;
}
return (B_TRUE);
}
static int
mlxcx_bufs_cache_constr(void *arg, void *cookie, int kmflags)
{
mlxcx_t *mlxp = cookie;
mlxcx_buffer_t *b = arg;
bzero(b, sizeof (mlxcx_buffer_t));
b->mlb_mlx = mlxp;
b->mlb_state = MLXCX_BUFFER_INIT;
list_create(&b->mlb_tx_chain, sizeof (mlxcx_buffer_t),
offsetof(mlxcx_buffer_t, mlb_tx_chain_entry));
return (0);
}
static void
mlxcx_bufs_cache_destr(void *arg, void *cookie)
{
mlxcx_t *mlxp = cookie;
mlxcx_buffer_t *b = arg;
VERIFY3P(b->mlb_mlx, ==, mlxp);
VERIFY(b->mlb_state == MLXCX_BUFFER_INIT);
list_destroy(&b->mlb_tx_chain);
}
mlxcx_buf_shard_t *
mlxcx_mlbs_create(mlxcx_t *mlxp)
{
mlxcx_buf_shard_t *s;
s = kmem_zalloc(sizeof (mlxcx_buf_shard_t), KM_SLEEP);
mutex_init(&s->mlbs_mtx, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(mlxp->mlx_intr_pri));
list_create(&s->mlbs_busy, sizeof (mlxcx_buffer_t),
offsetof(mlxcx_buffer_t, mlb_entry));
list_create(&s->mlbs_free, sizeof (mlxcx_buffer_t),
offsetof(mlxcx_buffer_t, mlb_entry));
list_create(&s->mlbs_loaned, sizeof (mlxcx_buffer_t),
offsetof(mlxcx_buffer_t, mlb_entry));
cv_init(&s->mlbs_free_nonempty, NULL, CV_DRIVER, NULL);
list_insert_tail(&mlxp->mlx_buf_shards, s);
return (s);
}
static boolean_t
mlxcx_setup_bufs(mlxcx_t *mlxp)
{
char namebuf[KSTAT_STRLEN];
(void) snprintf(namebuf, KSTAT_STRLEN, "mlxcx%d_bufs_cache",
ddi_get_instance(mlxp->mlx_dip));
mlxp->mlx_bufs_cache = kmem_cache_create(namebuf,
sizeof (mlxcx_buffer_t), sizeof (uint64_t),
mlxcx_bufs_cache_constr, mlxcx_bufs_cache_destr,
NULL, mlxp, NULL, 0);
list_create(&mlxp->mlx_buf_shards, sizeof (mlxcx_buf_shard_t),
offsetof(mlxcx_buf_shard_t, mlbs_entry));
return (B_TRUE);
}
static void
mlxcx_fm_qstate_ereport(mlxcx_t *mlxp, const char *qtype, uint32_t qnum,
const char *state, uint8_t statenum)
{
uint64_t ena;
char buf[FM_MAX_CLASS];
if (!DDI_FM_EREPORT_CAP(mlxp->mlx_fm_caps))
return;
(void) snprintf(buf, FM_MAX_CLASS, "%s.%s",
MLXCX_FM_SERVICE_MLXCX, "qstate.err");
ena = fm_ena_generate(0, FM_ENA_FMT1);
ddi_fm_ereport_post(mlxp->mlx_dip, buf, ena, DDI_NOSLEEP,
FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0,
"state", DATA_TYPE_STRING, state,
"state_num", DATA_TYPE_UINT8, statenum,
"qtype", DATA_TYPE_STRING, qtype,
"qnum", DATA_TYPE_UINT32, qnum,
NULL);
ddi_fm_service_impact(mlxp->mlx_dip, DDI_SERVICE_DEGRADED);
}
static void
mlxcx_eq_check(void *arg)
{
mlxcx_t *mlxp = (mlxcx_t *)arg;
mlxcx_event_queue_t *eq;
mlxcx_eventq_ctx_t ctx;
const char *str;
uint_t i;
for (i = 0; i < mlxp->mlx_intr_count; ++i) {
eq = &mlxp->mlx_eqs[i];
if ((eq->mleq_state & MLXCX_EQ_CREATED) == 0)
continue;
ASSERT0(eq->mleq_state & MLXCX_EQ_DESTROYED);
if (!mlxcx_cmd_query_eq(mlxp, eq, &ctx))
continue;
str = "???";
switch (ctx.mleqc_status) {
case MLXCX_EQ_STATUS_OK:
break;
case MLXCX_EQ_STATUS_WRITE_FAILURE:
str = "WRITE_FAILURE";
break;
}
if (ctx.mleqc_status != MLXCX_EQ_STATUS_OK) {
mlxcx_fm_qstate_ereport(mlxp, "event",
eq->mleq_num, str, ctx.mleqc_status);
mlxcx_warn(mlxp, "EQ %u is in bad status: %x (%s)",
eq->mleq_intr_index, ctx.mleqc_status, str);
}
if (ctx.mleqc_state != MLXCX_EQ_ST_ARMED &&
(eq->mleq_state & MLXCX_EQ_ARMED)) {
if (eq->mleq_cc == eq->mleq_check_disarm_cc &&
++eq->mleq_check_disarm_cnt >= 3) {
mlxcx_fm_ereport(mlxp, DDI_FM_DEVICE_STALL);
mlxcx_warn(mlxp, "EQ %u isn't armed",
eq->mleq_intr_index);
}
eq->mleq_check_disarm_cc = eq->mleq_cc;
} else {
eq->mleq_check_disarm_cc = 0;
eq->mleq_check_disarm_cnt = 0;
}
}
}
static void
mlxcx_cq_check(void *arg)
{
mlxcx_t *mlxp = (mlxcx_t *)arg;
mlxcx_completion_queue_t *cq;
mlxcx_completionq_ctx_t ctx;
const char *str, *type;
uint_t v;
for (cq = list_head(&mlxp->mlx_cqs); cq != NULL;
cq = list_next(&mlxp->mlx_cqs, cq)) {
if ((cq->mlcq_state & MLXCX_CQ_CREATED) == 0)
continue;
ASSERT0(cq->mlcq_state & MLXCX_CQ_DESTROYED);
ASSERT0(cq->mlcq_state & MLXCX_CQ_TEARDOWN);
if (cq->mlcq_fm_repd_qstate)
continue;
if (!mlxcx_cmd_query_cq(mlxp, cq, &ctx))
continue;
if (cq->mlcq_wq != NULL) {
mlxcx_work_queue_t *wq = cq->mlcq_wq;
if (wq->mlwq_type == MLXCX_WQ_TYPE_RECVQ)
type = "rx ";
else if (wq->mlwq_type == MLXCX_WQ_TYPE_SENDQ)
type = "tx ";
else
type = "";
} else {
type = "";
}
str = "???";
v = get_bits32(ctx.mlcqc_flags, MLXCX_CQ_CTX_STATUS);
switch (v) {
case MLXCX_CQC_STATUS_OK:
break;
case MLXCX_CQC_STATUS_OVERFLOW:
str = "OVERFLOW";
break;
case MLXCX_CQC_STATUS_WRITE_FAIL:
str = "WRITE_FAIL";
break;
case MLXCX_CQC_STATUS_INVALID:
str = "INVALID";
break;
}
if (v != MLXCX_CQC_STATUS_OK) {
mlxcx_fm_qstate_ereport(mlxp, "completion",
cq->mlcq_num, str, v);
mlxcx_warn(mlxp, "%sCQ 0x%x is in bad status: %x (%s)",
type, cq->mlcq_num, v, str);
cq->mlcq_fm_repd_qstate = B_TRUE;
}
v = get_bits32(ctx.mlcqc_flags, MLXCX_CQ_CTX_STATE);
if (v != MLXCX_CQC_STATE_ARMED &&
(cq->mlcq_state & MLXCX_CQ_ARMED) &&
!(cq->mlcq_state & MLXCX_CQ_POLLING)) {
if (cq->mlcq_cc == cq->mlcq_check_disarm_cc &&
++cq->mlcq_check_disarm_cnt >= 3) {
mlxcx_fm_ereport(mlxp, DDI_FM_DEVICE_STALL);
mlxcx_warn(mlxp, "%sCQ 0x%x (%p) isn't armed",
type, cq->mlcq_num, cq);
}
cq->mlcq_check_disarm_cc = cq->mlcq_cc;
} else {
cq->mlcq_check_disarm_cnt = 0;
cq->mlcq_check_disarm_cc = 0;
}
}
}
void
mlxcx_check_sq(mlxcx_t *mlxp, mlxcx_work_queue_t *sq)
{
mlxcx_sq_ctx_t ctx;
mlxcx_sq_state_t state;
if (!mlxcx_cmd_query_sq(mlxp, sq, &ctx))
return;
ASSERT3U(from_be24(ctx.mlsqc_cqn), ==, sq->mlwq_cq->mlcq_num);
state = get_bits32(ctx.mlsqc_flags, MLXCX_SQ_STATE);
switch (state) {
case MLXCX_SQ_STATE_RST:
if (sq->mlwq_state & MLXCX_WQ_STARTED) {
mlxcx_fm_qstate_ereport(mlxp, "send",
sq->mlwq_num, "RST", state);
sq->mlwq_fm_repd_qstate = B_TRUE;
}
break;
case MLXCX_SQ_STATE_RDY:
if (!(sq->mlwq_state & MLXCX_WQ_STARTED)) {
mlxcx_fm_qstate_ereport(mlxp, "send",
sq->mlwq_num, "RDY", state);
sq->mlwq_fm_repd_qstate = B_TRUE;
}
break;
case MLXCX_SQ_STATE_ERR:
mlxcx_fm_qstate_ereport(mlxp, "send",
sq->mlwq_num, "ERR", state);
sq->mlwq_fm_repd_qstate = B_TRUE;
break;
default:
mlxcx_fm_qstate_ereport(mlxp, "send",
sq->mlwq_num, "???", state);
sq->mlwq_fm_repd_qstate = B_TRUE;
break;
}
}
void
mlxcx_check_rq(mlxcx_t *mlxp, mlxcx_work_queue_t *rq)
{
mlxcx_rq_ctx_t ctx;
mlxcx_rq_state_t state;
if (!mlxcx_cmd_query_rq(mlxp, rq, &ctx))
return;
ASSERT3U(from_be24(ctx.mlrqc_cqn), ==, rq->mlwq_cq->mlcq_num);
state = get_bits32(ctx.mlrqc_flags, MLXCX_RQ_STATE);
switch (state) {
case MLXCX_RQ_STATE_RST:
if (rq->mlwq_state & MLXCX_WQ_STARTED) {
mlxcx_fm_qstate_ereport(mlxp, "receive",
rq->mlwq_num, "RST", state);
rq->mlwq_fm_repd_qstate = B_TRUE;
}
break;
case MLXCX_RQ_STATE_RDY:
if (!(rq->mlwq_state & MLXCX_WQ_STARTED)) {
mlxcx_fm_qstate_ereport(mlxp, "receive",
rq->mlwq_num, "RDY", state);
rq->mlwq_fm_repd_qstate = B_TRUE;
}
break;
case MLXCX_RQ_STATE_ERR:
mlxcx_fm_qstate_ereport(mlxp, "receive",
rq->mlwq_num, "ERR", state);
rq->mlwq_fm_repd_qstate = B_TRUE;
break;
default:
mlxcx_fm_qstate_ereport(mlxp, "receive",
rq->mlwq_num, "???", state);
rq->mlwq_fm_repd_qstate = B_TRUE;
break;
}
}
static void
mlxcx_wq_check(void *arg)
{
mlxcx_t *mlxp = (mlxcx_t *)arg;
mlxcx_work_queue_t *wq;
for (wq = list_head(&mlxp->mlx_wqs); wq != NULL;
wq = list_next(&mlxp->mlx_wqs, wq)) {
if ((wq->mlwq_state & MLXCX_WQ_CREATED) == 0)
continue;
ASSERT0(wq->mlwq_state & MLXCX_WQ_DESTROYED);
ASSERT0(wq->mlwq_state & MLXCX_WQ_TEARDOWN);
if (wq->mlwq_fm_repd_qstate)
continue;
switch (wq->mlwq_type) {
case MLXCX_WQ_TYPE_SENDQ:
mlxcx_check_sq(mlxp, wq);
break;
case MLXCX_WQ_TYPE_RECVQ:
mlxcx_check_rq(mlxp, wq);
break;
}
}
}
static boolean_t
mlxcx_setup_checktimers(mlxcx_t *mlxp)
{
if (mlxp->mlx_props.mldp_eq_check_interval_sec > 0) {
mlxp->mlx_eq_checktimer = ddi_periodic_add(mlxcx_eq_check, mlxp,
mlxp->mlx_props.mldp_eq_check_interval_sec * NANOSEC,
DDI_IPL_0);
}
if (mlxp->mlx_props.mldp_cq_check_interval_sec > 0) {
mlxp->mlx_cq_checktimer = ddi_periodic_add(mlxcx_cq_check, mlxp,
mlxp->mlx_props.mldp_cq_check_interval_sec * NANOSEC,
DDI_IPL_0);
}
if (mlxp->mlx_props.mldp_wq_check_interval_sec > 0) {
mlxp->mlx_wq_checktimer = ddi_periodic_add(mlxcx_wq_check, mlxp,
mlxp->mlx_props.mldp_wq_check_interval_sec * NANOSEC,
DDI_IPL_0);
}
return (B_TRUE);
}
int
mlxcx_dmac_fe_compare(const void *arg0, const void *arg1)
{
const mlxcx_flow_entry_t *left = arg0;
const mlxcx_flow_entry_t *right = arg1;
int bcmpr;
bcmpr = memcmp(left->mlfe_dmac, right->mlfe_dmac,
sizeof (left->mlfe_dmac));
if (bcmpr < 0)
return (-1);
if (bcmpr > 0)
return (1);
if (left->mlfe_vid < right->mlfe_vid)
return (-1);
if (left->mlfe_vid > right->mlfe_vid)
return (1);
return (0);
}
int
mlxcx_grmac_compare(const void *arg0, const void *arg1)
{
const mlxcx_group_mac_t *left = arg0;
const mlxcx_group_mac_t *right = arg1;
int bcmpr;
bcmpr = memcmp(left->mlgm_mac, right->mlgm_mac,
sizeof (left->mlgm_mac));
if (bcmpr < 0)
return (-1);
if (bcmpr > 0)
return (1);
return (0);
}
int
mlxcx_page_compare(const void *arg0, const void *arg1)
{
const mlxcx_dev_page_t *p0 = arg0;
const mlxcx_dev_page_t *p1 = arg1;
if (p0->mxdp_pa < p1->mxdp_pa)
return (-1);
if (p0->mxdp_pa > p1->mxdp_pa)
return (1);
return (0);
}
static boolean_t
mlxcx_setup_ports(mlxcx_t *mlxp)
{
uint_t i, j;
mlxcx_port_t *p;
mlxcx_flow_table_t *ft;
mlxcx_flow_group_t *fg;
mlxcx_flow_entry_t *fe;
VERIFY3U(mlxp->mlx_nports, >, 0);
mlxp->mlx_ports_size = mlxp->mlx_nports * sizeof (mlxcx_port_t);
mlxp->mlx_ports = kmem_zalloc(mlxp->mlx_ports_size, KM_SLEEP);
for (i = 0; i < mlxp->mlx_nports; ++i) {
p = &mlxp->mlx_ports[i];
p->mlp_num = i;
p->mlx_port_event.mla_mlx = mlxp;
p->mlx_port_event.mla_port = p;
mutex_init(&p->mlx_port_event.mla_mtx, NULL,
MUTEX_DRIVER, DDI_INTR_PRI(mlxp->mlx_async_intr_pri));
p->mlp_init |= MLXCX_PORT_INIT;
mutex_init(&p->mlp_mtx, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(mlxp->mlx_intr_pri));
mutex_enter(&p->mlp_mtx);
if (!mlxcx_cmd_query_nic_vport_ctx(mlxp, p)) {
mutex_exit(&p->mlp_mtx);
goto err;
}
if (!mlxcx_cmd_query_port_mtu(mlxp, p)) {
mutex_exit(&p->mlp_mtx);
goto err;
}
if (!mlxcx_cmd_query_port_status(mlxp, p)) {
mutex_exit(&p->mlp_mtx);
goto err;
}
if (!mlxcx_cmd_query_port_speed(mlxp, p)) {
mutex_exit(&p->mlp_mtx);
goto err;
}
if (!mlxcx_cmd_modify_nic_vport_ctx(mlxp, p,
MLXCX_MODIFY_NIC_VPORT_CTX_PROMISC)) {
mutex_exit(&p->mlp_mtx);
goto err;
}
if (!mlxcx_cmd_query_port_fec(mlxp, p)) {
mutex_exit(&p->mlp_mtx);
goto err;
}
p->mlp_fec_requested = LINK_FEC_AUTO;
mutex_exit(&p->mlp_mtx);
}
for (i = 0; i < mlxp->mlx_nports; ++i) {
p = &mlxp->mlx_ports[i];
mutex_enter(&p->mlp_mtx);
p->mlp_rx_flow = (ft = kmem_zalloc(sizeof (mlxcx_flow_table_t),
KM_SLEEP));
mutex_init(&ft->mlft_mtx, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(mlxp->mlx_intr_pri));
mutex_enter(&ft->mlft_mtx);
ft->mlft_type = MLXCX_FLOW_TABLE_NIC_RX;
ft->mlft_port = p;
ft->mlft_entshift = mlxp->mlx_props.mldp_ftbl_root_size_shift;
if (ft->mlft_entshift > mlxp->mlx_caps->mlc_max_rx_ft_shift)
ft->mlft_entshift = mlxp->mlx_caps->mlc_max_rx_ft_shift;
ft->mlft_nents = (1 << ft->mlft_entshift);
ft->mlft_entsize = ft->mlft_nents * sizeof (mlxcx_flow_entry_t);
ft->mlft_ent = kmem_zalloc(ft->mlft_entsize, KM_SLEEP);
list_create(&ft->mlft_groups, sizeof (mlxcx_flow_group_t),
offsetof(mlxcx_flow_group_t, mlfg_entry));
for (j = 0; j < ft->mlft_nents; ++j) {
ft->mlft_ent[j].mlfe_table = ft;
ft->mlft_ent[j].mlfe_index = j;
}
if (!mlxcx_cmd_create_flow_table(mlxp, ft)) {
mutex_exit(&ft->mlft_mtx);
mutex_exit(&p->mlp_mtx);
goto err;
}
if (!mlxcx_cmd_set_flow_table_root(mlxp, ft)) {
mutex_exit(&ft->mlft_mtx);
mutex_exit(&p->mlp_mtx);
goto err;
}
fg = kmem_zalloc(sizeof (mlxcx_flow_group_t), KM_SLEEP);
list_insert_tail(&ft->mlft_groups, fg);
fg->mlfg_table = ft;
fg->mlfg_size = 1;
fg->mlfg_mask |= MLXCX_FLOW_MATCH_DMAC;
if (!mlxcx_setup_flow_group(mlxp, ft, fg)) {
mutex_exit(&ft->mlft_mtx);
mutex_exit(&p->mlp_mtx);
goto err;
}
p->mlp_bcast = fg;
fe = list_head(&fg->mlfg_entries);
fe->mlfe_action = MLXCX_FLOW_ACTION_FORWARD;
(void) memset(fe->mlfe_dmac, 0xff, sizeof (fe->mlfe_dmac));
fe->mlfe_state |= MLXCX_FLOW_ENTRY_DIRTY;
fg = kmem_zalloc(sizeof (mlxcx_flow_group_t), KM_SLEEP);
list_insert_tail(&ft->mlft_groups, fg);
fg->mlfg_table = ft;
fg->mlfg_size = ft->mlft_nents - 2;
fg->mlfg_mask |= MLXCX_FLOW_MATCH_DMAC;
if (!mlxcx_setup_flow_group(mlxp, ft, fg)) {
mutex_exit(&ft->mlft_mtx);
mutex_exit(&p->mlp_mtx);
goto err;
}
p->mlp_umcast = fg;
fg = kmem_zalloc(sizeof (mlxcx_flow_group_t), KM_SLEEP);
list_insert_tail(&ft->mlft_groups, fg);
fg->mlfg_table = ft;
fg->mlfg_size = 1;
if (!mlxcx_setup_flow_group(mlxp, ft, fg)) {
mutex_exit(&ft->mlft_mtx);
mutex_exit(&p->mlp_mtx);
goto err;
}
p->mlp_promisc = fg;
fe = list_head(&fg->mlfg_entries);
fe->mlfe_action = MLXCX_FLOW_ACTION_FORWARD;
fe->mlfe_state |= MLXCX_FLOW_ENTRY_DIRTY;
avl_create(&p->mlp_dmac_fe, mlxcx_dmac_fe_compare,
sizeof (mlxcx_flow_entry_t), offsetof(mlxcx_flow_entry_t,
mlfe_dmac_entry));
mutex_exit(&ft->mlft_mtx);
mutex_exit(&p->mlp_mtx);
}
return (B_TRUE);
err:
mlxcx_teardown_ports(mlxp);
return (B_FALSE);
}
void
mlxcx_remove_all_vlan_entries(mlxcx_t *mlxp, mlxcx_ring_group_t *g)
{
mlxcx_flow_table_t *ft = g->mlg_rx_vlan_ft;
mlxcx_flow_group_t *fg = g->mlg_rx_vlan_fg;
mlxcx_flow_group_t *dfg = g->mlg_rx_vlan_def_fg;
mlxcx_flow_entry_t *fe;
mlxcx_group_vlan_t *v;
ASSERT(mutex_owned(&g->mlg_mtx));
mutex_enter(&ft->mlft_mtx);
if (!list_is_empty(&g->mlg_rx_vlans)) {
fe = list_head(&dfg->mlfg_entries);
(void) mlxcx_cmd_set_flow_table_entry(mlxp, fe);
}
while ((v = list_remove_head(&g->mlg_rx_vlans)) != NULL) {
fe = v->mlgv_fe;
ASSERT3P(fe->mlfe_table, ==, ft);
ASSERT3P(fe->mlfe_group, ==, fg);
kmem_free(v, sizeof (mlxcx_group_vlan_t));
(void) mlxcx_cmd_delete_flow_table_entry(mlxp, fe);
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_RESERVED;
}
mutex_exit(&ft->mlft_mtx);
}
boolean_t
mlxcx_remove_vlan_entry(mlxcx_t *mlxp, mlxcx_ring_group_t *g,
boolean_t tagged, uint16_t vid)
{
mlxcx_flow_table_t *ft = g->mlg_rx_vlan_ft;
mlxcx_flow_group_t *fg = g->mlg_rx_vlan_fg;
mlxcx_flow_group_t *dfg = g->mlg_rx_vlan_def_fg;
mlxcx_flow_entry_t *fe;
mlxcx_group_vlan_t *v;
boolean_t found = B_FALSE;
ASSERT(mutex_owned(&g->mlg_mtx));
mutex_enter(&ft->mlft_mtx);
for (v = list_head(&g->mlg_rx_vlans); v != NULL;
v = list_next(&g->mlg_rx_vlans, v)) {
if (v->mlgv_tagged == tagged && v->mlgv_vid == vid) {
found = B_TRUE;
break;
}
}
if (!found) {
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
list_remove(&g->mlg_rx_vlans, v);
if (list_is_empty(&g->mlg_rx_vlans)) {
fe = list_head(&dfg->mlfg_entries);
if (!mlxcx_cmd_set_flow_table_entry(mlxp, fe)) {
list_insert_tail(&g->mlg_rx_vlans, v);
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
}
fe = v->mlgv_fe;
ASSERT(fe->mlfe_state & MLXCX_FLOW_ENTRY_RESERVED);
ASSERT(fe->mlfe_state & MLXCX_FLOW_ENTRY_CREATED);
ASSERT3P(fe->mlfe_table, ==, ft);
ASSERT3P(fe->mlfe_group, ==, fg);
if (!mlxcx_cmd_delete_flow_table_entry(mlxp, fe)) {
list_insert_tail(&g->mlg_rx_vlans, v);
fe = list_head(&dfg->mlfg_entries);
if (fe->mlfe_state & MLXCX_FLOW_ENTRY_CREATED) {
(void) mlxcx_cmd_delete_flow_table_entry(mlxp, fe);
}
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_RESERVED;
kmem_free(v, sizeof (mlxcx_group_vlan_t));
mutex_exit(&ft->mlft_mtx);
return (B_TRUE);
}
boolean_t
mlxcx_add_vlan_entry(mlxcx_t *mlxp, mlxcx_ring_group_t *g, boolean_t tagged,
uint16_t vid)
{
mlxcx_flow_table_t *ft = g->mlg_rx_vlan_ft;
mlxcx_flow_group_t *fg = g->mlg_rx_vlan_fg;
mlxcx_flow_group_t *dfg = g->mlg_rx_vlan_def_fg;
mlxcx_flow_entry_t *fe;
mlxcx_group_vlan_t *v;
boolean_t found = B_FALSE;
boolean_t first = B_FALSE;
ASSERT(mutex_owned(&g->mlg_mtx));
mutex_enter(&ft->mlft_mtx);
for (v = list_head(&g->mlg_rx_vlans); v != NULL;
v = list_next(&g->mlg_rx_vlans, v)) {
if (v->mlgv_tagged == tagged && v->mlgv_vid == vid) {
mutex_exit(&ft->mlft_mtx);
return (B_TRUE);
}
}
if (list_is_empty(&g->mlg_rx_vlans))
first = B_TRUE;
for (fe = list_head(&fg->mlfg_entries); fe != NULL;
fe = list_next(&fg->mlfg_entries, fe)) {
if (!(fe->mlfe_state & MLXCX_FLOW_ENTRY_RESERVED)) {
found = B_TRUE;
break;
}
}
if (!found) {
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
v = kmem_zalloc(sizeof (mlxcx_group_vlan_t), KM_SLEEP);
v->mlgv_fe = fe;
v->mlgv_tagged = tagged;
v->mlgv_vid = vid;
fe->mlfe_state |= MLXCX_FLOW_ENTRY_RESERVED;
fe->mlfe_state |= MLXCX_FLOW_ENTRY_DIRTY;
fe->mlfe_vid = vid;
if (tagged) {
fe->mlfe_vlan_type = MLXCX_VLAN_TYPE_CVLAN;
} else {
fe->mlfe_vlan_type = MLXCX_VLAN_TYPE_NONE;
}
if (!mlxcx_cmd_set_flow_table_entry(mlxp, fe)) {
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_DIRTY;
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_RESERVED;
kmem_free(v, sizeof (mlxcx_group_vlan_t));
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
list_insert_tail(&g->mlg_rx_vlans, v);
if (first) {
fe = list_head(&dfg->mlfg_entries);
(void) mlxcx_cmd_delete_flow_table_entry(mlxp, fe);
}
mutex_exit(&ft->mlft_mtx);
return (B_TRUE);
}
void
mlxcx_remove_all_umcast_entries(mlxcx_t *mlxp, mlxcx_port_t *port,
mlxcx_ring_group_t *group)
{
mlxcx_flow_entry_t *fe;
mlxcx_flow_table_t *ft = port->mlp_rx_flow;
mlxcx_group_mac_t *gm, *ngm;
ASSERT(mutex_owned(&port->mlp_mtx));
ASSERT(mutex_owned(&group->mlg_mtx));
mutex_enter(&ft->mlft_mtx);
gm = avl_first(&group->mlg_rx_macs);
for (; gm != NULL; gm = ngm) {
ngm = AVL_NEXT(&group->mlg_rx_macs, gm);
ASSERT3P(gm->mlgm_group, ==, group);
fe = gm->mlgm_fe;
ASSERT3P(fe->mlfe_table, ==, ft);
avl_remove(&group->mlg_rx_macs, gm);
list_remove(&fe->mlfe_ring_groups, gm);
kmem_free(gm, sizeof (mlxcx_group_mac_t));
fe->mlfe_ndest = 0;
for (gm = list_head(&fe->mlfe_ring_groups); gm != NULL;
gm = list_next(&fe->mlfe_ring_groups, gm)) {
fe->mlfe_dest[fe->mlfe_ndest++].mlfed_flow =
gm->mlgm_group->mlg_rx_vlan_ft;
}
fe->mlfe_state |= MLXCX_FLOW_ENTRY_DIRTY;
if (fe->mlfe_ndest > 0) {
(void) mlxcx_cmd_set_flow_table_entry(mlxp, fe);
continue;
}
avl_remove(&port->mlp_dmac_fe, fe);
(void) mlxcx_cmd_delete_flow_table_entry(mlxp, fe);
list_destroy(&fe->mlfe_ring_groups);
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_RESERVED;
}
mutex_exit(&ft->mlft_mtx);
}
boolean_t
mlxcx_remove_umcast_entry(mlxcx_t *mlxp, mlxcx_port_t *port,
mlxcx_ring_group_t *group, const uint8_t *macaddr)
{
mlxcx_flow_entry_t *fe;
mlxcx_flow_table_t *ft = port->mlp_rx_flow;
mlxcx_group_mac_t *gm, probe;
ASSERT(mutex_owned(&port->mlp_mtx));
ASSERT(mutex_owned(&group->mlg_mtx));
bzero(&probe, sizeof (probe));
bcopy(macaddr, probe.mlgm_mac, sizeof (probe.mlgm_mac));
mutex_enter(&ft->mlft_mtx);
gm = avl_find(&group->mlg_rx_macs, &probe, NULL);
if (gm == NULL) {
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
ASSERT3P(gm->mlgm_group, ==, group);
ASSERT0(bcmp(macaddr, gm->mlgm_mac, sizeof (gm->mlgm_mac)));
fe = gm->mlgm_fe;
ASSERT3P(fe->mlfe_table, ==, ft);
ASSERT0(bcmp(macaddr, fe->mlfe_dmac, sizeof (fe->mlfe_dmac)));
list_remove(&fe->mlfe_ring_groups, gm);
avl_remove(&group->mlg_rx_macs, gm);
kmem_free(gm, sizeof (mlxcx_group_mac_t));
fe->mlfe_ndest = 0;
for (gm = list_head(&fe->mlfe_ring_groups); gm != NULL;
gm = list_next(&fe->mlfe_ring_groups, gm)) {
fe->mlfe_dest[fe->mlfe_ndest++].mlfed_flow =
gm->mlgm_group->mlg_rx_vlan_ft;
}
fe->mlfe_state |= MLXCX_FLOW_ENTRY_DIRTY;
if (fe->mlfe_ndest > 0) {
if (!mlxcx_cmd_set_flow_table_entry(mlxp, fe)) {
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
mutex_exit(&ft->mlft_mtx);
return (B_TRUE);
}
avl_remove(&port->mlp_dmac_fe, fe);
(void) mlxcx_cmd_delete_flow_table_entry(mlxp, fe);
list_destroy(&fe->mlfe_ring_groups);
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_RESERVED;
mutex_exit(&ft->mlft_mtx);
return (B_TRUE);
}
boolean_t
mlxcx_add_umcast_entry(mlxcx_t *mlxp, mlxcx_port_t *port,
mlxcx_ring_group_t *group, const uint8_t *macaddr)
{
mlxcx_flow_group_t *fg;
mlxcx_flow_entry_t *fe, probe;
mlxcx_flow_table_t *ft = port->mlp_rx_flow;
mlxcx_group_mac_t *gm;
boolean_t found = B_FALSE;
ASSERT(mutex_owned(&port->mlp_mtx));
ASSERT(mutex_owned(&group->mlg_mtx));
bzero(&probe, sizeof (probe));
bcopy(macaddr, probe.mlfe_dmac, sizeof (probe.mlfe_dmac));
mutex_enter(&ft->mlft_mtx);
fe = avl_find(&port->mlp_dmac_fe, &probe, NULL);
if (fe == NULL) {
fg = port->mlp_umcast;
for (fe = list_head(&fg->mlfg_entries); fe != NULL;
fe = list_next(&fg->mlfg_entries, fe)) {
if (!(fe->mlfe_state & MLXCX_FLOW_ENTRY_RESERVED)) {
found = B_TRUE;
break;
}
}
if (!found) {
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
list_create(&fe->mlfe_ring_groups, sizeof (mlxcx_group_mac_t),
offsetof(mlxcx_group_mac_t, mlgm_fe_entry));
fe->mlfe_state |= MLXCX_FLOW_ENTRY_RESERVED;
fe->mlfe_action = MLXCX_FLOW_ACTION_FORWARD;
bcopy(macaddr, fe->mlfe_dmac, sizeof (fe->mlfe_dmac));
avl_add(&port->mlp_dmac_fe, fe);
}
fe->mlfe_dest[fe->mlfe_ndest++].mlfed_flow = group->mlg_rx_vlan_ft;
fe->mlfe_state |= MLXCX_FLOW_ENTRY_DIRTY;
if (!mlxcx_cmd_set_flow_table_entry(mlxp, fe)) {
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_DIRTY;
if (--fe->mlfe_ndest == 0) {
fe->mlfe_state &= ~MLXCX_FLOW_ENTRY_RESERVED;
}
mutex_exit(&ft->mlft_mtx);
return (B_FALSE);
}
gm = kmem_zalloc(sizeof (mlxcx_group_mac_t), KM_SLEEP);
gm->mlgm_group = group;
gm->mlgm_fe = fe;
bcopy(macaddr, gm->mlgm_mac, sizeof (gm->mlgm_mac));
avl_add(&group->mlg_rx_macs, gm);
list_insert_tail(&fe->mlfe_ring_groups, gm);
mutex_exit(&ft->mlft_mtx);
return (B_TRUE);
}
boolean_t
mlxcx_setup_flow_group(mlxcx_t *mlxp, mlxcx_flow_table_t *ft,
mlxcx_flow_group_t *fg)
{
mlxcx_flow_entry_t *fe;
uint_t i, idx;
ASSERT(mutex_owned(&ft->mlft_mtx));
ASSERT(ft->mlft_state & MLXCX_FLOW_TABLE_CREATED);
ASSERT3P(fg->mlfg_table, ==, ft);
if (ft->mlft_next_ent + fg->mlfg_size > ft->mlft_nents)
return (B_FALSE);
fg->mlfg_start_idx = ft->mlft_next_ent;
if (!mlxcx_cmd_create_flow_group(mlxp, fg)) {
return (B_FALSE);
}
list_create(&fg->mlfg_entries, sizeof (mlxcx_flow_entry_t),
offsetof(mlxcx_flow_entry_t, mlfe_group_entry));
for (i = 0; i < fg->mlfg_size; ++i) {
idx = fg->mlfg_start_idx + i;
fe = &ft->mlft_ent[idx];
fe->mlfe_group = fg;
list_insert_tail(&fg->mlfg_entries, fe);
}
fg->mlfg_avail = fg->mlfg_size;
ft->mlft_next_ent += fg->mlfg_size;
return (B_TRUE);
}
static boolean_t
mlxcx_setup_eq(mlxcx_t *mlxp, uint_t vec, uint64_t events)
{
mlxcx_event_queue_t *mleq = &mlxp->mlx_eqs[vec];
mutex_enter(&mleq->mleq_mtx);
if (!mlxcx_eq_alloc_dma(mlxp, mleq)) {
mutex_exit(&mleq->mleq_mtx);
return (B_FALSE);
}
mleq->mleq_mlx = mlxp;
mleq->mleq_uar = &mlxp->mlx_uar;
mleq->mleq_events = events;
mleq->mleq_intr_index = vec;
if (!mlxcx_cmd_create_eq(mlxp, mleq)) {
mutex_exit(&mleq->mleq_mtx);
return (B_FALSE);
}
if (ddi_intr_enable(mlxp->mlx_intr_handles[vec]) != DDI_SUCCESS) {
mutex_exit(&mleq->mleq_mtx);
return (B_FALSE);
}
mleq->mleq_state |= MLXCX_EQ_INTR_ENABLED;
mleq->mleq_state |= MLXCX_EQ_ATTACHING;
mlxcx_arm_eq(mlxp, mleq);
mutex_exit(&mleq->mleq_mtx);
return (B_TRUE);
}
static void
mlxcx_eq_set_attached(mlxcx_t *mlxp)
{
uint_t vec;
mlxcx_event_queue_t *mleq;
for (vec = 0; vec < mlxp->mlx_intr_count; ++vec) {
mleq = &mlxp->mlx_eqs[vec];
mutex_enter(&mleq->mleq_mtx);
mleq->mleq_state &= ~MLXCX_EQ_ATTACHING;
mutex_exit(&mleq->mleq_mtx);
}
}
static boolean_t
mlxcx_setup_async_eqs(mlxcx_t *mlxp)
{
boolean_t ret;
ret = mlxcx_setup_eq(mlxp, 0,
(1ULL << MLXCX_EVENT_CMD_COMPLETION) |
(1ULL << MLXCX_EVENT_PAGE_REQUEST) |
(1ULL << MLXCX_EVENT_PORT_STATE) |
(1ULL << MLXCX_EVENT_INTERNAL_ERROR) |
(1ULL << MLXCX_EVENT_PORT_MODULE) |
(1ULL << MLXCX_EVENT_SENDQ_DRAIN) |
(1ULL << MLXCX_EVENT_LAST_WQE) |
(1ULL << MLXCX_EVENT_CQ_ERROR) |
(1ULL << MLXCX_EVENT_WQ_CATASTROPHE) |
(1ULL << MLXCX_EVENT_PAGE_FAULT) |
(1ULL << MLXCX_EVENT_WQ_INVALID_REQ) |
(1ULL << MLXCX_EVENT_WQ_ACCESS_VIOL) |
(1ULL << MLXCX_EVENT_NIC_VPORT) |
(1ULL << MLXCX_EVENT_DOORBELL_CONGEST));
if (ret)
mlxcx_cmd_eq_enable(mlxp);
return (ret);
}
int
mlxcx_cq_compare(const void *arg0, const void *arg1)
{
const mlxcx_completion_queue_t *left = arg0;
const mlxcx_completion_queue_t *right = arg1;
if (left->mlcq_num < right->mlcq_num) {
return (-1);
}
if (left->mlcq_num > right->mlcq_num) {
return (1);
}
return (0);
}
static boolean_t
mlxcx_setup_eqs(mlxcx_t *mlxp)
{
uint_t i;
mlxcx_event_queue_t *mleq;
ASSERT3S(mlxp->mlx_intr_count, >, 0);
for (i = mlxp->mlx_intr_cq0; i < mlxp->mlx_intr_count; ++i) {
mleq = &mlxp->mlx_eqs[i];
mutex_enter(&mleq->mleq_mtx);
if (!mlxcx_eq_alloc_dma(mlxp, mleq)) {
mutex_exit(&mleq->mleq_mtx);
return (B_FALSE);
}
mleq->mleq_uar = &mlxp->mlx_uar;
if (!mlxcx_cmd_create_eq(mlxp, mleq)) {
mutex_exit(&mleq->mleq_mtx);
return (B_FALSE);
}
if (mlxp->mlx_props.mldp_intrmod_period_usec != 0 &&
!mlxcx_cmd_set_int_mod(mlxp, i,
mlxp->mlx_props.mldp_intrmod_period_usec)) {
mutex_exit(&mleq->mleq_mtx);
return (B_FALSE);
}
if (ddi_intr_enable(mlxp->mlx_intr_handles[i]) != DDI_SUCCESS) {
mutex_exit(&mleq->mleq_mtx);
return (B_FALSE);
}
mleq->mleq_state |= MLXCX_EQ_INTR_ENABLED;
mlxcx_arm_eq(mlxp, mleq);
mutex_exit(&mleq->mleq_mtx);
}
mlxp->mlx_next_eq = mlxp->mlx_intr_cq0;
return (B_TRUE);
}
static void
mlxcx_explore_pcam(mlxcx_t *mlxp, mlxcx_caps_t *c)
{
mlxcx_register_data_t data;
mlxcx_reg_pcam_t *pcam = &data.mlrd_pcam;
ASSERT(c->mlc_pcam);
bzero(&data, sizeof (data));
VERIFY(mlxcx_cmd_access_register(mlxp, MLXCX_CMD_ACCESS_REGISTER_READ,
MLXCX_REG_PCAM, &data));
ASSERT0(pcam->mlrd_pcam_feature_group);
ASSERT0(pcam->mlrd_pcam_access_reg_group);
c->mlc_ext_ptys = get_bit64(pcam->mlrd_pcam_feature_cap_mask_low,
MLXCX_PCAM_LOW_FFLAGS_PTYS_EXTENDED);
}
static boolean_t
mlxcx_init_caps(mlxcx_t *mlxp)
{
mlxcx_caps_t *c;
mlxp->mlx_caps = c = kmem_zalloc(sizeof (mlxcx_caps_t), KM_SLEEP);
if (!mlxcx_cmd_query_hca_cap(mlxp, MLXCX_HCA_CAP_GENERAL,
MLXCX_HCA_CAP_MODE_CURRENT, &c->mlc_hca_cur)) {
mlxcx_warn(mlxp, "failed to obtain current HCA general caps");
}
if (!mlxcx_cmd_query_hca_cap(mlxp, MLXCX_HCA_CAP_GENERAL,
MLXCX_HCA_CAP_MODE_MAX, &c->mlc_hca_max)) {
mlxcx_warn(mlxp, "failed to obtain maximum HCA general caps");
}
if (!mlxcx_cmd_query_hca_cap(mlxp, MLXCX_HCA_CAP_ETHERNET,
MLXCX_HCA_CAP_MODE_CURRENT, &c->mlc_ether_cur)) {
mlxcx_warn(mlxp, "failed to obtain current HCA eth caps");
}
if (!mlxcx_cmd_query_hca_cap(mlxp, MLXCX_HCA_CAP_ETHERNET,
MLXCX_HCA_CAP_MODE_MAX, &c->mlc_ether_max)) {
mlxcx_warn(mlxp, "failed to obtain maximum HCA eth caps");
}
if (!mlxcx_cmd_query_hca_cap(mlxp, MLXCX_HCA_CAP_NIC_FLOW,
MLXCX_HCA_CAP_MODE_CURRENT, &c->mlc_nic_flow_cur)) {
mlxcx_warn(mlxp, "failed to obtain current HCA flow caps");
}
if (!mlxcx_cmd_query_hca_cap(mlxp, MLXCX_HCA_CAP_NIC_FLOW,
MLXCX_HCA_CAP_MODE_MAX, &c->mlc_nic_flow_max)) {
mlxcx_warn(mlxp, "failed to obtain maximum HCA flow caps");
}
const mlxcx_hca_cap_general_caps_t *gen = &c->mlc_hca_cur.mhc_general;
if (gen->mlcap_general_log_pg_sz != 12) {
mlxcx_warn(mlxp, "!hardware has page size != 4k "
"(log_pg_sz = %u)", (uint_t)gen->mlcap_general_log_pg_sz);
goto err;
}
if (gen->mlcap_general_cqe_version != 1) {
mlxcx_warn(mlxp, "!hardware does not support CQE v1 "
"(cqe_ver = %u)", (uint_t)gen->mlcap_general_cqe_version);
goto err;
}
if (gen->mlcap_general_port_type !=
MLXCX_CAP_GENERAL_PORT_TYPE_ETHERNET) {
mlxcx_warn(mlxp, "!hardware has non-ethernet ports");
goto err;
}
mlxp->mlx_nports = gen->mlcap_general_num_ports;
mlxp->mlx_max_sdu = (1 << (gen->mlcap_general_log_max_msg & 0x1F));
if (mlxp->mlx_type >= MLXCX_DEV_CX5 &&
get_bit16(gen->mlcap_general_flags_c,
MLXCX_CAP_GENERAL_FLAGS_C_PCAM_REG)) {
c->mlc_pcam = B_TRUE;
}
c->mlc_max_tir = (1 << gen->mlcap_general_log_max_tir);
c->mlc_checksum = get_bit32(c->mlc_ether_cur.mhc_eth.mlcap_eth_flags,
MLXCX_ETH_CAP_CSUM_CAP);
c->mlc_vxlan = get_bit32(c->mlc_ether_cur.mhc_eth.mlcap_eth_flags,
MLXCX_ETH_CAP_TUNNEL_STATELESS_VXLAN);
c->mlc_max_lso_size = (1 << get_bits32(c->mlc_ether_cur.mhc_eth.
mlcap_eth_flags, MLXCX_ETH_CAP_MAX_LSO_CAP));
if (c->mlc_max_lso_size == 1) {
c->mlc_max_lso_size = 0;
c->mlc_lso = B_FALSE;
} else {
c->mlc_lso = B_TRUE;
}
c->mlc_max_rqt_size = (1 << get_bits32(c->mlc_ether_cur.mhc_eth.
mlcap_eth_flags, MLXCX_ETH_CAP_RSS_IND_TBL_CAP));
if (!get_bit32(c->mlc_nic_flow_cur.mhc_flow.mlcap_flow_nic_rx.
mlcap_flow_prop_flags, MLXCX_FLOW_CAP_PROPS_SUPPORT)) {
mlxcx_warn(mlxp, "!hardware does not support rx flow tables");
goto err;
}
if (!get_bit32(c->mlc_nic_flow_cur.mhc_flow.mlcap_flow_nic_rx.
mlcap_flow_prop_flags, MLXCX_FLOW_CAP_PROPS_MODIFY)) {
mlxcx_warn(mlxp, "!hardware does not support modifying rx "
"flow table entries");
goto err;
}
c->mlc_max_rx_ft_shift = c->mlc_nic_flow_cur.mhc_flow.mlcap_flow_nic_rx.
mlcap_flow_prop_log_max_ft_size;
c->mlc_max_rx_flows = (1 << c->mlc_nic_flow_cur.mhc_flow.
mlcap_flow_nic_rx.mlcap_flow_prop_log_max_flow);
c->mlc_max_rx_ft = (1 << c->mlc_nic_flow_cur.mhc_flow.
mlcap_flow_nic_rx.mlcap_flow_prop_log_max_ft_num);
c->mlc_max_rx_fe_dest = (1 << c->mlc_nic_flow_cur.mhc_flow.
mlcap_flow_nic_rx.mlcap_flow_prop_log_max_destination);
return (B_TRUE);
err:
kmem_free(mlxp->mlx_caps, sizeof (mlxcx_caps_t));
return (B_FALSE);
}
static int
mlxcx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
mlxcx_t *mlxp;
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
mlxp = ddi_get_driver_private(dip);
if (mlxp == NULL) {
mlxcx_warn(NULL, "asked to detach, but missing instance "
"private data");
return (DDI_FAILURE);
}
if (mlxp->mlx_attach & MLXCX_ATTACH_MAC_HDL) {
if (mac_unregister(mlxp->mlx_mac_hdl) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
mlxp->mlx_attach &= ~MLXCX_ATTACH_MAC_HDL;
}
mlxcx_teardown(mlxp);
return (DDI_SUCCESS);
}
static size_t
mlxcx_calc_rx_ngroups(mlxcx_t *mlxp)
{
size_t ngroups = mlxp->mlx_props.mldp_rx_ngroups_large +
mlxp->mlx_props.mldp_rx_ngroups_small;
size_t tirlim, flowlim, gflowlim;
tirlim = mlxp->mlx_caps->mlc_max_tir / MLXCX_TIRS_PER_GROUP;
if (tirlim < ngroups) {
mlxcx_note(mlxp, "limiting number of rx groups to %u based "
"on number of TIRs available", tirlim);
ngroups = tirlim;
}
flowlim = (1 << mlxp->mlx_caps->mlc_max_rx_ft_shift) - 2;
if (flowlim < ngroups) {
mlxcx_note(mlxp, "limiting number of rx groups to %u based "
"on max size of RX flow tables", flowlim);
ngroups = flowlim;
}
flowlim = (mlxp->mlx_caps->mlc_max_rx_ft - mlxp->mlx_nports) / 2;
if (flowlim < ngroups) {
mlxcx_note(mlxp, "limiting number of rx groups to %u based "
"on max number of RX flow tables",
flowlim);
ngroups = flowlim;
}
do {
gflowlim = mlxp->mlx_caps->mlc_max_rx_flows - 16 * ngroups - 2;
if (gflowlim < ngroups) {
mlxcx_note(mlxp, "limiting number of rx groups to %u "
"based on max total RX flows", gflowlim);
--ngroups;
}
} while (gflowlim < ngroups);
return (ngroups);
}
static int
mlxcx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
mlxcx_t *mlxp;
char tq_name[TASKQ_NAMELEN];
uint_t i;
int inst, ret;
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
inst = ddi_get_instance(dip);
ret = ddi_soft_state_zalloc(mlxcx_softstate, inst);
if (ret != 0)
return (ret);
mlxp = ddi_get_soft_state(mlxcx_softstate, inst);
if (mlxp == NULL)
return (DDI_FAILURE);
mlxp->mlx_dip = dip;
mlxp->mlx_inst = inst;
ddi_set_driver_private(dip, mlxp);
mlxcx_load_props(mlxp);
mlxcx_fm_init(mlxp);
mlxp->mlx_attach |= MLXCX_ATTACH_FM;
if (pci_config_setup(mlxp->mlx_dip, &mlxp->mlx_cfg_handle) !=
DDI_SUCCESS) {
mlxcx_warn(mlxp, "failed to initial PCI config space");
goto err;
}
mlxcx_get_model(mlxp);
mlxp->mlx_attach |= MLXCX_ATTACH_PCI_CONFIG;
if (!mlxcx_regs_map(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_REGS;
if (!mlxcx_cmd_queue_init(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_CMD;
if (!mlxcx_cmd_enable_hca(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_ENABLE_HCA;
if (!mlxcx_check_issi(mlxp)) {
goto err;
}
if (!mlxcx_intr_setup(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_INTRS;
mutex_init(&mlxp->mlx_pagemtx, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(mlxp->mlx_intr_pri));
avl_create(&mlxp->mlx_pages, mlxcx_page_compare,
sizeof (mlxcx_dev_page_t), offsetof(mlxcx_dev_page_t, mxdp_tree));
mlxp->mlx_attach |= MLXCX_ATTACH_PAGE_LIST;
(void) snprintf(tq_name, sizeof (tq_name), "%s_async_%d",
ddi_driver_name(mlxp->mlx_dip), mlxp->mlx_inst);
mlxp->mlx_async_tq = taskq_create(tq_name, 1, minclsyspri, 1, INT_MAX,
TASKQ_PREPOPULATE);
for (i = 0; i <= MLXCX_FUNC_ID_MAX; i++) {
mlxp->mlx_npages_req[i].mla_mlx = mlxp;
mutex_init(&mlxp->mlx_npages_req[i].mla_mtx, NULL,
MUTEX_DRIVER, DDI_INTR_PRI(mlxp->mlx_async_intr_pri));
}
mlxp->mlx_attach |= MLXCX_ATTACH_ASYNC_TQ;
if (!mlxcx_init_pages(mlxp, MLXCX_QUERY_PAGES_OPMOD_BOOT)) {
goto err;
}
if (!mlxcx_init_caps(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_CAPS;
if (!mlxcx_init_pages(mlxp, MLXCX_QUERY_PAGES_OPMOD_INIT)) {
goto err;
}
if (!mlxcx_cmd_init_hca(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_INIT_HCA;
if (!mlxcx_cmd_set_driver_version(mlxp, MLXCX_DRIVER_VERSION)) {
goto err;
}
if (mlxp->mlx_caps->mlc_pcam) {
mlxcx_explore_pcam(mlxp, mlxp->mlx_caps);
}
if (!mlxcx_cmd_alloc_uar(mlxp, &mlxp->mlx_uar)) {
goto err;
}
for (i = 0; i < MLXCX_BF_PER_UAR; ++i) {
mutex_init(&mlxp->mlx_uar.mlu_bf[i].mbf_mtx, NULL,
MUTEX_DRIVER, DDI_INTR_PRI(mlxp->mlx_intr_pri));
}
mlxp->mlx_attach |= MLXCX_ATTACH_UAR_PD_TD;
if (!mlxcx_setup_async_eqs(mlxp)) {
goto err;
}
if (!mlxcx_cmd_alloc_pd(mlxp, &mlxp->mlx_pd)) {
goto err;
}
if (!mlxcx_cmd_alloc_tdom(mlxp, &mlxp->mlx_tdom)) {
goto err;
}
if (!mlxcx_cmd_query_special_ctxs(mlxp)) {
goto err;
}
if (!mlxcx_setup_ports(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_PORTS;
mlxcx_load_model_props(mlxp);
if (!mlxcx_setup_eqs(mlxp)) {
goto err;
}
list_create(&mlxp->mlx_cqs, sizeof (mlxcx_completion_queue_t),
offsetof(mlxcx_completion_queue_t, mlcq_entry));
mlxp->mlx_attach |= MLXCX_ATTACH_CQS;
list_create(&mlxp->mlx_wqs, sizeof (mlxcx_work_queue_t),
offsetof(mlxcx_work_queue_t, mlwq_entry));
mlxp->mlx_attach |= MLXCX_ATTACH_WQS;
mlxp->mlx_rx_ngroups = mlxcx_calc_rx_ngroups(mlxp);
mlxp->mlx_rx_groups_size = mlxp->mlx_rx_ngroups *
sizeof (mlxcx_ring_group_t);
mlxp->mlx_rx_groups = kmem_zalloc(mlxp->mlx_rx_groups_size, KM_SLEEP);
mlxp->mlx_tx_ngroups = mlxp->mlx_props.mldp_tx_ngroups;
mlxp->mlx_tx_groups_size = mlxp->mlx_tx_ngroups *
sizeof (mlxcx_ring_group_t);
mlxp->mlx_tx_groups = kmem_zalloc(mlxp->mlx_tx_groups_size, KM_SLEEP);
mlxp->mlx_attach |= MLXCX_ATTACH_GROUPS;
if (!mlxcx_setup_bufs(mlxp))
goto err;
mlxp->mlx_attach |= MLXCX_ATTACH_BUFS;
for (i = 0; i < mlxp->mlx_tx_ngroups; ++i) {
if (!mlxcx_tx_group_setup(mlxp, &mlxp->mlx_tx_groups[i]))
goto err;
}
for (i = 0; i < mlxp->mlx_rx_ngroups; ++i) {
if (!mlxcx_rx_group_setup(mlxp, &mlxp->mlx_rx_groups[i]))
goto err;
}
if (!mlxcx_setup_checktimers(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_CHKTIMERS;
if (mlxcx_setup_sensors(mlxp)) {
mlxp->mlx_attach |= MLXCX_ATTACH_SENSORS;
}
if (!mlxcx_register_mac(mlxp)) {
goto err;
}
mlxp->mlx_attach |= MLXCX_ATTACH_MAC_HDL;
mlxcx_eq_set_attached(mlxp);
return (DDI_SUCCESS);
err:
mlxcx_teardown(mlxp);
return (DDI_FAILURE);
}
static struct cb_ops mlxcx_cb_ops = {
.cb_open = nulldev,
.cb_close = nulldev,
.cb_strategy = nodev,
.cb_print = nodev,
.cb_dump = nodev,
.cb_read = nodev,
.cb_write = nodev,
.cb_ioctl = nodev,
.cb_devmap = nodev,
.cb_mmap = nodev,
.cb_segmap = nodev,
.cb_chpoll = nochpoll,
.cb_prop_op = ddi_prop_op,
.cb_flag = D_MP,
.cb_rev = CB_REV,
.cb_aread = nodev,
.cb_awrite = nodev
};
static struct dev_ops mlxcx_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_getinfo = NULL,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = mlxcx_attach,
.devo_detach = mlxcx_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_supported,
.devo_cb_ops = &mlxcx_cb_ops
};
static struct modldrv mlxcx_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "Mellanox Connect-X 4/5/6",
.drv_dev_ops = &mlxcx_dev_ops
};
static struct modlinkage mlxcx_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &mlxcx_modldrv, NULL }
};
int
_init(void)
{
int ret;
ret = ddi_soft_state_init(&mlxcx_softstate, sizeof (mlxcx_t), 0);
if (ret != 0) {
return (ret);
}
mac_init_ops(&mlxcx_dev_ops, MLXCX_MODULE_NAME);
if ((ret = mod_install(&mlxcx_modlinkage)) != DDI_SUCCESS) {
mac_fini_ops(&mlxcx_dev_ops);
ddi_soft_state_fini(&mlxcx_softstate);
return (ret);
}
return (DDI_SUCCESS);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&mlxcx_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&mlxcx_modlinkage)) != DDI_SUCCESS) {
return (ret);
}
mac_fini_ops(&mlxcx_dev_ops);
ddi_soft_state_fini(&mlxcx_softstate);
return (DDI_SUCCESS);
}