/* SPDX-License-Identifier: Apache-2.0 * Copyright (c) 2023 Cisco Systems, Inc. */ #include #include #include #include #include #include #include #include #include #define IIAVF_AQ_LARGE_BUF 512 #define IIAVF_AQ_ATQ_LEN 4 #define IIAVF_AQ_ARQ_LEN 16 VLIB_REGISTER_LOG_CLASS (iavf_log, static) = { .class_name = "iavf", .subclass_name = "adminq", }; struct iavf_adminq_dma_mem { iavf_aq_desc_t atq[IIAVF_AQ_ATQ_LEN]; iavf_aq_desc_t arq[IIAVF_AQ_ARQ_LEN]; struct { u8 data[IIAVF_AQ_BUF_SIZE]; } atq_bufs[IIAVF_AQ_ATQ_LEN]; struct { u8 data[IIAVF_AQ_BUF_SIZE]; } arq_bufs[IIAVF_AQ_ARQ_LEN]; }; static const iavf_dyn_ctl dyn_ctl0_disable = { .itr_indx = 3, }; static const iavf_dyn_ctl dyn_ctl0_enable = { .intena = 1, .clearpba = 1, .itr_indx = 3, }; static const iavf_vfint_icr0_ena1 icr0_ena1_aq_enable = { .adminq = 1, }; static inline void iavf_irq_0_disable (iavf_device_t *ad) { iavf_reg_write (ad, IAVF_VFINT_ICR0_ENA1, 0); iavf_reg_write (ad, IAVF_VFINT_DYN_CTL0, dyn_ctl0_disable.as_u32); iavf_reg_flush (ad); } static inline void iavf_irq_0_enable (iavf_device_t *ad) { iavf_reg_write (ad, IAVF_VFINT_ICR0_ENA1, icr0_ena1_aq_enable.as_u32); iavf_reg_write (ad, IAVF_VFINT_DYN_CTL0, dyn_ctl0_enable.as_u32); iavf_reg_flush (ad); } static_always_inline int iavf_aq_desc_is_done (iavf_aq_desc_t *d) { iavf_aq_desc_flags_t flags; flags.as_u16 = __atomic_load_n (&d->flags.as_u16, __ATOMIC_ACQUIRE); return flags.dd; } static u8 * format_iavf_aq_desc_flags (u8 *s, va_list *args) { iavf_aq_desc_flags_t f = va_arg (*args, iavf_aq_desc_flags_t); int i = 0; #define _(n, v) \ if (f.v) \ { \ char str[] = #v, *sp = str; \ if (i++) \ { \ vec_add1 (s, ','); \ vec_add1 (s, ' '); \ } \ while (sp[0]) \ vec_add1 (s, (u8) toupper (sp++[0])); \ } foreach_iavf_aq_desc_flag #undef _ return s; } static u8 * format_iavf_aq_desc_retval (u8 *s, va_list *args) { iavf_aq_desc_retval_t rv = va_arg (*args, u32); char *retvals[] = { #define _(a, b) [a] = #b, foreach_iavf_aq_desc_retval #undef _ }; if (rv >= ARRAY_LEN (retvals) || retvals[rv] == 0) return format (s, "UNKNOWN(%d)", rv); return format (s, "%s", retvals[rv]); } static u8 * format_iavf_aq_desc (u8 *s, va_list *args) { iavf_aq_desc_t *d = va_arg (*args, iavf_aq_desc_t *); u32 indent = format_get_indent (s); s = format (s, "opcode 0x%04x datalen %u retval %U (%u) flags %U", d->opcode, d->datalen, format_iavf_aq_desc_retval, d->retval, d->retval, format_iavf_aq_desc_flags, d->flags); if (d->opcode == IIAVF_AQ_DESC_OP_SEND_TO_PF || d->opcode == IIAVF_AQ_DESC_OP_MESSAGE_FROM_PF) { s = format (s, "\n%Uv_opcode %U (%u) v_retval %U (%d) buf_dma_addr 0x%lx", format_white_space, indent, format_virtchnl_op_name, d->v_opcode, d->v_opcode, format_virtchnl_status, d->v_retval, d->v_retval, (uword) d->param2 << 32 | d->param3); } else { s = format ( s, "\n%Ucookie_hi 0x%x cookie_lo 0x%x params %08x %08x %08x %08x", format_white_space, indent, d->cookie_hi, d->cookie_lo, d->param0, d->param1, d->param2, d->param3); } return s; } vnet_dev_rv_t iavf_aq_alloc (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); return vnet_dev_dma_mem_alloc (vm, dev, sizeof (iavf_adminq_dma_mem_t), 0, (void **) &ad->aq_mem); } void iavf_aq_free (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); vnet_dev_dma_mem_free (vm, dev, ad->aq_mem); } static void iavf_aq_arq_slot_init (vlib_main_t *vm, vnet_dev_t *dev, u16 slot) { iavf_device_t *ad = vnet_dev_get_data (dev); u64 pa = vnet_dev_get_dma_addr (vm, dev, ad->aq_mem->arq_bufs + slot); ad->aq_mem->arq[slot] = (iavf_aq_desc_t){ .flags.buf = 1, .flags.lb = IIAVF_AQ_BUF_SIZE > IIAVF_AQ_LARGE_BUF, .datalen = sizeof (ad->aq_mem->arq_bufs[0].data), .addr_hi = (u32) (pa >> 32), .addr_lo = (u32) pa, }; } static void iavf_aq_poll (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); iavf_aq_desc_t *d; u8 *b; while (iavf_aq_arq_next_acq (vm, dev, &d, &b, 0)) { log_debug (dev, "poll[%u] flags %x %U op %u v_op %u", ad->arq_next_slot, d->flags.as_u16, format_iavf_aq_desc_flags, d->flags, d->opcode, d->v_opcode); if ((d->datalen != sizeof (virtchnl_pf_event_t)) || ((d->flags.buf) == 0)) { log_err (dev, "event message error"); } vec_add1 (ad->events, *(virtchnl_pf_event_t *) b); iavf_aq_arq_next_rel (vm, dev); } if (vec_len (ad->events)) { virtchnl_pf_event_t *e; char *virtchnl_event_names[] = { #define _(v, n) [v] = #n, foreach_virtchnl_event_code #undef _ }; vec_foreach (e, ad->events) { log_debug (dev, "event %s (%u) sev %d", virtchnl_event_names[e->event], e->event, e->severity); if (e->event == VIRTCHNL_EVENT_LINK_CHANGE) { vnet_dev_port_state_changes_t changes = {}; vnet_dev_port_t *port = vnet_dev_get_port_by_id (dev, 0); if (port) { iavf_port_t *ap = vnet_dev_get_port_data (port); int link_up; u32 speed = 0; if (ap->vf_cap_flags & VIRTCHNL_VF_CAP_ADV_LINK_SPEED) { link_up = e->event_data.link_event_adv.link_status; speed = e->event_data.link_event_adv.link_speed; } else { const u32 speed_table[8] = { 100, 1000, 10000, 40000, 20000, 25000, 2500, 5000 }; link_up = e->event_data.link_event.link_status; speed = e->event_data.link_event.link_speed; if (count_set_bits (speed) == 1 && speed && pow2_mask (8)) speed = speed_table[get_lowest_set_bit_index (speed)]; else { if (link_up) log_warn (dev, "unsupported link speed value " "received (0x%x)", speed); speed = 0; } } log_debug (dev, "LINK_CHANGE speed %u state %u", speed, link_up); if (port->link_up != link_up) { changes.change.link_state = 1; changes.link_state = link_up; log_debug (dev, "link state changed to %s", link_up ? "up" : "down"); } if (port->speed != speed * 1000) { changes.change.link_speed = 1; changes.link_speed = speed * 1000; log_debug (dev, "link speed changed to %u Mbps", speed); } if (changes.change.any) vnet_dev_port_state_change (vm, port, changes); } } } vec_reset_length (ad->events); } } static void iavf_adminq_msix_handler (vlib_main_t *vm, vnet_dev_t *dev, u16 line) { iavf_device_t *ad = vnet_dev_get_data (dev); iavf_reg_write (ad, IAVF_VFINT_DYN_CTL0, dyn_ctl0_enable.as_u32); log_debug (dev, "MSI-X interrupt %u received", line); vnet_dev_process_call_op_no_wait (vm, dev, iavf_aq_poll); } static void iavf_adminq_intx_handler (vlib_main_t *vm, vnet_dev_t *dev) { iavf_adminq_msix_handler (vm, dev, 0); } void iavf_aq_init (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); uword pa; u32 len; /* disable both tx and rx adminq queue */ iavf_reg_write (ad, IAVF_ATQLEN, 0); iavf_reg_write (ad, IAVF_ARQLEN, 0); len = IIAVF_AQ_ATQ_LEN; pa = vnet_dev_get_dma_addr (vm, dev, &ad->aq_mem->atq); iavf_reg_write (ad, IAVF_ATQT, 0); /* Tail */ iavf_reg_write (ad, IAVF_ATQH, 0); /* Head */ iavf_reg_write (ad, IAVF_ATQBAL, (u32) pa); /* Base Address Low */ iavf_reg_write (ad, IAVF_ATQBAH, (u32) (pa >> 32)); /* Base Address High */ iavf_reg_write (ad, IAVF_ATQLEN, len | (1ULL << 31)); /* len & ena */ len = IIAVF_AQ_ARQ_LEN; pa = vnet_dev_get_dma_addr (vm, dev, ad->aq_mem->arq); iavf_reg_write (ad, IAVF_ARQT, 0); /* Tail */ iavf_reg_write (ad, IAVF_ARQH, 0); /* Head */ iavf_reg_write (ad, IAVF_ARQBAL, (u32) pa); /* Base Address Low */ iavf_reg_write (ad, IAVF_ARQBAH, (u32) (pa >> 32)); /* Base Address High */ iavf_reg_write (ad, IAVF_ARQLEN, len | (1ULL << 31)); /* len & ena */ for (int i = 0; i < len; i++) iavf_aq_arq_slot_init (vm, dev, i); iavf_reg_write (ad, IAVF_ARQT, len - 1); /* Tail */ ad->atq_next_slot = 0; ad->arq_next_slot = 0; ad->adminq_active = 1; } void iavf_aq_poll_on (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); vnet_dev_poll_dev_add (vm, dev, IIAVF_AQ_POLL_INTERVAL, iavf_aq_poll); if (vnet_dev_get_pci_n_msix_interrupts (dev) > 0) { vnet_dev_pci_msix_add_handler (vm, dev, iavf_adminq_msix_handler, 0, 1); vnet_dev_pci_msix_enable (vm, dev, 0, 1); } else vnet_dev_pci_intx_add_handler (vm, dev, iavf_adminq_intx_handler); iavf_irq_0_enable (ad); } void iavf_aq_poll_off (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); iavf_irq_0_disable (ad); vnet_dev_poll_dev_remove (vm, dev, iavf_aq_poll); if (vnet_dev_get_pci_n_msix_interrupts (dev) > 0) { vnet_dev_pci_msix_disable (vm, dev, 0, 1); vnet_dev_pci_msix_remove_handler (vm, dev, 0, 1); } else vnet_dev_pci_intx_remove_handler (vm, dev); } vnet_dev_rv_t iavf_aq_atq_enq (vlib_main_t *vm, vnet_dev_t *dev, iavf_aq_desc_t *desc, const u8 *data, u16 len, f64 timeout) { iavf_device_t *ad = vnet_dev_get_data (dev); iavf_aq_desc_t *d = ad->aq_mem->atq + ad->atq_next_slot; u8 *buf = ad->aq_mem->atq_bufs[ad->atq_next_slot].data; ASSERT (len <= IIAVF_AQ_BUF_SIZE); *d = *desc; if (len) { u64 pa = vnet_dev_get_dma_addr (vm, dev, buf); d->datalen = len; d->addr_hi = (u32) (pa >> 32); d->addr_lo = (u32) pa; d->flags.buf = 1; d->flags.rd = 1; d->flags.lb = len > IIAVF_AQ_LARGE_BUF; clib_memcpy_fast (buf, data, len); } log_debug (dev, "slot %u\n %U", ad->atq_next_slot, format_iavf_aq_desc, d); ad->atq_next_slot = (ad->atq_next_slot + 1) % IIAVF_AQ_ATQ_LEN; iavf_reg_write (ad, IAVF_ATQT, ad->atq_next_slot); iavf_reg_flush (ad); if (timeout > 0) { f64 suspend_time = timeout / 62; f64 t0 = vlib_time_now (vm); iavf_aq_desc_flags_t flags; while (1) { flags.as_u16 = __atomic_load_n (&d->flags.as_u16, __ATOMIC_ACQUIRE); if (flags.err) { log_err (dev, "adminq enqueue error [opcode 0x%x, retval %d]", d->opcode, d->retval); return VNET_DEV_ERR_BUG; } if (flags.dd && flags.cmp) return VNET_DEV_OK; if (vlib_time_now (vm) - t0 > timeout) { log_err (dev, "adminq enqueue timeout [opcode 0x%x]", d->opcode); return VNET_DEV_ERR_TIMEOUT; } vlib_process_suspend (vm, suspend_time); suspend_time *= 2; } } return VNET_DEV_OK; } void iavf_aq_deinit (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); if (ad->adminq_active) { iavf_aq_desc_t d = { .opcode = IIAVF_AQ_DESC_OP_QUEUE_SHUTDOWN, .driver_unloading = 1, .flags = { .si = 1 }, }; log_debug (dev, "adminq queue shutdown"); iavf_aq_atq_enq (vm, dev, &d, 0, 0, 0); ad->adminq_active = 0; } } int iavf_aq_arq_next_acq (vlib_main_t *vm, vnet_dev_t *dev, iavf_aq_desc_t **dp, u8 **bp, f64 timeout) { iavf_device_t *ad = vnet_dev_get_data (dev); iavf_aq_desc_t *d = ad->aq_mem->arq + ad->arq_next_slot; if (timeout) { f64 suspend_time = timeout / 62; f64 t0 = vlib_time_now (vm); while (!iavf_aq_desc_is_done (d)) { if (vlib_time_now (vm) - t0 > timeout) return 0; vlib_process_suspend (vm, suspend_time); suspend_time *= 2; } } else if (!iavf_aq_desc_is_done (d)) return 0; log_debug (dev, "arq desc acquired in slot %u\n %U", ad->arq_next_slot, format_iavf_aq_desc, d); *dp = d; *bp = ad->aq_mem->arq_bufs[ad->arq_next_slot].data; return 1; } void iavf_aq_arq_next_rel (vlib_main_t *vm, vnet_dev_t *dev) { iavf_device_t *ad = vnet_dev_get_data (dev); ASSERT (iavf_aq_desc_is_done (ad->aq_mem->arq + ad->arq_next_slot)); iavf_aq_arq_slot_init (vm, dev, ad->arq_next_slot); iavf_reg_write (ad, IAVF_ARQT, ad->arq_next_slot); iavf_reg_flush (ad); ad->arq_next_slot = (ad->arq_next_slot + 1) % IIAVF_AQ_ARQ_LEN; }