/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2024 Oxide Computer Company */ /* * This file contains everything having to do with communicating with * the admin queue for sending commands to the device. */ #include "ena_hw.h" #include "ena.h" /* * Mark the context as complete (a response has been received). */ static void ena_complete_cmd_ctx(ena_cmd_ctx_t *ctx, enahw_resp_desc_t *hwresp) { bcopy(hwresp, ctx->ectx_resp, sizeof (*hwresp)); ctx->ectx_pending = false; } static inline void ena_reset_cmd_ctx(ena_cmd_ctx_t *ctx) { ctx->ectx_pending = false; ctx->ectx_resp = NULL; ctx->ectx_cmd_opcode = ENAHW_CMD_NONE; } /* * Reset and release the context back to the free list. */ static void ena_release_cmd_ctx(ena_t *ena, ena_cmd_ctx_t *ctx) { ASSERT(ctx->ectx_pending == false); ena_reset_cmd_ctx(ctx); mutex_enter(&ena->ena_aq.ea_sq_lock); /* * We return the free descriptor to the end of the list so that we * cycle through them with each admin command, and don't end up almost * always re-using the same entry with the same command ID. While the * controller does not appear to mind, it's a little counter-intuitive. */ list_remove(&ena->ena_aq.ea_cmd_ctxs_used, ctx); list_insert_tail(&ena->ena_aq.ea_cmd_ctxs_free, ctx); ena->ena_aq.ea_pending_cmds--; mutex_exit(&ena->ena_aq.ea_sq_lock); } void ena_release_all_cmd_ctx(ena_t *ena) { ena_adminq_t *aq = &ena->ena_aq; ena_cmd_ctx_t *ctx; mutex_enter(&aq->ea_sq_lock); while ((ctx = list_remove_head(&aq->ea_cmd_ctxs_used)) != NULL) { ena_reset_cmd_ctx(ctx); list_insert_tail(&aq->ea_cmd_ctxs_free, ctx); } aq->ea_pending_cmds = 0; mutex_exit(&aq->ea_sq_lock); } void ena_create_cmd_ctx(ena_t *ena) { ena_adminq_t *aq = &ena->ena_aq; for (uint_t i = 0; i < aq->ea_qlen; i++) { ena_cmd_ctx_t *ctx = &aq->ea_cmd_ctxs[i]; ctx->ectx_id = i; ena_reset_cmd_ctx(ctx); list_insert_tail(&aq->ea_cmd_ctxs_free, ctx); } } /* * Acquire the next available command context. */ static ena_cmd_ctx_t * ena_acquire_cmd_ctx(ena_adminq_t *aq) { VERIFY(MUTEX_HELD(&aq->ea_sq_lock)); ASSERT3U(aq->ea_pending_cmds, <, aq->ea_qlen); ena_cmd_ctx_t *ctx = list_remove_head(&aq->ea_cmd_ctxs_free); list_insert_head(&aq->ea_cmd_ctxs_used, ctx); ctx->ectx_pending = true; return (ctx); } /* * Submit a command to the admin queue. */ int ena_admin_submit_cmd(ena_t *ena, enahw_cmd_desc_t *cmd, enahw_resp_desc_t *resp, ena_cmd_ctx_t **ctx) { VERIFY3U(cmd->ecd_opcode, !=, 0); ena_adminq_t *aq = &ena->ena_aq; ena_admin_sq_t *sq = &aq->ea_sq; const uint16_t modulo_mask = aq->ea_qlen - 1; ena_cmd_ctx_t *lctx = NULL; mutex_enter(&aq->ea_sq_lock); uint16_t tail_mod = sq->eas_tail & modulo_mask; if (aq->ea_pending_cmds >= aq->ea_qlen) { mutex_enter(&aq->ea_stat_lock); aq->ea_stats.queue_full++; mutex_exit(&aq->ea_stat_lock); mutex_exit(&aq->ea_sq_lock); return (ENOSPC); } lctx = ena_acquire_cmd_ctx(aq); lctx->ectx_cmd_opcode = cmd->ecd_opcode; lctx->ectx_resp = resp; cmd->ecd_flags = sq->eas_phase & ENAHW_CMD_PHASE_MASK; ENAHW_CMD_ID(cmd, lctx->ectx_id); bcopy(cmd, &sq->eas_entries[tail_mod], sizeof (*cmd)); ENA_DMA_SYNC(sq->eas_dma, DDI_DMA_SYNC_FORDEV); sq->eas_tail++; aq->ea_pending_cmds++; mutex_enter(&aq->ea_stat_lock); aq->ea_stats.cmds_submitted++; mutex_exit(&aq->ea_stat_lock); DTRACE_PROBE4(cmd__submit, enahw_cmd_desc_t *, cmd, ena_cmd_ctx_t *, lctx, uint16_t, tail_mod, uint8_t, sq->eas_phase); if ((sq->eas_tail & modulo_mask) == 0) { sq->eas_phase ^= 1; } ena_hw_abs_write32(ena, sq->eas_dbaddr, sq->eas_tail); mutex_exit(&aq->ea_sq_lock); *ctx = lctx; return (0); } /* * Read a single response from the admin queue. */ static void ena_admin_read_resp(ena_t *ena, enahw_resp_desc_t *hwresp) { ena_adminq_t *aq = &ena->ena_aq; ena_admin_cq_t *cq = &aq->ea_cq; ena_cmd_ctx_t *ctx = NULL; uint16_t modulo_mask = aq->ea_qlen - 1; VERIFY(MUTEX_HELD(&aq->ea_cq_lock)); uint16_t head_mod = cq->eac_head & modulo_mask; uint8_t phase = cq->eac_phase & ENAHW_RESP_PHASE_MASK; uint16_t cmd_id = ENAHW_RESP_CMD_ID(hwresp); ctx = &aq->ea_cmd_ctxs[cmd_id]; ASSERT3U(ctx->ectx_id, ==, cmd_id); ena_complete_cmd_ctx(ctx, hwresp); if (hwresp->erd_status != ENAHW_RESP_SUCCESS) { mutex_enter(&aq->ea_stat_lock); aq->ea_stats.cmds_fail++; mutex_exit(&aq->ea_stat_lock); DTRACE_PROBE4(cmd__fail, enahw_resp_desc_t *, hwresp, ena_cmd_ctx_t *, ctx, uint16_t, head_mod, uint8_t, phase); return; } DTRACE_PROBE4(cmd__success, enahw_resp_desc_t *, hwresp, ena_cmd_ctx_t *, ctx, uint16_t, head_mod, uint8_t, phase); mutex_enter(&aq->ea_stat_lock); aq->ea_stats.cmds_success++; mutex_exit(&aq->ea_stat_lock); } static void ena_admin_process_responses(ena_t *ena) { ena_adminq_t *aq = &ena->ena_aq; ena_admin_cq_t *cq = &aq->ea_cq; uint16_t modulo_mask = aq->ea_qlen - 1; enahw_resp_desc_t *hwresp; mutex_enter(&aq->ea_cq_lock); uint16_t head_mod = cq->eac_head & modulo_mask; uint8_t phase = cq->eac_phase & ENAHW_RESP_PHASE_MASK; ENA_DMA_SYNC(cq->eac_dma, DDI_DMA_SYNC_FORKERNEL); hwresp = &cq->eac_entries[head_mod]; while ((hwresp->erd_flags & ENAHW_RESP_PHASE_MASK) == phase) { ena_admin_read_resp(ena, hwresp); cq->eac_head++; head_mod = cq->eac_head & modulo_mask; if (head_mod == 0) { phase ^= 1; } hwresp = &cq->eac_entries[head_mod]; } cq->eac_phase = phase; mutex_exit(&aq->ea_cq_lock); } /* * Wait for the command described by ctx to complete by polling for * status updates. */ int ena_admin_poll_for_resp(ena_t *ena, ena_cmd_ctx_t *ctx) { int ret = 0; hrtime_t expire = gethrtime() + ena->ena_aq.ea_cmd_timeout_ns; for (;;) { ena_admin_process_responses(ena); if (!ctx->ectx_pending) { break; } /* Wait for 1 millisecond. */ delay(drv_usectohz(1000)); if (gethrtime() > expire) { /* * We have no visibility into the device to * confirm it is making progress on this * command. At this point the driver and * device cannot agree on the state of the * world: perhaps the device is still making * progress but not fast enough, perhaps the * device completed the command but there was * a failure to deliver the reply, perhaps the * command failed but once again the reply was * not delivered. With this unknown state the * best thing to do is to reset the device and * start from scratch. There is even a reset * reason code just for this. */ ena_err(ena, "timed out waiting for admin response"); ena_trigger_reset(ena, ENAHW_RESET_ADMIN_TO); return (EIO); } } ret = enahw_resp_status_to_errno(ena, ctx->ectx_resp->erd_status); ena_release_cmd_ctx(ena, ctx); return (ret); } void ena_free_host_info(ena_t *ena) { ena_dma_free(&ena->ena_host_info); } bool ena_init_host_info(ena_t *ena) { enahw_host_info_t *ehi; int ret = 0; int *regs; uint_t nregs; ena_dma_buf_t *hi_dma; enahw_cmd_desc_t cmd; enahw_feat_host_attr_t *ha_cmd = &cmd.ecd_cmd.ecd_set_feat.ecsf_feat.ecsf_host_attr; enahw_resp_desc_t resp; hi_dma = &ena->ena_host_info; if (hi_dma->edb_va == NULL) { ena_dma_conf_t conf = { .edc_size = ENAHW_HOST_INFO_ALLOC_SZ, .edc_align = ENAHW_HOST_INFO_ALIGNMENT, .edc_sgl = 1, .edc_endian = DDI_NEVERSWAP_ACC, .edc_stream = false, }; if (!ena_dma_alloc(ena, hi_dma, &conf, 4096)) { ena_err(ena, "failed to allocate DMA for host info"); return (false); } } ehi = (void *)hi_dma->edb_va; ehi->ehi_ena_spec_version = ((ENA_SPEC_VERSION_MAJOR << ENAHW_HOST_INFO_SPEC_MAJOR_SHIFT) | (ENA_SPEC_VERSION_MINOR)); ehi->ehi_bdf = 0; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ena->ena_dip, DDI_PROP_DONTPASS, "reg", ®s, &nregs) == DDI_PROP_SUCCESS) { if (nregs != 0) { ehi->ehi_bdf |= PCI_REG_BUS_G(regs[0]) << 8; ehi->ehi_bdf |= PCI_REG_DEV_G(regs[0]) << 3; ehi->ehi_bdf |= PCI_REG_FUNC_G(regs[0]); } ddi_prop_free(regs); } /* * There is no illumos OS type, it would be nice to ping * someone at Amazon and see if we can't get one added. */ ehi->ehi_os_type = ENAHW_OS_FREEBSD; ehi->ehi_kernel_ver = 511; /* If you know you know */ (void) strlcpy((char *)ehi->ehi_kernel_ver_str, utsname.version, sizeof (ehi->ehi_kernel_ver_str)); ehi->ehi_os_dist = 0; /* What everyone else does. */ ehi->ehi_driver_ver = (ENA_MODULE_VER_MAJOR) | (ENA_MODULE_VER_MINOR << ENAHW_HOST_INFO_MINOR_SHIFT) | (ENA_MODULE_VER_SUBMINOR << ENAHW_HOST_INFO_SUB_MINOR_SHIFT); ehi->ehi_num_cpus = ncpus_online; /* * ENA devices are not created equal. Some will support * features not found in others. This field tells the device * which features the driver supports. * * ENAHW_HOST_INFO_RX_OFFSET * * Some ENA devices will write the frame data at an offset * in the buffer, presumably for alignment purposes. We * support this feature for the sole reason that the Linux * driver does as well. * * ENAHW_HOST_INFO_INTERRUPT_MODERATION * * Based on the Linux history this flag indicates that the * driver "supports interrupt moderation properly". What * that means is anyone's guess. The Linux driver seems to * have some "adaptive" interrupt moderation, so perhaps * it's that? In any case, FreeBSD doesn't bother with * setting this flag, so we'll leave it be for now as well. * * If you're curious to know if the device supports * interrupt moderation: the FEAT_INTERRUPT_MODERATION flag * will be set in ena_hw.eh_supported_features. * * ENAHW_HOST_INFO_RX_BUF_MIRRORING * * Support traffic mirroring by allowing the hypervisor to * read the buffer memory directly. This probably has to do * with AWS flow logs, allowing more efficient mirroring. * But it's hard to say for sure given we only have the * Linux commit log to go off of. In any case, the only * requirement for this feature is that the Rx DMA buffers * be read/write, which they are. * * ENAHW_HOST_INFO_RSS_CONFIGURABLE_FUNCTION_KEY * * The device supports the retrieving and updating of the * RSS function and hash key. As we don't yet implement RSS * this is disabled. * * ENA_ADMIN_HOST_INFO_RX_PAGE_REUSE * * Dynamic Rx Buffer feature. This feature allows the driver to * avoid additional Rx buffer allocations by effectively using a * buffer more than once if there is space remaining after receiving * a packet. We currently use fixed TCBs and rings and don't * implement this feature. * * ENA_ADMIN_HOST_INFO_TX_IPV6_CSUM_OFFLOAD * * Indicate that the driver supports Tx IPv6 checksum offload. * * ENA_ADMIN_HOST_INFO_PHC * * Instructs the device to enable its PHC (Precision Time Protocol * Hardware Clock). In Linux, this would be exposed to userland NTP * software as a PTP device. We don't support this so leave it * disabled. */ ehi->ehi_driver_supported_features = ENAHW_HOST_INFO_RX_OFFSET_MASK | ENAHW_HOST_INFO_RX_BUF_MIRRORING_MASK; ENA_DMA_SYNC(*hi_dma, DDI_DMA_SYNC_FORDEV); bzero(&cmd, sizeof (cmd)); ena_set_dma_addr(ena, hi_dma->edb_cookie->dmac_laddress, &ha_cmd->efha_os_addr); /* * You might notice the "debug area" is not allocated or * configured, that is on purpose. * * The "debug area" is a region of host memory that contains * the String Set (SS) tables used to report statistics to * tools like ethtool (on Linux). This table consists of one * of more entries of a 32-byte string (the name of the * statistic) along with its associated 64-bit value. The * stats reported here contain both the host-side stats as * well as device-reported stats (ENAHW_GET_STATS_TYPE_ENI). I * believe the reason for calling it the "debug area" is that * it can be accessed from outside of the guest, allowing an * AWS user (?) or Amazon employee to get basic information * about the state of the device from the guest's point of * view. * * In the fullness of time, our driver should probably support * this aspect of ENA. For the time being, all testing * indicates the driver and device function fine without it. */ ret = ena_set_feature(ena, &cmd, &resp, ENAHW_FEAT_HOST_ATTR_CONFIG, ENAHW_FEAT_HOST_ATTR_CONFIG_VER); if (ret != 0) { ena_err(ena, "failed to set host attributes: %d", ret); ena_dma_free(hi_dma); return (false); } return (true); } int ena_create_cq(ena_t *ena, uint16_t num_descs, uint64_t phys_addr, bool is_tx, uint32_t vector, uint16_t *hw_index, uint32_t **unmask_addr, uint32_t **numanode) { int ret; enahw_cmd_desc_t cmd; enahw_cmd_create_cq_t *cmd_cq = &cmd.ecd_cmd.ecd_create_cq; enahw_resp_desc_t resp; enahw_resp_create_cq_t *resp_cq = &resp.erd_resp.erd_create_cq; ena_cmd_ctx_t *ctx = NULL; uint8_t desc_size = is_tx ? sizeof (enahw_tx_cdesc_t) : sizeof (enahw_rx_cdesc_t); bzero(&cmd, sizeof (cmd)); bzero(&resp, sizeof (resp)); cmd.ecd_opcode = ENAHW_CMD_CREATE_CQ; ENAHW_CMD_CREATE_CQ_INTERRUPT_MODE_ENABLE(cmd_cq); ASSERT3U(desc_size % 4, ==, 0); ENAHW_CMD_CREATE_CQ_DESC_SIZE_WORDS(cmd_cq, desc_size / 4); cmd_cq->ecq_num_descs = num_descs; cmd_cq->ecq_msix_vector = vector; ena_set_dma_addr(ena, phys_addr, &cmd_cq->ecq_addr); if ((ret = ena_admin_submit_cmd(ena, &cmd, &resp, &ctx)) != 0) { ena_err(ena, "failed to submit Create CQ command: %d", ret); return (ret); } if ((ret = ena_admin_poll_for_resp(ena, ctx)) != 0) { ena_err(ena, "failed to Create CQ: %d", ret); return (ret); } *hw_index = resp_cq->ercq_idx; *unmask_addr = (uint32_t *)(ena->ena_reg_base + resp_cq->ercq_interrupt_mask_reg_offset); if (resp_cq->ercq_numa_node_reg_offset != 0) { *numanode = (uint32_t *)(ena->ena_reg_base + resp_cq->ercq_numa_node_reg_offset); } else { *numanode = NULL; } /* * The CQ head doorbell register is no longer supported by any * existing adapter hardware. */ VERIFY0(resp_cq->ercq_head_db_reg_offset); return (0); } int ena_destroy_cq(ena_t *ena, uint16_t hw_idx) { enahw_cmd_desc_t cmd; enahw_resp_desc_t resp; ena_cmd_ctx_t *ctx = NULL; int ret; bzero(&cmd, sizeof (cmd)); bzero(&resp, sizeof (resp)); cmd.ecd_opcode = ENAHW_CMD_DESTROY_CQ; cmd.ecd_cmd.ecd_destroy_cq.edcq_idx = hw_idx; if ((ret = ena_admin_submit_cmd(ena, &cmd, &resp, &ctx)) != 0) { ena_err(ena, "failed to submit Destroy CQ command: %d", ret); return (ret); } if ((ret = ena_admin_poll_for_resp(ena, ctx)) != 0) { ena_err(ena, "failed to Destroy CQ: %d", ret); return (ret); } return (0); } int ena_create_sq(ena_t *ena, uint16_t num_descs, uint64_t phys_addr, bool is_tx, uint16_t cq_index, uint16_t *hw_index, uint32_t **db_addr) { int ret; enahw_cmd_desc_t cmd; enahw_cmd_create_sq_t *cmd_sq = &cmd.ecd_cmd.ecd_create_sq; enahw_resp_desc_t resp; enahw_resp_create_sq_t *resp_sq = &resp.erd_resp.erd_create_sq; enahw_sq_direction_t dir = is_tx ? ENAHW_SQ_DIRECTION_TX : ENAHW_SQ_DIRECTION_RX; ena_cmd_ctx_t *ctx = NULL; if (!ISP2(num_descs)) { ena_err(ena, "the number of descs must be a power of 2, but " " is %d", num_descs); return (false); } bzero(&cmd, sizeof (cmd)); bzero(&resp, sizeof (resp)); cmd.ecd_opcode = ENAHW_CMD_CREATE_SQ; ENAHW_CMD_CREATE_SQ_DIR(cmd_sq, dir); ENAHW_CMD_CREATE_SQ_PLACEMENT_POLICY(cmd_sq, ENAHW_PLACEMENT_POLICY_HOST); ENAHW_CMD_CREATE_SQ_COMPLETION_POLICY(cmd_sq, ENAHW_COMPLETION_POLICY_DESC); /* * We limit all SQ descriptor rings to an SGL of 1, therefore * they are always physically contiguous. */ ENAHW_CMD_CREATE_SQ_PHYSMEM_CONTIG(cmd_sq); cmd_sq->ecsq_cq_idx = cq_index; cmd_sq->ecsq_num_descs = num_descs; /* * If we ever use a non-host placement policy, then guard this * code against placement type (this value should not be set * for device placement). */ ena_set_dma_addr(ena, phys_addr, &cmd_sq->ecsq_base); if ((ret = ena_admin_submit_cmd(ena, &cmd, &resp, &ctx)) != 0) { ena_err(ena, "failed to submit Create SQ command: %d", ret); return (ret); } if ((ret = ena_admin_poll_for_resp(ena, ctx)) != 0) { ena_err(ena, "failed to Create SQ: %d", ret); return (ret); } *hw_index = resp_sq->ersq_idx; *db_addr = (uint32_t *)(ena->ena_reg_base + resp_sq->ersq_db_reg_offset); return (0); } int ena_destroy_sq(ena_t *ena, uint16_t hw_idx, bool is_tx) { enahw_cmd_desc_t cmd; enahw_cmd_destroy_sq_t *cmd_sq = &cmd.ecd_cmd.ecd_destroy_sq; enahw_sq_direction_t dir = is_tx ? ENAHW_SQ_DIRECTION_TX : ENAHW_SQ_DIRECTION_RX; enahw_resp_desc_t resp; ena_cmd_ctx_t *ctx = NULL; int ret; bzero(&cmd, sizeof (cmd)); bzero(&resp, sizeof (resp)); cmd.ecd_opcode = ENAHW_CMD_DESTROY_SQ; cmd_sq->edsq_idx = hw_idx; ENAHW_CMD_DESTROY_SQ_DIR(cmd_sq, dir); if ((ret = ena_admin_submit_cmd(ena, &cmd, &resp, &ctx)) != 0) { ena_err(ena, "failed to submit Destroy SQ command: %d", ret); return (ret); } if ((ret = ena_admin_poll_for_resp(ena, ctx)) != 0) { ena_err(ena, "failed Destroy SQ: %d", ret); return (ret); } return (0); } int ena_set_feature(ena_t *ena, enahw_cmd_desc_t *cmd, enahw_resp_desc_t *resp, const enahw_feature_id_t feat_id, const uint8_t feat_ver) { enahw_cmd_set_feat_t *cmd_sf = &cmd->ecd_cmd.ecd_set_feat; ena_cmd_ctx_t *ctx = NULL; int ret = 0; if (!ena_is_feat_avail(ena, feat_id)) { ena_err(ena, "attempted to set unsupported feature: 0x%x %d" " (0x%x)", feat_id, feat_ver, ena->ena_supported_features); return (ENOTSUP); } cmd->ecd_opcode = ENAHW_CMD_SET_FEATURE; cmd_sf->ecsf_comm.efc_id = feat_id; cmd_sf->ecsf_comm.efc_version = feat_ver; cmd_sf->ecsf_comm.efc_flags = 0; if ((ret = ena_admin_submit_cmd(ena, cmd, resp, &ctx)) != 0) { ena_err(ena, "failed to submit Set Feature command: %d", ret); return (ret); } return (ena_admin_poll_for_resp(ena, ctx)); } int ena_get_feature(ena_t *ena, enahw_resp_desc_t *resp, const enahw_feature_id_t feat_id, const uint8_t feat_ver) { enahw_cmd_desc_t cmd; enahw_cmd_get_feat_t *cmd_gf = &cmd.ecd_cmd.ecd_get_feat; ena_cmd_ctx_t *ctx = NULL; int ret = 0; if (!ena_is_feat_avail(ena, feat_id)) { return (ENOTSUP); } bzero(&cmd, sizeof (cmd)); cmd.ecd_opcode = ENAHW_CMD_GET_FEATURE; cmd_gf->ecgf_comm.efc_id = feat_id; cmd_gf->ecgf_comm.efc_version = feat_ver; ENAHW_GET_FEAT_FLAGS_GET_CURR_VAL(cmd_gf); if ((ret = ena_admin_submit_cmd(ena, &cmd, resp, &ctx)) != 0) { ena_err(ena, "failed to submit Get Feature command: %d", ret); return (ret); } return (ena_admin_poll_for_resp(ena, ctx)); } int ena_admin_get_basic_stats(ena_t *ena, enahw_resp_desc_t *resp) { int ret = 0; enahw_cmd_desc_t cmd; enahw_cmd_get_stats_t *cmd_stats = &cmd.ecd_cmd.ecd_get_stats; ena_cmd_ctx_t *ctx = NULL; bzero(&cmd, sizeof (cmd)); bzero(resp, sizeof (*resp)); cmd.ecd_opcode = ENAHW_CMD_GET_STATS; cmd_stats->ecgs_type = ENAHW_GET_STATS_TYPE_BASIC; cmd_stats->ecgs_scope = ENAHW_GET_STATS_SCOPE_ETH; cmd_stats->ecgs_device_id = ENAHW_CMD_GET_STATS_MY_DEVICE_ID; if ((ret = ena_admin_submit_cmd(ena, &cmd, resp, &ctx)) != 0) { ena_err(ena, "failed to submit Get Basic Stats command: %d", ret); return (ret); } if ((ret = ena_admin_poll_for_resp(ena, ctx)) != 0) { ena_err(ena, "failed to Get Basic Stats: %d", ret); return (ret); } return (0); } int ena_admin_get_eni_stats(ena_t *ena, enahw_resp_desc_t *resp) { int ret = 0; enahw_cmd_desc_t cmd; enahw_cmd_get_stats_t *cmd_stats = &cmd.ecd_cmd.ecd_get_stats; ena_cmd_ctx_t *ctx = NULL; bzero(&cmd, sizeof (cmd)); bzero(resp, sizeof (*resp)); cmd.ecd_opcode = ENAHW_CMD_GET_STATS; cmd_stats->ecgs_type = ENAHW_GET_STATS_TYPE_ENI; cmd_stats->ecgs_scope = ENAHW_GET_STATS_SCOPE_ETH; cmd_stats->ecgs_device_id = ENAHW_CMD_GET_STATS_MY_DEVICE_ID; if ((ret = ena_admin_submit_cmd(ena, &cmd, resp, &ctx)) != 0) { ena_err(ena, "failed to submit Get ENI Stats command: %d", ret); return (ret); } if ((ret = ena_admin_poll_for_resp(ena, ctx)) != 0) { ena_err(ena, "failed to Get ENI Stats: %d", ret); return (ret); } return (0); }