#include "ena_hw.h"
#include "ena.h"
CTASSERT(sizeof (enahw_tx_data_desc_t) == 16);
CTASSERT(sizeof (enahw_tx_data_desc_t) == sizeof (enahw_tx_meta_desc_t));
CTASSERT(sizeof (enahw_tx_data_desc_t) == sizeof (enahw_tx_desc_t));
CTASSERT(sizeof (enahw_tx_meta_desc_t) == sizeof (enahw_tx_desc_t));
#ifdef _BIG_ENDIAN
#error "ENA driver is little-endian only"
#endif
uint64_t ena_admin_cmd_timeout_ns = ENA_ADMIN_CMD_DEF_TIMEOUT_NS;
void
ena_err(const ena_t *ena, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (ena != NULL && ena->ena_dip != NULL) {
vdev_err(ena->ena_dip, CE_WARN, fmt, ap);
} else {
vcmn_err(CE_WARN, fmt, ap);
}
va_end(ap);
}
void
ena_panic(const ena_t *ena, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (ena != NULL && ena->ena_dip != NULL) {
vdev_err(ena->ena_dip, CE_PANIC, fmt, ap);
} else {
vcmn_err(CE_PANIC, fmt, ap);
}
va_end(ap);
}
bool ena_debug = false;
void
ena_dbg(const ena_t *ena, const char *fmt, ...)
{
va_list ap;
if (ena_debug) {
char msg[1024];
va_start(ap, fmt);
(void) vsnprintf(msg, sizeof (msg), fmt, ap);
va_end(ap);
if (ena != NULL && ena->ena_dip != NULL) {
dev_err(ena->ena_dip, CE_NOTE, "!%s", msg);
} else {
cmn_err(CE_NOTE, "!%s", msg);
}
}
}
void
ena_trigger_reset(ena_t *ena, enahw_reset_reason_t reason)
{
mutex_enter(&ena->ena_lock);
ena->ena_reset_reason = reason;
mutex_exit(&ena->ena_lock);
atomic_or_32(&ena->ena_state, ENA_STATE_ERROR);
}
bool
ena_is_feat_avail(ena_t *ena, const enahw_feature_id_t feat_id)
{
VERIFY3U(feat_id, <=, ENAHW_FEAT_NUM);
uint32_t mask = 1U << feat_id;
if (feat_id == ENAHW_FEAT_DEVICE_ATTRIBUTES ||
feat_id == ENAHW_FEAT_HOST_ATTR_CONFIG) {
return (true);
}
return ((ena->ena_supported_features & mask) != 0);
}
bool
ena_is_cap_avail(ena_t *ena, const enahw_capability_id_t cap_id)
{
VERIFY3U(cap_id, <=, ENAHW_CAP_NUM);
uint32_t mask = 1U << cap_id;
return ((ena->ena_capabilities & mask) != 0);
}
static bool
ena_device_reset(ena_t *ena, enum enahw_reset_reason_types reason)
{
uint32_t rval, wval, reason_lsb, reason_msb;
hrtime_t timeout, expired;
rval = ena_hw_bar_read32(ena, ENAHW_REG_DEV_STS);
if ((rval & ENAHW_DEV_STS_READY_MASK) == 0) {
ena_err(ena, "reset: device is not ready");
return (false);
}
rval = ena_hw_bar_read32(ena, ENAHW_REG_CAPS);
timeout = MSEC2NSEC(ENAHW_CAPS_RESET_TIMEOUT(rval) * 100);
if (timeout == 0) {
ena_err(ena, "device gave invalid (0) reset timeout");
return (false);
}
expired = gethrtime() + timeout;
wval = ENAHW_DEV_CTL_DEV_RESET_MASK;
reason_lsb = ENAHW_RESET_REASON_LSB(reason);
reason_msb = ENAHW_RESET_REASON_MSB(reason);
wval |= (reason_lsb << ENAHW_DEV_CTL_RESET_REASON_SHIFT) &
ENAHW_DEV_CTL_RESET_REASON_MASK;
if (ena_is_cap_avail(ena, ENAHW_CAP_EXTENDED_RESET_REASONS)) {
wval |= (reason_msb << ENAHW_DEV_CTL_RESET_REASON_EXT_SHIFT) &
ENAHW_DEV_CTL_RESET_REASON_EXT_MASK;
} else if (reason_msb != 0) {
wval = ENAHW_DEV_CTL_DEV_RESET_MASK;
wval |= (ENAHW_RESET_GENERIC <<
ENAHW_DEV_CTL_RESET_REASON_SHIFT) &
ENAHW_DEV_CTL_RESET_REASON_MASK;
}
ena_hw_bar_write32(ena, ENAHW_REG_DEV_CTL, wval);
for (;;) {
rval = ena_hw_bar_read32(ena, ENAHW_REG_DEV_STS);
if ((rval & ENAHW_DEV_STS_RESET_IN_PROGRESS_MASK) != 0)
break;
if (gethrtime() > expired) {
ena_err(ena, "device reset start timed out");
return (false);
}
delay(drv_usectohz(100 * 1000));
}
expired = gethrtime() + timeout;
ena_hw_bar_write32(ena, ENAHW_REG_DEV_CTL, 0);
for (;;) {
rval = ena_hw_bar_read32(ena, ENAHW_REG_DEV_STS);
if ((rval & ENAHW_DEV_STS_RESET_IN_PROGRESS_MASK) == 0) {
break;
}
if (gethrtime() > expired) {
ena_err(ena, "device reset timed out");
return (false);
}
delay(drv_usectohz(100 * 1000));
}
ena_dbg(ena, "device reset succeeded");
return (true);
}
static bool
ena_attach_pci(ena_t *ena)
{
ddi_acc_handle_t hdl;
if (pci_config_setup(ena->ena_dip, &hdl) != 0) {
return (false);
}
ena->ena_pci_hdl = hdl;
ena->ena_pci_vid = pci_config_get16(hdl, PCI_CONF_VENID);
ena->ena_pci_did = pci_config_get16(hdl, PCI_CONF_DEVID);
ena->ena_pci_rev = pci_config_get8(hdl, PCI_CONF_REVID);
ena->ena_pci_svid = pci_config_get16(hdl, PCI_CONF_SUBVENID);
ena->ena_pci_sdid = pci_config_get16(hdl, PCI_CONF_SUBSYSID);
ena_dbg(ena, "vid: 0x%x did: 0x%x rev: 0x%x svid: 0x%x sdid: 0x%x",
ena->ena_pci_vid, ena->ena_pci_did, ena->ena_pci_rev,
ena->ena_pci_svid, ena->ena_pci_sdid);
return (true);
}
static void
ena_cleanup_pci(ena_t *ena, bool resetting)
{
VERIFY0(resetting);
pci_config_teardown(&ena->ena_pci_hdl);
}
static void
ena_cleanup_regs_map(ena_t *ena, bool resetting)
{
VERIFY0(resetting);
ddi_regs_map_free(&ena->ena_reg_hdl);
}
static int
ena_bar_to_rnumber(ena_t *ena, uint8_t bar)
{
pci_regspec_t *regs;
uint_t bar_offset, regs_length, rcount;
int rnumber = -1;
if (bar > 5)
return (-1);
bar_offset = PCI_CONF_BASE0 + sizeof (uint32_t) * bar;
if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ena->ena_dip,
DDI_PROP_DONTPASS, "reg", (int **)®s, ®s_length) !=
DDI_PROP_SUCCESS) {
return (-1);
}
rcount = regs_length * sizeof (int) / sizeof (pci_regspec_t);
for (int i = 0; i < rcount; i++) {
if (PCI_REG_REG_G(regs[i].pci_phys_hi) == bar_offset) {
rnumber = i;
break;
}
}
ddi_prop_free(regs);
return (rnumber);
}
static bool
ena_attach_regs_map(ena_t *ena)
{
ddi_device_acc_attr_t attr;
int rnumber;
int ret = 0;
rnumber = ena_bar_to_rnumber(ena, ENA_REG_BAR);
if (rnumber == -1) {
ena_err(ena, "failed to find rnumber for BAR %d", ENA_REG_BAR);
return (false);
}
if (ddi_dev_regsize(ena->ena_dip, rnumber, &ena->ena_reg_size) !=
DDI_SUCCESS) {
ena_err(ena, "failed to get register set %d size", rnumber);
return (false);
}
ena_dbg(ena, "register size: %ld", ena->ena_reg_size);
bzero(&attr, sizeof (attr));
attr.devacc_attr_version = DDI_DEVICE_ATTR_V1;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
attr.devacc_attr_access = DDI_DEFAULT_ACC;
ret = ddi_regs_map_setup(ena->ena_dip, rnumber,
&ena->ena_reg_base, 0, ena->ena_reg_size, &attr,
&ena->ena_reg_hdl);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to map BAR %d (reg %d): %d",
ENA_REG_BAR, rnumber, ret);
return (false);
}
ena_dbg(ena, "registers BAR %d mapped to base: 0x%p size: %ld",
ENA_REG_BAR, (void *)ena->ena_reg_base, ena->ena_reg_size);
return (true);
}
static void
ena_admin_sq_free(ena_t *ena)
{
ena_dma_free(&ena->ena_aq.ea_sq.eas_dma);
}
static bool
ena_admin_sq_init(ena_t *ena)
{
ena_adminq_t *aq = &ena->ena_aq;
ena_dma_buf_t *dma = &aq->ea_sq.eas_dma;
size_t size = aq->ea_qlen * sizeof (*aq->ea_sq.eas_entries);
uint32_t addr_low, addr_high, wval;
if (aq->ea_sq.eas_entries == NULL) {
ena_dma_conf_t conf = {
.edc_size = size,
.edc_align = ENAHW_ADMIN_SQ_DESC_BUF_ALIGNMENT,
.edc_sgl = 1,
.edc_endian = DDI_NEVERSWAP_ACC,
.edc_stream = false,
};
if (!ena_dma_alloc(ena, dma, &conf, size)) {
ena_err(ena, "failed to allocate DMA for Admin SQ");
return (false);
}
ENA_DMA_VERIFY_ADDR(ena, dma->edb_cookie->dmac_laddress);
aq->ea_sq.eas_entries = (void *)dma->edb_va;
} else {
ena_dma_bzero(dma);
}
aq->ea_sq.eas_tail = 0;
aq->ea_sq.eas_phase = 1;
aq->ea_sq.eas_dbaddr =
(uint32_t *)(ena->ena_reg_base + ENAHW_REG_ASQ_DB);
addr_low = (uint32_t)(dma->edb_cookie->dmac_laddress);
addr_high = (uint32_t)(dma->edb_cookie->dmac_laddress >> 32);
ena_hw_bar_write32(ena, ENAHW_REG_ASQ_BASE_LO, addr_low);
ena_hw_bar_write32(ena, ENAHW_REG_ASQ_BASE_HI, addr_high);
wval = ENAHW_ASQ_CAPS_DEPTH(aq->ea_qlen) |
ENAHW_ASQ_CAPS_ENTRY_SIZE(sizeof (*aq->ea_sq.eas_entries));
ena_hw_bar_write32(ena, ENAHW_REG_ASQ_CAPS, wval);
return (true);
}
static void
ena_admin_cq_free(ena_t *ena)
{
ena_dma_free(&ena->ena_aq.ea_cq.eac_dma);
}
static bool
ena_admin_cq_init(ena_t *ena)
{
ena_adminq_t *aq = &ena->ena_aq;
ena_dma_buf_t *dma = &aq->ea_cq.eac_dma;
uint32_t addr_low, addr_high, wval;
if (aq->ea_cq.eac_entries == NULL) {
size_t size = aq->ea_qlen * sizeof (*aq->ea_cq.eac_entries);
ena_dma_conf_t conf = {
.edc_size = size,
.edc_align = ENAHW_ADMIN_CQ_DESC_BUF_ALIGNMENT,
.edc_sgl = 1,
.edc_endian = DDI_NEVERSWAP_ACC,
.edc_stream = false,
};
if (!ena_dma_alloc(ena, dma, &conf, size)) {
ena_err(ena, "failed to allocate DMA for Admin CQ");
return (false);
}
ENA_DMA_VERIFY_ADDR(ena, dma->edb_cookie->dmac_laddress);
aq->ea_cq.eac_entries = (void *)dma->edb_va;
} else {
ena_dma_bzero(dma);
}
aq->ea_cq.eac_head = 0;
aq->ea_cq.eac_phase = 1;
addr_low = (uint32_t)(dma->edb_cookie->dmac_laddress);
addr_high = (uint32_t)(dma->edb_cookie->dmac_laddress >> 32);
ena_hw_bar_write32(ena, ENAHW_REG_ACQ_BASE_LO, addr_low);
ena_hw_bar_write32(ena, ENAHW_REG_ACQ_BASE_HI, addr_high);
wval = ENAHW_ACQ_CAPS_DEPTH(aq->ea_qlen) |
ENAHW_ACQ_CAPS_ENTRY_SIZE(sizeof (*aq->ea_cq.eac_entries));
ena_hw_bar_write32(ena, ENAHW_REG_ACQ_CAPS, wval);
return (true);
}
void
ena_update_hints(ena_t *ena, enahw_device_hints_t *hints)
{
ena->ena_device_hints.eh_mmio_read_timeout =
hints->edh_mmio_read_timeout;
ena->ena_device_hints.eh_keep_alive_timeout =
hints->edh_keep_alive_timeout;
ena->ena_device_hints.eh_tx_comp_timeout = hints->edh_tx_comp_timeout;
ena->ena_device_hints.eh_missed_tx_reset_threshold =
hints->edh_missed_tx_reset_threshold;
ena->ena_device_hints.eh_admin_comp_timeout =
hints->edh_admin_comp_timeout;
ena->ena_device_hints.eh_max_tx_sgl = hints->edh_max_tx_sgl;
ena->ena_device_hints.eh_max_rx_sgl = hints->edh_max_rx_sgl;
}
static void
ena_set_max_io_queues(ena_t *ena)
{
uint32_t max = ENAHW_MAX_NUM_IO_QUEUES;
max = MIN(ncpus_online, max);
max = MIN(ena->ena_tx_max_sq_num, max);
max = MIN(ena->ena_tx_max_cq_num, max);
max = MIN(ena->ena_rx_max_sq_num, max);
max = MIN(ena->ena_rx_max_cq_num, max);
if (max == 0) {
max = 1;
}
ena->ena_max_io_queues = max;
}
static void
ena_update_buf_sizes(ena_t *ena)
{
ena->ena_max_frame_hdr = sizeof (struct ether_vlan_header);
ena->ena_max_frame_total = ena->ena_max_frame_hdr + ena->ena_mtu;
ena->ena_tx_buf_sz = P2ROUNDUP_TYPED(ena->ena_max_frame_total,
ena->ena_page_sz, uint32_t);
ena->ena_rx_buf_sz = P2ROUNDUP_TYPED(ena->ena_max_frame_total +
ENA_RX_BUF_IPHDR_ALIGNMENT, ena->ena_page_sz, uint32_t);
}
static bool
ena_get_hints(ena_t *ena)
{
int ret;
enahw_resp_desc_t resp;
enahw_device_hints_t *hints = &resp.erd_resp.erd_get_feat.ergf_hints;
ena_dbg(ena, "Requesting hints");
bzero(&resp, sizeof (resp));
ret = ena_get_feature(ena, &resp, ENAHW_FEAT_HW_HINTS,
ENAHW_FEAT_HW_HINTS_VER);
if (ret == ENOTSUP) {
ena_dbg(ena, "Hints are unsupported");
return (true);
} else if (ret != 0) {
ena_err(ena, "Error getting hints: %d", ret);
return (false);
}
ena_update_hints(ena, hints);
return (true);
}
static bool
ena_get_offloads(ena_t *ena)
{
int ret = 0;
enahw_resp_desc_t resp;
enahw_feat_offload_t *feat = &resp.erd_resp.erd_get_feat.ergf_offload;
ena->ena_tx_l3_ipv4_csum = false;
ena->ena_tx_l4_ipv4_part_csum = false;
ena->ena_tx_l4_ipv4_full_csum = false;
ena->ena_tx_l4_ipv4_lso = false;
ena->ena_tx_l4_ipv6_part_csum = false;
ena->ena_tx_l4_ipv6_full_csum = false;
ena->ena_tx_l4_ipv6_lso = false;
ena->ena_rx_l3_ipv4_csum = false;
ena->ena_rx_l4_ipv4_csum = false;
ena->ena_rx_l4_ipv6_csum = false;
ena->ena_rx_hash = false;
bzero(&resp, sizeof (resp));
ret = ena_get_feature(ena, &resp, ENAHW_FEAT_STATELESS_OFFLOAD_CONFIG,
ENAHW_FEAT_STATELESS_OFFLOAD_CONFIG_VER);
if (ret == ENOTSUP) {
return (true);
} else if (ret != 0) {
ena_err(ena, "error getting stateless offload: %d", ret);
return (false);
}
ena->ena_tx_l3_ipv4_csum = ENAHW_FEAT_OFFLOAD_TX_L3_IPV4_CSUM(feat);
ena->ena_tx_l4_ipv4_part_csum =
ENAHW_FEAT_OFFLOAD_TX_L4_IPV4_CSUM_PART(feat);
ena->ena_tx_l4_ipv4_full_csum =
ENAHW_FEAT_OFFLOAD_TX_L4_IPV4_CSUM_FULL(feat);
ena->ena_tx_l4_ipv4_lso = ENAHW_FEAT_OFFLOAD_TSO_IPV4(feat);
ena->ena_tx_l4_ipv6_part_csum =
ENAHW_FEAT_OFFLOAD_TX_L4_IPV6_CSUM_PART(feat);
ena->ena_tx_l4_ipv6_full_csum =
ENAHW_FEAT_OFFLOAD_TX_L4_IPV6_CSUM_FULL(feat);
ena->ena_tx_l4_ipv6_lso = ENAHW_FEAT_OFFLOAD_TSO_IPV6(feat);
ena->ena_rx_l3_ipv4_csum = ENAHW_FEAT_OFFLOAD_RX_L3_IPV4_CSUM(feat);
ena->ena_rx_l4_ipv4_csum = ENAHW_FEAT_OFFLOAD_RX_L4_IPV4_CSUM(feat);
ena->ena_rx_l4_ipv6_csum = ENAHW_FEAT_OFFLOAD_RX_L4_IPV6_CSUM(feat);
return (true);
}
static int
ena_get_prop(ena_t *ena, char *propname, const int minval, const int maxval,
const int defval)
{
int value = ddi_prop_get_int(DDI_DEV_T_ANY, ena->ena_dip,
DDI_PROP_DONTPASS, propname, defval);
if (value > maxval) {
ena_err(ena, "user value %s=%d exceeded maximum, setting to %d",
propname, value, maxval);
value = maxval;
}
if (value < minval) {
ena_err(ena, "user value %s=%d below minimum, setting to %d",
propname, value, minval);
value = minval;
}
return (value);
}
static bool
ena_set_mtu(ena_t *ena)
{
int ret = 0;
enahw_cmd_desc_t cmd;
enahw_feat_mtu_t *feat = &cmd.ecd_cmd.ecd_set_feat.ecsf_feat.ecsf_mtu;
enahw_resp_desc_t resp;
bzero(&cmd, sizeof (cmd));
bzero(&resp, sizeof (resp));
feat->efm_mtu = ena->ena_mtu;
if ((ret = ena_set_feature(ena, &cmd, &resp, ENAHW_FEAT_MTU,
ENAHW_FEAT_MTU_VER)) != 0) {
ena_err(ena, "failed to set device MTU to %u: %d", ena->ena_mtu,
ret);
return (false);
}
return (true);
}
static void
ena_get_link_config(ena_t *ena)
{
enahw_resp_desc_t resp;
enahw_feat_link_conf_t *feat =
&resp.erd_resp.erd_get_feat.ergf_link_conf;
bool full_duplex;
bzero(&resp, sizeof (resp));
if (ena_get_feature(ena, &resp, ENAHW_FEAT_LINK_CONFIG,
ENAHW_FEAT_LINK_CONFIG_VER) != 0) {
ena->ena_link_speed_mbits = 1000;
ena->ena_link_speeds = ENAHW_LINK_SPEED_1G;
ena->ena_link_duplex = LINK_DUPLEX_FULL;
ena->ena_link_autoneg = true;
return;
}
ena->ena_link_speed_mbits = feat->eflc_speed;
ena->ena_link_speeds = feat->eflc_supported;
full_duplex = ENAHW_FEAT_LINK_CONF_FULL_DUPLEX(feat);
ena->ena_link_duplex = full_duplex ? LINK_DUPLEX_FULL :
LINK_DUPLEX_HALF;
ena->ena_link_autoneg = ENAHW_FEAT_LINK_CONF_AUTONEG(feat);
}
static bool
ena_attach_read_conf(ena_t *ena)
{
uint32_t gcv;
gcv = MIN(ena->ena_rx_max_sq_num_descs, ena->ena_rx_max_cq_num_descs);
ASSERT3U(gcv, <=, INT_MAX);
ena->ena_rxq_num_descs = ena_get_prop(ena, ENA_PROP_RXQ_NUM_DESCS,
ENA_PROP_RXQ_NUM_DESCS_MIN, gcv, gcv);
ena->ena_rxq_intr_limit = ena_get_prop(ena, ENA_PROP_RXQ_INTR_LIMIT,
ENA_PROP_RXQ_INTR_LIMIT_MIN, ENA_PROP_RXQ_INTR_LIMIT_MAX,
ENA_PROP_RXQ_INTR_LIMIT_DEF);
gcv = MIN(ena->ena_tx_max_sq_num_descs, ena->ena_tx_max_cq_num_descs);
ASSERT3U(gcv, <=, INT_MAX);
ena->ena_txq_num_descs = ena_get_prop(ena, ENA_PROP_TXQ_NUM_DESCS,
ENA_PROP_TXQ_NUM_DESCS_MIN, gcv, gcv);
return (true);
}
static bool
ena_attach_dev_cfg(ena_t *ena)
{
ASSERT3U(ena->ena_attach_seq, >=, ENA_ATTACH_READ_CONF);
if (!ena_set_mtu(ena)) {
ena->ena_mtu = 1500;
ena_err(ena, "trying fallback MTU: %u", ena->ena_mtu);
if (!ena_set_mtu(ena)) {
return (false);
}
}
return (true);
}
static bool
ena_check_versions(ena_t *ena)
{
uint32_t dev_vsn = ena_hw_bar_read32(ena, ENAHW_REG_VERSION);
uint32_t ctrl_vsn =
ena_hw_bar_read32(ena, ENAHW_REG_CONTROLLER_VERSION);
ena->ena_dev_major_vsn = ENAHW_DEV_MAJOR_VSN(dev_vsn);
ena->ena_dev_minor_vsn = ENAHW_DEV_MINOR_VSN(dev_vsn);
ena->ena_ctrl_major_vsn = ENAHW_CTRL_MAJOR_VSN(ctrl_vsn);
ena->ena_ctrl_minor_vsn = ENAHW_CTRL_MINOR_VSN(ctrl_vsn);
ena->ena_ctrl_subminor_vsn = ENAHW_CTRL_SUBMINOR_VSN(ctrl_vsn);
ena->ena_ctrl_impl_id = ENAHW_CTRL_IMPL_ID(ctrl_vsn);
ena_dbg(ena, "device version: %u.%u",
ena->ena_dev_major_vsn, ena->ena_dev_minor_vsn);
ena_dbg(ena, "controller version: %u.%u.%u implementation %u",
ena->ena_ctrl_major_vsn, ena->ena_ctrl_minor_vsn,
ena->ena_ctrl_subminor_vsn, ena->ena_ctrl_impl_id);
if (ena->ena_ctrl_subminor_vsn < ENA_CTRL_SUBMINOR_VSN_MIN) {
ena_err(ena, "unsupported controller version: %u.%u.%u",
ena->ena_ctrl_major_vsn, ena->ena_ctrl_minor_vsn,
ena->ena_ctrl_subminor_vsn);
return (false);
}
return (true);
}
static bool
ena_adminq_init(ena_t *ena)
{
ena_adminq_t *aq = &ena->ena_aq;
mutex_init(&aq->ea_sq_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&aq->ea_cq_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&aq->ea_stat_lock, NULL, MUTEX_DRIVER, NULL);
aq->ea_qlen = ENA_ADMINQ_DEPTH;
aq->ea_pending_cmds = 0;
aq->ea_cmd_ctxs = kmem_zalloc(sizeof (ena_cmd_ctx_t) * aq->ea_qlen,
KM_SLEEP);
list_create(&aq->ea_cmd_ctxs_free, sizeof (ena_cmd_ctx_t),
offsetof(ena_cmd_ctx_t, ectx_node));
list_create(&aq->ea_cmd_ctxs_used, sizeof (ena_cmd_ctx_t),
offsetof(ena_cmd_ctx_t, ectx_node));
ena_create_cmd_ctx(ena);
ena_hw_bar_write32(ena, ENAHW_REG_INTERRUPT_MASK, ENAHW_INTR_MASK);
aq->ea_poll_mode = true;
return (true);
}
static void
ena_cleanup_device_init(ena_t *ena, bool resetting)
{
ena_adminq_t *aq = &ena->ena_aq;
VERIFY0(resetting);
if (ena->ena_llq_bar_mapped) {
ddi_regs_map_free(&ena->ena_llq_bar_hdl);
ena->ena_llq_bar_mapped = false;
}
ena_free_host_info(ena);
mutex_destroy(&aq->ea_sq_lock);
mutex_destroy(&aq->ea_cq_lock);
mutex_destroy(&aq->ea_stat_lock);
list_destroy(&aq->ea_cmd_ctxs_free);
list_destroy(&aq->ea_cmd_ctxs_used);
kmem_free(aq->ea_cmd_ctxs, sizeof (ena_cmd_ctx_t) * aq->ea_qlen);
ena_admin_sq_free(ena);
ena_admin_cq_free(ena);
ena_aenq_free(ena);
ena_stat_device_cleanup(ena);
ena_stat_device_basic_cleanup(ena);
ena_stat_device_extended_cleanup(ena);
ena_stat_aenq_cleanup(ena);
}
static bool
ena_disable_hw_timestamp(ena_t *ena)
{
enahw_cmd_desc_t cmd;
enahw_resp_desc_t resp;
enahw_feat_hw_timestamp_t *ts =
&cmd.ecd_cmd.ecd_set_feat.ecsf_feat.ecsf_hw_timestamp;
int ret;
if (!ena_is_feat_avail(ena, ENAHW_FEAT_HW_TIMESTAMP))
return (true);
ena_dbg(ena, "Disabling HW timestamping");
bzero(&cmd, sizeof (cmd));
ts->efhwts_tx = ENAHW_HW_TIMESTAMP_NONE;
ts->efhwts_rx = ENAHW_HW_TIMESTAMP_NONE;
ret = ena_set_feature(ena, &cmd, &resp, ENAHW_FEAT_HW_TIMESTAMP,
ENAHW_FEAT_HW_TIMESTAMP_VER);
if (ret != 0) {
ena_err(ena, "failed to disable HW timestamping: %d", ret);
return (false);
}
return (true);
}
static bool
ena_map_llq_mem_bar(ena_t *ena)
{
ddi_device_acc_attr_t attr;
int rnumber, ret;
rnumber = ena_bar_to_rnumber(ena, ENA_LLQ_BAR);
if (rnumber == -1) {
ena_err(ena, "failed to find rnumber for BAR %d", ENA_LLQ_BAR);
return (false);
}
if (ddi_dev_regsize(ena->ena_dip, rnumber, &ena->ena_llq_bar_size) !=
DDI_SUCCESS) {
ena_err(ena, "failed to get mem BAR %d size", ENA_LLQ_BAR);
return (false);
}
bzero(&attr, sizeof (attr));
attr.devacc_attr_version = DDI_DEVICE_ATTR_V1;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
attr.devacc_attr_dataorder = DDI_STORECACHING_OK_ACC;
attr.devacc_attr_access = DDI_DEFAULT_ACC;
ret = ddi_regs_map_setup(ena->ena_dip, rnumber,
&ena->ena_llq_bar_base, 0, ena->ena_llq_bar_size, &attr,
&ena->ena_llq_bar_hdl);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to map mem BAR %d (reg %d): %d",
ENA_LLQ_BAR, rnumber, ret);
return (false);
}
ena->ena_llq_bar_mapped = true;
ena_dbg(ena, "LLQ BAR %d mapped to base: 0x%p size: %ld", ENA_LLQ_BAR,
(void *)ena->ena_llq_bar_base, ena->ena_llq_bar_size);
return (true);
}
static bool
ena_configure_llq(ena_t *ena)
{
enahw_resp_desc_t resp;
enahw_feat_llq_t *llq_feat;
enahw_cmd_desc_t cmd;
enahw_feat_llq_t *llq_cmd;
enahw_llq_accel_mode_get_t accel_get;
uint16_t supported;
int ret;
bzero(&resp, sizeof (resp));
ret = ena_get_feature(ena, &resp, ENAHW_FEAT_LLQ, ENAHW_FEAT_LLQ_VER);
if (ret == ENOTSUP) {
ena_dbg(ena, "LLQ not supported, using host placement");
return (true);
} else if (ret != 0) {
ena_err(ena, "failed to query LLQ feature: %d", ret);
return (false);
}
llq_feat = &resp.erd_resp.erd_get_feat.ergf_llq;
ena_dbg(ena, "LLQ max_num=%u max_depth=%u",
llq_feat->efllq_max_llq_num, llq_feat->efllq_max_llq_depth);
ena_dbg(ena, "LLQ header_location supported=0x%x "
"entry_size supported=0x%x recommended=%u",
llq_feat->efllq_header_location_ctrl_supported,
llq_feat->efllq_entry_size_ctrl_supported,
llq_feat->efllq_entry_size_recommended);
ena_dbg(ena, "LLQ descs_before_header supported=0x%x "
"stride supported=0x%x",
llq_feat->efllq_desc_num_before_header_supported,
llq_feat->efllq_descs_stride_ctrl_supported);
ena_dbg(ena, "LLQ accel_mode supported_flags=0x%x "
"max_tx_burst_size=%u",
llq_feat->efllq_accel_mode.eam_get.eamg_supported_flags,
llq_feat->efllq_accel_mode.eam_get.eamg_max_tx_burst_size);
supported = llq_feat->efllq_header_location_ctrl_supported;
if (!(supported & ENAHW_LLQ_HEADER_INLINE)) {
ena_err(ena, "device does not support inline header "
"LLQ (supported: 0x%x)", supported);
return (false);
}
ena->ena_llq_header_location = ENAHW_LLQ_HEADER_INLINE;
supported = llq_feat->efllq_descs_stride_ctrl_supported;
if (supported & ENAHW_LLQ_MULTIPLE_DESCS_PER_ENTRY) {
ena->ena_llq_stride_ctrl = ENAHW_LLQ_MULTIPLE_DESCS_PER_ENTRY;
} else if (supported & ENAHW_LLQ_SINGLE_DESC_PER_ENTRY) {
ena->ena_llq_stride_ctrl = ENAHW_LLQ_SINGLE_DESC_PER_ENTRY;
} else {
ena_err(ena, "no supported LLQ stride control (0x%x)",
supported);
return (false);
}
supported = llq_feat->efllq_entry_size_ctrl_supported;
if (llq_feat->efllq_entry_size_recommended != 0 &&
(supported & llq_feat->efllq_entry_size_recommended)) {
ena->ena_llq_entry_size =
llq_feat->efllq_entry_size_recommended;
} else if (supported & ENAHW_LLQ_ENTRY_SIZE_128B) {
ena->ena_llq_entry_size = ENAHW_LLQ_ENTRY_SIZE_128B;
} else if (supported & ENAHW_LLQ_ENTRY_SIZE_192B) {
ena->ena_llq_entry_size = ENAHW_LLQ_ENTRY_SIZE_192B;
} else if (supported & ENAHW_LLQ_ENTRY_SIZE_256B) {
ena->ena_llq_entry_size = ENAHW_LLQ_ENTRY_SIZE_256B;
} else {
ena_err(ena, "no supported LLQ entry size (0x%x)", supported);
return (false);
}
switch (ena->ena_llq_entry_size) {
case ENAHW_LLQ_ENTRY_SIZE_128B:
ena->ena_llq_entry_size_bytes = 128;
break;
case ENAHW_LLQ_ENTRY_SIZE_192B:
ena->ena_llq_entry_size_bytes = 192;
break;
case ENAHW_LLQ_ENTRY_SIZE_256B:
ena->ena_llq_entry_size_bytes = 256;
break;
}
supported = llq_feat->efllq_desc_num_before_header_supported;
if (supported & ENAHW_LLQ_NUM_DESCS_BEFORE_HEADER_2) {
ena->ena_llq_num_descs_before_header =
ENAHW_LLQ_NUM_DESCS_BEFORE_HEADER_2;
} else if (supported & ENAHW_LLQ_NUM_DESCS_BEFORE_HEADER_4) {
ena->ena_llq_num_descs_before_header =
ENAHW_LLQ_NUM_DESCS_BEFORE_HEADER_4;
} else if (supported & ENAHW_LLQ_NUM_DESCS_BEFORE_HEADER_8) {
ena->ena_llq_num_descs_before_header =
ENAHW_LLQ_NUM_DESCS_BEFORE_HEADER_8;
} else {
ena_err(ena, "no supported descs_before_header (0x%x)",
supported);
return (false);
}
accel_get = llq_feat->efllq_accel_mode.eam_get;
const bool limit_tx_burst = (accel_get.eamg_supported_flags &
ENAHW_LLQ_ACCEL_MODE_LIMIT_TX_BURST);
if (limit_tx_burst) {
ena->ena_llq_max_tx_burst_size =
accel_get.eamg_max_tx_burst_size;
}
ena_dbg(ena, "LLQ negotiated: header_location=%u entry_size=%uB "
"descs_before_header=%u stride=%u max_tx_burst=%u",
ena->ena_llq_header_location,
ena->ena_llq_entry_size_bytes,
ena->ena_llq_num_descs_before_header,
ena->ena_llq_stride_ctrl,
ena->ena_llq_max_tx_burst_size);
ena->ena_tx_max_sq_num_descs = llq_feat->efllq_max_llq_depth;
if (ena->ena_llq_entry_size == ENAHW_LLQ_ENTRY_SIZE_256B) {
if (llq_feat->efllq_max_wide_llq_depth != 0) {
ena->ena_tx_max_sq_num_descs = MIN(
ena->ena_tx_max_sq_num_descs,
llq_feat->efllq_max_wide_llq_depth);
} else {
ena->ena_tx_max_sq_num_descs /= 2;
}
}
bzero(&cmd, sizeof (cmd));
llq_cmd = &cmd.ecd_cmd.ecd_set_feat.ecsf_feat.ecsf_llq;
llq_cmd->efllq_header_location_ctrl_enabled =
ena->ena_llq_header_location;
llq_cmd->efllq_entry_size_ctrl_enabled = ena->ena_llq_entry_size;
llq_cmd->efllq_desc_num_before_header_enabled =
ena->ena_llq_num_descs_before_header;
llq_cmd->efllq_descs_stride_ctrl_enabled = ena->ena_llq_stride_ctrl;
llq_cmd->efllq_accel_mode.eam_set.eams_enabled_flags =
ENAHW_LLQ_ACCEL_MODE_DISABLE_META_CACHING |
(limit_tx_burst ? ENAHW_LLQ_ACCEL_MODE_LIMIT_TX_BURST : 0);
bzero(&resp, sizeof (resp));
ret = ena_set_feature(ena, &cmd, &resp, ENAHW_FEAT_LLQ,
ENAHW_FEAT_LLQ_VER);
if (ret != 0) {
ena_err(ena, "failed to set LLQ feature: %d", ret);
return (false);
}
if (!ena_map_llq_mem_bar(ena))
return (false);
ena->ena_llq_enabled = true;
ena_dbg(ena, "LLQ enabled");
return (true);
}
static bool
ena_attach_device_init(ena_t *ena)
{
ena_adminq_t *aq = &ena->ena_aq;
uint32_t rval;
uint8_t dma_width;
hrtime_t cmd_timeout;
enahw_resp_desc_t resp;
enahw_feat_dev_attr_t *feat = &resp.erd_resp.erd_get_feat.ergf_dev_attr;
uint8_t *maddr;
uint32_t supported_features;
int ret = 0;
ena->ena_reset_reason = ENAHW_RESET_NORMAL;
if (!ena_device_reset(ena, ena->ena_reset_reason))
return (false);
if (!ena_check_versions(ena))
return (false);
ena_init_regcache(ena);
rval = ena_hw_bar_read32(ena, ENAHW_REG_CAPS);
dma_width = ENAHW_CAPS_DMA_ADDR_WIDTH(rval);
ena->ena_dma_width = dma_width;
cmd_timeout = MSEC2NSEC(ENAHW_CAPS_ADMIN_CMD_TIMEOUT(rval) * 100);
aq->ea_cmd_timeout_ns = max(cmd_timeout, ena_admin_cmd_timeout_ns);
if (aq->ea_cmd_timeout_ns == 0)
aq->ea_cmd_timeout_ns = ENA_ADMIN_CMD_DEF_TIMEOUT_NS;
if (!ena_adminq_init(ena))
return (false);
if (!ena_admin_sq_init(ena))
return (false);
if (!ena_admin_cq_init(ena))
return (false);
if (!ena_aenq_init(ena))
return (false);
if (!ena_init_host_info(ena))
return (false);
bzero(&resp, sizeof (resp));
ret = ena_get_feature(ena, &resp, ENAHW_FEAT_DEVICE_ATTRIBUTES,
ENAHW_FEAT_DEVICE_ATTRIBUTES_VER);
if (ret != 0) {
ena_err(ena, "failed to get device attributes: %d", ret);
return (false);
}
ena_dbg(ena, "impl ID: %u", feat->efda_impl_id);
ena_dbg(ena, "device version: %u", feat->efda_device_version);
ena_dbg(ena, "supported features: 0x%x",
feat->efda_supported_features);
ena_dbg(ena, "device capabilities: 0x%x", feat->efda_capabilities);
ena_dbg(ena, "phys addr width: %u", feat->efda_phys_addr_width);
ena_dbg(ena, "virt addr width: %u", feat->efda_virt_addr_with);
maddr = feat->efda_mac_addr;
ena_dbg(ena, "mac addr: %x:%x:%x:%x:%x:%x", maddr[0], maddr[1],
maddr[2], maddr[3], maddr[4], maddr[5]);
ena_dbg(ena, "max MTU: %u", feat->efda_max_mtu);
bcopy(maddr, ena->ena_mac_addr, ETHERADDRL);
ena->ena_max_mtu = feat->efda_max_mtu;
ena->ena_capabilities = feat->efda_capabilities;
supported_features = feat->efda_supported_features;
ena->ena_supported_features = supported_features;
feat = NULL;
bzero(&resp, sizeof (resp));
if (ena_is_feat_avail(ena, ENAHW_FEAT_MAX_QUEUES_EXT)) {
enahw_feat_max_queue_ext_t *feat_mqe =
&resp.erd_resp.erd_get_feat.ergf_max_queue_ext;
ret = ena_get_feature(ena, &resp, ENAHW_FEAT_MAX_QUEUES_EXT,
ENAHW_FEAT_MAX_QUEUES_EXT_VER);
if (ret != 0) {
ena_err(ena, "failed to query max queues ext: %d", ret);
return (false);
}
ena->ena_tx_max_sq_num = feat_mqe->efmqe_max_tx_sq_num;
ena->ena_tx_max_sq_num_descs = feat_mqe->efmqe_max_tx_sq_depth;
ena->ena_tx_max_cq_num = feat_mqe->efmqe_max_tx_cq_num;
ena->ena_tx_max_cq_num_descs = feat_mqe->efmqe_max_tx_cq_depth;
ena->ena_tx_max_desc_per_pkt =
feat_mqe->efmqe_max_per_packet_tx_descs;
ena->ena_tx_max_hdr_len = feat_mqe->efmqe_max_tx_header_size;
ena->ena_rx_max_sq_num = feat_mqe->efmqe_max_rx_sq_num;
ena->ena_rx_max_sq_num_descs = feat_mqe->efmqe_max_rx_sq_depth;
ena->ena_rx_max_cq_num = feat_mqe->efmqe_max_rx_cq_num;
ena->ena_rx_max_cq_num_descs = feat_mqe->efmqe_max_rx_cq_depth;
ena->ena_rx_max_desc_per_pkt =
feat_mqe->efmqe_max_per_packet_rx_descs;
ena_set_max_io_queues(ena);
} else {
enahw_feat_max_queue_t *feat_mq =
&resp.erd_resp.erd_get_feat.ergf_max_queue;
ret = ena_get_feature(ena, &resp, ENAHW_FEAT_MAX_QUEUES_NUM,
ENAHW_FEAT_MAX_QUEUES_NUM_VER);
if (ret != 0) {
ena_err(ena, "failed to query max queues: %d", ret);
return (false);
}
ena->ena_tx_max_sq_num = feat_mq->efmq_max_sq_num;
ena->ena_tx_max_sq_num_descs = feat_mq->efmq_max_sq_depth;
ena->ena_tx_max_cq_num = feat_mq->efmq_max_cq_num;
ena->ena_tx_max_cq_num_descs = feat_mq->efmq_max_cq_depth;
ena->ena_tx_max_desc_per_pkt =
feat_mq->efmq_max_per_packet_tx_descs;
ena->ena_tx_max_hdr_len = feat_mq->efmq_max_header_size;
ena->ena_rx_max_sq_num = feat_mq->efmq_max_sq_num;
ena->ena_rx_max_sq_num_descs = feat_mq->efmq_max_sq_depth;
ena->ena_rx_max_cq_num = feat_mq->efmq_max_cq_num;
ena->ena_rx_max_cq_num_descs = feat_mq->efmq_max_cq_depth;
ena->ena_rx_max_desc_per_pkt =
feat_mq->efmq_max_per_packet_rx_descs;
ena_set_max_io_queues(ena);
}
ena->ena_mtu = ena->ena_max_mtu;
ena_update_buf_sizes(ena);
if (!ena_get_hints(ena))
return (false);
ena->ena_tx_sgl_max_sz = 1;
ena->ena_rx_sgl_max_sz = 1;
if (ena->ena_device_hints.eh_max_tx_sgl != 0)
ena->ena_tx_sgl_max_sz = ena->ena_device_hints.eh_max_tx_sgl;
if (ena->ena_device_hints.eh_max_rx_sgl != 0)
ena->ena_rx_sgl_max_sz = ena->ena_device_hints.eh_max_rx_sgl;
if (!ena_disable_hw_timestamp(ena))
return (false);
if (!ena_configure_llq(ena))
return (false);
if (!ena_aenq_configure(ena))
return (false);
ena_get_link_config(ena);
if (!ena_get_offloads(ena))
return (false);
if (!ena_stat_device_init(ena))
return (false);
if (!ena_stat_device_basic_init(ena))
return (false);
if (!ena_stat_device_extended_init(ena))
return (false);
if (!ena_stat_aenq_init(ena))
return (false);
ena_update_regcache(ena);
return (true);
}
static void
ena_cleanup_intr_alloc(ena_t *ena, bool resetting)
{
VERIFY0(resetting);
for (int i = 0; i < ena->ena_num_intrs; i++) {
int ret = ddi_intr_free(ena->ena_intr_handles[i]);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to free interrupt %d: %d", i, ret);
}
}
if (ena->ena_intr_handles != NULL) {
kmem_free(ena->ena_intr_handles, ena->ena_intr_handles_sz);
ena->ena_intr_handles = NULL;
ena->ena_intr_handles_sz = 0;
}
}
static bool
ena_attach_intr_alloc(ena_t *ena)
{
int ret;
int types;
int min, req, ideal, avail, actual;
ret = ddi_intr_get_supported_types(ena->ena_dip, &types);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to get interrupt types: %d", ret);
return (false);
}
ena_dbg(ena, "supported interrupt types: 0x%x", types);
if ((types & DDI_INTR_TYPE_MSIX) == 0) {
ena_err(ena, "the ena driver only supports MSI-X interrupts");
return (false);
}
min = 2;
ideal = ena->ena_max_io_queues + 1;
ret = ddi_intr_get_nintrs(ena->ena_dip, DDI_INTR_TYPE_MSIX, &avail);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to get number of MSI-X interrupts: %d",
ret);
return (false);
}
if (avail < min) {
ena_err(ena, "number of MSI-X interrupts is %d, but the driver "
"requires a minimum of %d", avail, min);
return (false);
}
ena_dbg(ena, "%d MSI-X interrupts available", avail);
ret = ddi_intr_get_navail(ena->ena_dip, DDI_INTR_TYPE_MSIX, &avail);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to get available interrupts: %d", ret);
return (false);
}
if (avail < min) {
ena_err(ena, "number of available MSI-X interrupts is %d, "
"but the driver requires a minimum of %d", avail, min);
return (false);
}
req = MIN(ideal, avail);
ena->ena_intr_handles_sz = req * sizeof (ddi_intr_handle_t);
ena->ena_intr_handles = kmem_zalloc(ena->ena_intr_handles_sz, KM_SLEEP);
ret = ddi_intr_alloc(ena->ena_dip, ena->ena_intr_handles,
DDI_INTR_TYPE_MSIX, 0, req, &actual, DDI_INTR_ALLOC_NORMAL);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to allocate %d MSI-X interrupts: %d",
req, ret);
return (false);
}
if (actual < min) {
ena_err(ena, "number of allocated interrupts is %d, but the "
"driver requires a minimum of %d", actual, min);
return (false);
}
ena->ena_num_intrs = actual;
ret = ddi_intr_get_cap(ena->ena_intr_handles[0], &ena->ena_intr_caps);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to get interrupt capability: %d", ret);
return (false);
}
ret = ddi_intr_get_pri(ena->ena_intr_handles[0], &ena->ena_intr_pri);
if (ret != DDI_SUCCESS) {
ena_err(ena, "failed to get interrupt priority: %d", ret);
return (false);
}
ena_dbg(ena, "MSI-X interrupts allocated: %d, cap: 0x%x, pri: %u",
actual, ena->ena_intr_caps, ena->ena_intr_pri);
mutex_init(&ena->ena_lock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(ena->ena_intr_pri));
mutex_init(&ena->ena_watchdog_lock, NULL, MUTEX_DRIVER, NULL);
return (true);
}
static bool
ena_attach_alloc_rxqs(ena_t *ena)
{
bool resetting = false;
if (ena->ena_rxqs == NULL) {
VERIFY3U(ena->ena_attach_seq, >=, ENA_ATTACH_INTR_ALLOC);
ena->ena_num_rxqs = ena->ena_num_intrs - 1;
ASSERT3U(ena->ena_num_rxqs, >, 0);
ena->ena_rxqs = kmem_zalloc(
ena->ena_num_rxqs * sizeof (*ena->ena_rxqs), KM_SLEEP);
} else {
resetting = true;
}
for (uint_t i = 0; i < ena->ena_num_rxqs; i++) {
ena_rxq_t *rxq = &ena->ena_rxqs[i];
rxq->er_rxqs_idx = i;
rxq->er_intr_vector = i + 1;
rxq->er_mrh = NULL;
if (!resetting) {
mutex_init(&rxq->er_lock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(ena->ena_intr_pri));
mutex_init(&rxq->er_stat_lock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(ena->ena_intr_pri));
}
rxq->er_ena = ena;
rxq->er_sq_num_descs = ena->ena_rxq_num_descs;
rxq->er_cq_num_descs = ena->ena_rxq_num_descs;
if (!ena_stat_rxq_init(rxq)) {
return (false);
}
if (!ena_alloc_rxq(rxq)) {
ena_stat_rxq_cleanup(rxq);
return (false);
}
}
return (true);
}
static void
ena_cleanup_rxqs(ena_t *ena, bool resetting)
{
for (uint_t i = 0; i < ena->ena_num_rxqs; i++) {
ena_rxq_t *rxq = &ena->ena_rxqs[i];
ena_cleanup_rxq(rxq, resetting);
if (!resetting) {
mutex_destroy(&rxq->er_lock);
mutex_destroy(&rxq->er_stat_lock);
}
ena_stat_rxq_cleanup(rxq);
}
if (!resetting) {
kmem_free(ena->ena_rxqs,
ena->ena_num_rxqs * sizeof (*ena->ena_rxqs));
ena->ena_rxqs = NULL;
}
}
static bool
ena_attach_alloc_txqs(ena_t *ena)
{
bool resetting = false;
if (ena->ena_txqs == NULL) {
VERIFY3U(ena->ena_attach_seq, >=, ENA_ATTACH_INTR_ALLOC);
ena->ena_num_txqs = ena->ena_num_intrs - 1;
ASSERT3U(ena->ena_num_txqs, >, 0);
ena->ena_txqs = kmem_zalloc(
ena->ena_num_txqs * sizeof (*ena->ena_txqs), KM_SLEEP);
} else {
resetting = true;
}
for (uint_t i = 0; i < ena->ena_num_txqs; i++) {
ena_txq_t *txq = &ena->ena_txqs[i];
txq->et_txqs_idx = i;
txq->et_intr_vector = i + 1;
txq->et_mrh = NULL;
if (!resetting) {
mutex_init(&txq->et_lock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(ena->ena_intr_pri));
mutex_init(&txq->et_stat_lock, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(ena->ena_intr_pri));
}
txq->et_ena = ena;
txq->et_sq_num_descs = ena->ena_txq_num_descs;
txq->et_cq_num_descs = ena->ena_txq_num_descs;
if (!ena_stat_txq_init(txq)) {
return (false);
}
if (!ena_alloc_txq(txq)) {
ena_stat_txq_cleanup(txq);
return (false);
}
}
return (true);
}
static void
ena_cleanup_txqs(ena_t *ena, bool resetting)
{
for (uint_t i = 0; i < ena->ena_num_txqs; i++) {
ena_txq_t *txq = &ena->ena_txqs[i];
ena_cleanup_txq(txq, resetting);
if (!resetting) {
mutex_destroy(&txq->et_lock);
mutex_destroy(&txq->et_stat_lock);
}
ena_stat_txq_cleanup(txq);
}
if (!resetting) {
kmem_free(ena->ena_txqs,
ena->ena_num_txqs * sizeof (*ena->ena_txqs));
ena->ena_txqs = NULL;
}
}
bool
ena_reset(ena_t *ena, const enahw_reset_reason_t reason)
{
ena_txq_state_t tx_state[ena->ena_num_txqs];
ena_rxq_state_t rx_state[ena->ena_num_rxqs];
bool ret = false;
ena_err(ena, "resetting device with reason 0x%x [%s]",
reason, enahw_reset_reason(reason));
VERIFY0(ena->ena_state & ENA_STATE_RESETTING);
atomic_or_32(&ena->ena_state, ENA_STATE_RESETTING);
VERIFY(ena->ena_state & ENA_STATE_STARTED);
atomic_and_32(&ena->ena_state, ~ENA_STATE_STARTED);
mutex_enter(&ena->ena_lock);
ena_update_regcache(ena);
for (uint_t i = 0; i < ena->ena_num_txqs; i++) {
ena_txq_t *txq = &ena->ena_txqs[i];
mutex_enter(&txq->et_lock);
tx_state[i] = txq->et_state;
if (txq->et_state & ENA_TXQ_STATE_RUNNING)
ena_ring_tx_stop((mac_ring_driver_t)txq);
}
for (uint_t i = 0; i < ena->ena_num_rxqs; i++) {
ena_rxq_t *rxq = &ena->ena_rxqs[i];
mutex_enter(&rxq->er_lock);
rx_state[i] = rxq->er_state;
if (rxq->er_state & ENA_RXQ_STATE_RUNNING)
ena_ring_rx_stop((mac_ring_driver_t)rxq);
}
if (!ena_device_reset(ena, reason)) {
ena_err(ena, "reset: failed to reset device");
goto out;
}
ena_hw_bar_write32(ena, ENAHW_REG_INTERRUPT_MASK, ENAHW_INTR_MASK);
ena_cleanup_txqs(ena, true);
ena_cleanup_rxqs(ena, true);
ena_release_all_cmd_ctx(ena);
if (!ena_admin_cq_init(ena) || !ena_admin_sq_init(ena)) {
ena_err(ena, "reset: failed to program admin queues");
goto out;
}
if (!ena_init_host_info(ena)) {
ena_err(ena, "reset: failed to set host info");
goto out;
}
if (!ena_aenq_init(ena) || !ena_aenq_configure(ena)) {
ena_err(ena, "reset: failed to configure aenq");
goto out;
}
if (!ena_set_mtu(ena)) {
ena_err(ena, "reset: failed to set MTU");
goto out;
}
if (!ena_attach_alloc_txqs(ena) || !ena_attach_alloc_rxqs(ena)) {
ena_err(ena, "reset: failed to program IO queues");
goto out;
}
ena_aenq_enable(ena);
ena_hw_bar_write32(ena, ENAHW_REG_INTERRUPT_MASK, ENAHW_INTR_UNMASK);
for (uint_t i = 0; i < ena->ena_num_rxqs; i++) {
ena_rxq_t *rxq = &ena->ena_rxqs[i];
mutex_exit(&rxq->er_lock);
if (rx_state[i] & ENA_RXQ_STATE_RUNNING) {
(void) ena_ring_rx_start((mac_ring_driver_t)rxq,
rxq->er_m_gen_num);
}
}
for (uint_t i = 0; i < ena->ena_num_txqs; i++) {
ena_txq_t *txq = &ena->ena_txqs[i];
mutex_exit(&txq->et_lock);
if (tx_state[i] & ENA_TXQ_STATE_RUNNING) {
(void) ena_ring_tx_start((mac_ring_driver_t)txq,
txq->et_m_gen_num);
}
}
atomic_or_32(&ena->ena_state, ENA_STATE_STARTED);
ret = true;
out:
atomic_and_32(&ena->ena_state, ~ENA_STATE_RESETTING);
mutex_exit(&ena->ena_lock);
ena_update_regcache(ena);
return (ret);
}
ena_attach_desc_t ena_attach_tbl[ENA_ATTACH_NUM_ENTRIES] = {
{
.ead_seq = ENA_ATTACH_PCI,
.ead_name = "PCI config",
.ead_attach_fn = ena_attach_pci,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = ena_cleanup_pci,
},
{
.ead_seq = ENA_ATTACH_REGS,
.ead_name = "BAR mapping",
.ead_attach_fn = ena_attach_regs_map,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = ena_cleanup_regs_map,
},
{
.ead_seq = ENA_ATTACH_DEV_INIT,
.ead_name = "device initialization",
.ead_attach_fn = ena_attach_device_init,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = ena_cleanup_device_init,
},
{
.ead_seq = ENA_ATTACH_READ_CONF,
.ead_name = "ena.conf",
.ead_attach_fn = ena_attach_read_conf,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = NULL,
},
{
.ead_seq = ENA_ATTACH_DEV_CFG,
.ead_name = "device config",
.ead_attach_fn = ena_attach_dev_cfg,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = NULL,
},
{
.ead_seq = ENA_ATTACH_INTR_ALLOC,
.ead_name = "interrupt allocation",
.ead_attach_fn = ena_attach_intr_alloc,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = ena_cleanup_intr_alloc,
},
{
.ead_seq = ENA_ATTACH_INTR_HDLRS,
.ead_name = "interrupt handlers",
.ead_attach_fn = ena_intr_add_handlers,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = ena_intr_remove_handlers,
},
{
.ead_seq = ENA_ATTACH_TXQS_ALLOC,
.ead_name = "Tx queues",
.ead_attach_fn = ena_attach_alloc_txqs,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = ena_cleanup_txqs,
},
{
.ead_seq = ENA_ATTACH_RXQS_ALLOC,
.ead_name = "Rx queues",
.ead_attach_fn = ena_attach_alloc_rxqs,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = ena_cleanup_rxqs,
},
{
.ead_seq = ENA_ATTACH_MAC_REGISTER,
.ead_name = "mac registration",
.ead_attach_fn = ena_mac_register,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = NULL,
},
{
.ead_seq = ENA_ATTACH_INTRS_ENABLE,
.ead_name = "enable interrupts",
.ead_attach_fn = ena_intrs_enable,
.ead_attach_hard_fail = true,
.ead_cleanup_fn = NULL,
}
};
static void
ena_cleanup(ena_t *ena)
{
if (ena == NULL || ena->ena_attach_seq == 0) {
return;
}
VERIFY3U(ena->ena_attach_seq, <, ENA_ATTACH_NUM_ENTRIES);
while (ena->ena_attach_seq > 0) {
int idx = ena->ena_attach_seq - 1;
ena_attach_desc_t *desc = &ena_attach_tbl[idx];
ena_dbg(ena, "running cleanup sequence: %s (%d)",
desc->ead_name, idx);
if (desc->ead_cleanup_fn != NULL)
desc->ead_cleanup_fn(ena, false);
ena->ena_attach_seq--;
}
ASSERT3U(ena->ena_attach_seq, ==, 0);
mutex_destroy(&ena->ena_lock);
mutex_destroy(&ena->ena_watchdog_lock);
}
static int
ena_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
ena_t *ena;
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
ena = kmem_zalloc(sizeof (ena_t), KM_SLEEP);
ena->ena_instance = ddi_get_instance(dip);
ena->ena_dip = dip;
ena->ena_instance = ddi_get_instance(dip);
ena->ena_page_sz = ddi_ptob(dip, 1);
for (int i = 0; i < ENA_ATTACH_NUM_ENTRIES; i++) {
bool success;
ena_attach_desc_t *desc = &ena_attach_tbl[i];
ena_dbg(ena, "running attach sequence: %s (%d)", desc->ead_name,
i);
if (!(success = desc->ead_attach_fn(ena))) {
ena_err(ena, "attach sequence failed: %s (%d)",
desc->ead_name, i);
if (ena->ena_attach_seq == ENA_ATTACH_MAC_REGISTER) {
if (ena_mac_unregister(ena) != 0) {
return (DDI_FAILURE);
}
ena->ena_attach_seq--;
} else {
if (desc->ead_cleanup_fn != NULL)
desc->ead_cleanup_fn(ena, false);
}
ena_cleanup(ena);
kmem_free(ena, sizeof (ena_t));
return (DDI_FAILURE);
}
if (success) {
ena_dbg(ena, "attach sequence completed: %s (%d)",
desc->ead_name, i);
}
ena->ena_attach_seq = desc->ead_seq;
}
ena_hw_bar_write32(ena, ENAHW_REG_INTERRUPT_MASK, ENAHW_INTR_UNMASK);
ena_aenq_enable(ena);
ddi_set_driver_private(dip, ena);
ena_update_regcache(ena);
atomic_or_32(&ena->ena_state, ENA_STATE_INITIALIZED);
return (DDI_SUCCESS);
}
static int
ena_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
ena_t *ena = ddi_get_driver_private(dip);
if (ena == NULL) {
return (DDI_FAILURE);
}
if (!ena_intrs_disable(ena)) {
return (DDI_FAILURE);
}
if (ena_mac_unregister(ena) != 0) {
(void) ena_intrs_enable(ena);
return (DDI_FAILURE);
}
ena->ena_attach_seq = ENA_ATTACH_RXQS_ALLOC;
ena_cleanup(ena);
ddi_set_driver_private(dip, NULL);
kmem_free(ena, sizeof (ena_t));
return (DDI_SUCCESS);
}
static struct cb_ops ena_cb_ops = {
.cb_open = nodev,
.cb_close = nodev,
.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 ena_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_getinfo = NULL,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = ena_attach,
.devo_detach = ena_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_supported,
.devo_cb_ops = &ena_cb_ops
};
static struct modldrv ena_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "AWS ENA Ethernet",
.drv_dev_ops = &ena_dev_ops
};
static struct modlinkage ena_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &ena_modldrv, NULL }
};
int
_init(void)
{
int ret;
mac_init_ops(&ena_dev_ops, ENA_MODULE_NAME);
if ((ret = mod_install(&ena_modlinkage)) != 0) {
mac_fini_ops(&ena_dev_ops);
return (ret);
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&ena_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&ena_modlinkage)) != 0) {
return (ret);
}
mac_fini_ops(&ena_dev_ops);
return (ret);
}