#include "ena_hw.h"
#include "ena.h"
CTASSERT(sizeof (enahw_aenq_desc_t) == 64);
CTASSERT(ENAHW_AENQ_GROUPS_ARR_NUM == 8);
typedef struct ena_aenq_grpstr {
enahw_aenq_groups_t eag_type;
const char *eag_str;
} ena_aenq_grpstr_t;
static ena_aenq_grpstr_t ena_groups_str[ENAHW_AENQ_GROUPS_ARR_NUM] = {
{
.eag_type = ENAHW_AENQ_GROUP_LINK_CHANGE,
.eag_str = "LINK CHANGE"
},
{
.eag_type = ENAHW_AENQ_GROUP_FATAL_ERROR,
.eag_str = "FATAL ERROR"
},
{
.eag_type = ENAHW_AENQ_GROUP_WARNING,
.eag_str = "WARNING"
},
{
.eag_type = ENAHW_AENQ_GROUP_NOTIFICATION,
.eag_str = "NOTIFICATION"
},
{
.eag_type = ENAHW_AENQ_GROUP_KEEP_ALIVE,
.eag_str = "KEEP ALIVE"
},
{
.eag_type = ENAHW_AENQ_GROUP_REFRESH_CAPABILITIES,
.eag_str = "REFRESH CAPABILITIES"
},
{
.eag_type = ENAHW_AENQ_GROUP_CONF_NOTIFICATIONS,
.eag_str = "CONFIG NOTIFICATIONS"
},
{
.eag_type = ENAHW_AENQ_GROUP_DEVICE_REQUEST_RESET,
.eag_str = "DEVICE RESET REQUEST"
}
};
bool
ena_aenq_configure(ena_t *ena)
{
enahw_cmd_desc_t cmd;
enahw_feat_aenq_t *cmd_feat =
&cmd.ecd_cmd.ecd_set_feat.ecsf_feat.ecsf_aenq;
enahw_resp_desc_t resp;
enahw_feat_aenq_t *resp_feat = &resp.erd_resp.erd_get_feat.ergf_aenq;
enahw_aenq_groups_t to_enable;
bzero(&resp, sizeof (resp));
if (ena_get_feature(ena, &resp, ENAHW_FEAT_AENQ_CONFIG,
ENAHW_FEAT_AENQ_CONFIG_VER) != 0) {
return (false);
}
to_enable = BIT(ENAHW_AENQ_GROUP_LINK_CHANGE) |
BIT(ENAHW_AENQ_GROUP_FATAL_ERROR) |
BIT(ENAHW_AENQ_GROUP_WARNING) |
BIT(ENAHW_AENQ_GROUP_NOTIFICATION) |
BIT(ENAHW_AENQ_GROUP_KEEP_ALIVE) |
BIT(ENAHW_AENQ_GROUP_DEVICE_REQUEST_RESET);
to_enable &= resp_feat->efa_supported_groups;
bzero(&cmd, sizeof (cmd));
bzero(&resp, sizeof (cmd));
cmd_feat->efa_enabled_groups = to_enable;
if (ena_set_feature(ena, &cmd, &resp, ENAHW_FEAT_AENQ_CONFIG,
ENAHW_FEAT_AENQ_CONFIG_VER) != 0) {
return (false);
}
bzero(&resp, sizeof (resp));
if (ena_get_feature(ena, &resp, ENAHW_FEAT_AENQ_CONFIG,
ENAHW_FEAT_AENQ_CONFIG_VER) != 0) {
return (false);
}
ena->ena_aenq_supported_groups = resp_feat->efa_supported_groups;
ena->ena_aenq_enabled_groups = resp_feat->efa_enabled_groups;
for (uint_t i = 0; i < ENAHW_AENQ_GROUPS_ARR_NUM; i++) {
ena_aenq_grpstr_t *grpstr = &ena_groups_str[i];
bool supported = BIT(grpstr->eag_type) &
resp_feat->efa_supported_groups;
bool enabled = BIT(grpstr->eag_type) &
resp_feat->efa_enabled_groups;
ena_dbg(ena, "%s supported: %s enabled: %s", grpstr->eag_str,
supported ? "Y" : "N", enabled ? "Y" : "N");
}
return (true);
}
void
ena_aenq_work(ena_t *ena)
{
ena_aenq_t *aenq = &ena->ena_aenq;
uint16_t head_mod = aenq->eaenq_head & (aenq->eaenq_num_descs - 1);
bool processed = false;
enahw_aenq_desc_t *desc = &aenq->eaenq_descs[head_mod];
ENA_DMA_SYNC(aenq->eaenq_dma, DDI_DMA_SYNC_FORKERNEL);
while (ENAHW_AENQ_DESC_PHASE(desc) == aenq->eaenq_phase) {
ena_aenq_hdlr_t hdlr;
ASSERT3U(desc->ead_group, <, ENAHW_AENQ_GROUPS_ARR_NUM);
processed = true;
if (ena_debug &&
desc->ead_group != ENAHW_AENQ_GROUP_KEEP_ALIVE) {
uint64_t ts = ((uint64_t)desc->ead_ts_high << 32) |
(uint64_t)desc->ead_ts_low;
ena_dbg(ena,
"AENQ Group: (0x%x) %s Syndrome: 0x%x ts: %" PRIu64
" us", desc->ead_group,
ena_groups_str[desc->ead_group].eag_str,
desc->ead_syndrome, ts);
}
hdlr = ena->ena_aenq.eaenq_hdlrs[desc->ead_group];
hdlr(ena, desc);
aenq->eaenq_head++;
head_mod = aenq->eaenq_head & (aenq->eaenq_num_descs - 1);
if (head_mod == 0)
aenq->eaenq_phase ^= 1;
desc = &aenq->eaenq_descs[head_mod];
}
if (processed) {
ena_hw_bar_write32(ena, ENAHW_REG_AENQ_HEAD_DB,
aenq->eaenq_head);
}
}
static void
ena_aenq_link_change_hdlr(void *data, enahw_aenq_desc_t *desc)
{
ena_t *ena = data;
bool is_up = (desc->ead_payload.link_change.flags &
ENAHW_AENQ_LINK_CHANGE_LINK_STATUS_MASK) != 0;
link_state_t new_state = is_up ? LINK_STATE_UP : LINK_STATE_DOWN;
ASSERT3U(ena->ena_attach_seq, >=, ENA_ATTACH_MAC_REGISTER);
ena->ena_aenq_stat.eaes_link_change.value.ui64++;
ena_dbg(ena, "link is %s", is_up ? "UP" : "DOWN");
mutex_enter(&ena->ena_lock);
if (ena->ena_link_state != new_state) {
mac_link_update(ena->ena_mh, new_state);
ena->ena_link_state = new_state;
}
mutex_exit(&ena->ena_lock);
}
static void
ena_aenq_notification_hdlr(void *data, enahw_aenq_desc_t *desc)
{
ena_t *ena = data;
if (desc->ead_syndrome == ENAHW_AENQ_SYNDROME_UPDATE_HINTS) {
enahw_device_hints_t *hints =
(enahw_device_hints_t *)desc->ead_payload.raw;
ena_update_hints(ena, hints);
} else {
ena_err(ena, "Invalid aenq notification syndrome 0x%x",
desc->ead_syndrome);
}
ena->ena_aenq_stat.eaes_notification.value.ui64++;
}
static void
ena_aenq_keep_alive_hdlr(void *data, enahw_aenq_desc_t *desc)
{
ena_t *ena = data;
uint64_t rx_drops, tx_drops, rx_overruns;
ena_basic_stat_t *ebs = ena->ena_device_basic_kstat->ks_data;
uint64_t now = (uint64_t)gethrtime();
(void) atomic_swap_64(&ena->ena_watchdog_last_keepalive, now);
rx_drops =
((uint64_t)desc->ead_payload.keep_alive.rx_drops_high << 32) |
desc->ead_payload.keep_alive.rx_drops_low;
tx_drops =
((uint64_t)desc->ead_payload.keep_alive.tx_drops_high << 32) |
desc->ead_payload.keep_alive.tx_drops_low;
rx_overruns =
((uint64_t)desc->ead_payload.keep_alive.rx_overruns_high << 32) |
desc->ead_payload.keep_alive.rx_overruns_low;
mutex_enter(&ena->ena_device_basic_stat_lock);
ebs->ebs_rx_drops.value.ui64 = rx_drops;
ebs->ebs_tx_drops.value.ui64 = tx_drops;
ebs->ebs_rx_overruns.value.ui64 = rx_overruns;
mutex_exit(&ena->ena_device_basic_stat_lock);
ena->ena_aenq_stat.eaes_keep_alive.value.ui64++;
}
static void
ena_aenq_request_reset_hdlr(void *data, enahw_aenq_desc_t *desc)
{
ena_t *ena = data;
ena->ena_reset_reason = ENAHW_RESET_DEVICE_REQUEST;
atomic_or_32(&ena->ena_state, ENA_STATE_ERROR);
ena->ena_aenq_stat.eaes_request_reset.value.ui64++;
}
static void
ena_aenq_fatal_error_hdlr(void *data, enahw_aenq_desc_t *desc)
{
ena_t *ena = data;
ena->ena_aenq_stat.eaes_fatal_error.value.ui64++;
}
static void
ena_aenq_warning_hdlr(void *data, enahw_aenq_desc_t *desc)
{
ena_t *ena = data;
ena->ena_aenq_stat.eaes_warning.value.ui64++;
}
static void
ena_aenq_default_hdlr(void *data, enahw_aenq_desc_t *desc)
{
ena_t *ena = data;
ena->ena_aenq_stat.eaes_default.value.ui64++;
ena_dbg(ena, "unimplemented handler for aenq group: %s",
ena_groups_str[desc->ead_group].eag_str);
}
static void
ena_aenq_set_hdlrs(ena_aenq_t *aenq)
{
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_LINK_CHANGE] =
ena_aenq_link_change_hdlr;
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_NOTIFICATION] =
ena_aenq_notification_hdlr;
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_KEEP_ALIVE] =
ena_aenq_keep_alive_hdlr;
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_DEVICE_REQUEST_RESET] =
ena_aenq_request_reset_hdlr;
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_FATAL_ERROR] =
ena_aenq_fatal_error_hdlr;
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_WARNING] =
ena_aenq_warning_hdlr;
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_REFRESH_CAPABILITIES] =
ena_aenq_default_hdlr;
aenq->eaenq_hdlrs[ENAHW_AENQ_GROUP_CONF_NOTIFICATIONS] =
ena_aenq_default_hdlr;
}
bool
ena_aenq_init(ena_t *ena)
{
ena_aenq_t *aenq = &ena->ena_aenq;
uint32_t addr_low, addr_high, wval;
if (aenq->eaenq_descs == NULL) {
size_t size;
aenq->eaenq_num_descs = ENA_AENQ_NUM_DESCS;
size = aenq->eaenq_num_descs * sizeof (*aenq->eaenq_descs);
ena_dma_conf_t conf = {
.edc_size = size,
.edc_align = ENAHW_AENQ_DESC_BUF_ALIGNMENT,
.edc_sgl = 1,
.edc_endian = DDI_NEVERSWAP_ACC,
.edc_stream = false,
};
if (!ena_dma_alloc(ena, &aenq->eaenq_dma, &conf, size)) {
ena_err(ena, "failed to allocate DMA for AENQ");
return (false);
}
ENA_DMA_VERIFY_ADDR(ena,
aenq->eaenq_dma.edb_cookie->dmac_laddress);
aenq->eaenq_descs = (void *)aenq->eaenq_dma.edb_va;
ena_aenq_set_hdlrs(aenq);
} else {
ena_dma_bzero(&aenq->eaenq_dma);
}
aenq->eaenq_head = aenq->eaenq_num_descs;
aenq->eaenq_phase = 1;
addr_low = (uint32_t)(aenq->eaenq_dma.edb_cookie->dmac_laddress);
addr_high = (uint32_t)(aenq->eaenq_dma.edb_cookie->dmac_laddress >> 32);
ena_hw_bar_write32(ena, ENAHW_REG_AENQ_BASE_LO, addr_low);
ena_hw_bar_write32(ena, ENAHW_REG_AENQ_BASE_HI, addr_high);
ENA_DMA_SYNC(aenq->eaenq_dma, DDI_DMA_SYNC_FORDEV);
wval = ENAHW_AENQ_CAPS_DEPTH(aenq->eaenq_num_descs) |
ENAHW_AENQ_CAPS_ENTRY_SIZE(sizeof (*aenq->eaenq_descs));
ena_hw_bar_write32(ena, ENAHW_REG_AENQ_CAPS, wval);
return (true);
}
void
ena_aenq_enable(ena_t *ena)
{
ena->ena_watchdog_last_keepalive = 0;
ena_hw_bar_write32(ena, ENAHW_REG_AENQ_HEAD_DB,
ena->ena_aenq.eaenq_head);
}
void
ena_aenq_free(ena_t *ena)
{
ena_dma_free(&ena->ena_aenq.eaenq_dma);
}