diff --git a/share/man/man4/ix.4 b/share/man/man4/ix.4 --- a/share/man/man4/ix.4 +++ b/share/man/man4/ix.4 @@ -138,7 +138,7 @@ driver supports the following .Xr sysctl 8 variables: -.Bl -tag -width "dev.ix.?.debug.dump.clusters" +.Bl -tag -width "dev.ix.?.debug.fw_log.severity." .It Va dev.ix.?.debug.dump.clusters Specify a bitmask to select firmware event clusters to be included in the debug dump. @@ -160,6 +160,38 @@ and decoded by Intel Customer Support. .Pp This feature is only supported on E610. +.It Va dev.ix.?.debug.fw_log.severity. +Specify firmware logging verbosity level for the specified module. +Available levels include: +.Pp +.Bl -tag -compact +.It 0 +none +.It 1 +error +.It 2 +warning +.It 3 +normal +.It 4 +verbose +.El +.Pp +Supported modules: general, ctrl, link, link_topo, dnl, i2c, sdp, mdio, +adminq, hdma, lldp, dcbx, dcb, xlr, nvm, auth, vpd, iosf, parser, sw, +scheduler, txq, acl, post, watchdog, task_dispatch, mng, synce, health, +tsdrv, pfreg, mdlver. +.Pp +This feature is only supported on E610 devices. +.It Va dev.ix.?.debug.fw_log.register +Specify 1 to apply per-device firmware logging configuration. +.Pp +This feature is only supported on E610 devices. +.It Va dev.ix.?.debug.fw_log.on_load +Enable firmware logging during driver initialization when set via +.Xr kenv 1 . +.Pp +This feature is only supported on E610 devices. .El .Sh DIAGNOSTICS .Bl -diag diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -2302,6 +2302,8 @@ compile-with "${NORMAL_C} -I$S/dev/ixgbe" dev/ixgbe/ixgbe_common.c optional ix inet | ixv inet \ compile-with "${NORMAL_C} -I$S/dev/ixgbe" +dev/ixgbe/ixgbe_fw_logging.c optional ix inet | ixv inet \ + compile-with "${NORMAL_C} -I$S/dev/ixgbe" dev/ixgbe/ixgbe_mbx.c optional ix inet | ixv inet \ compile-with "${NORMAL_C} -I$S/dev/ixgbe" dev/ixgbe/ixgbe_vf.c optional ix inet | ixv inet \ diff --git a/sys/dev/ixgbe/if_ix.c b/sys/dev/ixgbe/if_ix.c --- a/sys/dev/ixgbe/if_ix.c +++ b/sys/dev/ixgbe/if_ix.c @@ -1118,10 +1118,13 @@ break; } - /* Check the FW API version */ - if (hw->mac.type == ixgbe_mac_E610 && ixgbe_check_fw_api_version(sc)) { - error = EIO; - goto err_pci; + /* Check the FW API version and enable FW logging support for E610 */ + if (hw->mac.type == ixgbe_mac_E610) { + if (ixgbe_check_fw_api_version(sc)) { + error = EIO; + goto err_pci; + } + ixgbe_fwlog_set_support_ena(hw); } /* Most of the iflib initialization... */ @@ -3395,6 +3398,9 @@ if (sc->feat_en & IXGBE_FEATURE_DBG_DUMP) ixgbe_add_debug_dump_sysctls(sc); + + if (sc->feat_en & IXGBE_FEATURE_FW_LOGGING) + ixgbe_add_fw_logging_tunables(sc, sc->debug_sysctls); } /* ixgbe_add_debug_sysctls */ /************************************************************************ @@ -4491,6 +4497,10 @@ sc->task_requests |= IXGBE_REQUEST_TASK_LSC; break; + case ixgbe_aci_opc_fw_logs_event: + ixgbe_fwlog_event_dump(&sc->hw, &event.desc, event.msg_buf); + break; + case ixgbe_aci_opc_temp_tca_event: if (hw->adapter_stopped == FALSE) ixgbe_if_stop(ctx); diff --git a/sys/dev/ixgbe/ixgbe.h b/sys/dev/ixgbe/ixgbe.h --- a/sys/dev/ixgbe/ixgbe.h +++ b/sys/dev/ixgbe/ixgbe.h @@ -607,6 +607,11 @@ void ixgbe_free_receive_structures(struct ixgbe_softc *); int ixgbe_get_regs(SYSCTL_HANDLER_ARGS); +void ixgbe_add_fw_logging_tunables(struct ixgbe_softc *sc, + struct sysctl_oid *parent); + +#define IXGBE_STR_BUF_LEN 32 + #include "ixgbe_bypass.h" #include "ixgbe_fdir.h" #include "ixgbe_rss.h" diff --git a/sys/dev/ixgbe/ixgbe_e610.h b/sys/dev/ixgbe/ixgbe_e610.h --- a/sys/dev/ixgbe/ixgbe_e610.h +++ b/sys/dev/ixgbe/ixgbe_e610.h @@ -169,6 +169,19 @@ s32 ixgbe_aci_set_health_status_config(struct ixgbe_hw *hw, u8 event_source); +s32 ixgbe_fwlog_init(struct ixgbe_hw *hw, struct ixgbe_fwlog_cfg *cfg); +bool ixgbe_fwlog_supported(struct ixgbe_hw *hw); +s32 ixgbe_fwlog_set(struct ixgbe_hw *hw, struct ixgbe_fwlog_cfg *cfg); +s32 ixgbe_fwlog_update_modules(struct ixgbe_hw *hw, + struct ixgbe_fwlog_module_entry *entries, + u16 num_entries); +s32 ixgbe_fwlog_register(struct ixgbe_hw *hw); +s32 ixgbe_fwlog_unregister(struct ixgbe_hw *hw); +void ixgbe_fwlog_set_support_ena(struct ixgbe_hw *hw); +s32 ixgbe_fwlog_get(struct ixgbe_hw *hw, struct ixgbe_fwlog_cfg *cfg); +void ixgbe_fwlog_event_dump(struct ixgbe_hw *hw, + struct ixgbe_aci_desc *desc, void *buf); + /* E610 operations */ s32 ixgbe_init_ops_E610(struct ixgbe_hw *hw); s32 ixgbe_reset_hw_E610(struct ixgbe_hw *hw); diff --git a/sys/dev/ixgbe/ixgbe_e610.c b/sys/dev/ixgbe/ixgbe_e610.c --- a/sys/dev/ixgbe/ixgbe_e610.c +++ b/sys/dev/ixgbe/ixgbe_e610.c @@ -3811,6 +3811,492 @@ } } +/** + * ixgbe_fwlog_cache_cfg - Cache FW logging config + * @hw: pointer to the HW structure + * @cfg: config to cache + * + * Cache FW logging config. + */ +static void ixgbe_fwlog_cache_cfg(struct ixgbe_hw *hw, + struct ixgbe_fwlog_cfg *cfg) +{ + hw->fwlog_cfg = *cfg; +} + +/** + * ixgbe_fwlog_valid_module_entries - validate all the module entry IDs and + * log levels + * @hw: pointer to the HW structure + * @entries: entries to validate + * @num_entries: number of entries to validate + * + * Checks if all the module entry IDs and log levels are valid. + * + * Return: true if all the module entry IDs and log levels are valid, + * otherwise false. + */ +static bool ixgbe_fwlog_valid_module_entries(struct ixgbe_hw *hw, + struct ixgbe_fwlog_module_entry *entries, + u16 num_entries) +{ + u16 i; + + UNREFERENCED_1PARAMETER(hw); + + if (!entries) { + return false; + } + + if (!num_entries) { + return false; + } + + for (i = 0; i < num_entries; i++) { + struct ixgbe_fwlog_module_entry *entry = &entries[i]; + + if (entry->module_id >= IXGBE_ACI_FW_LOG_ID_MAX) { + return false; + } + + if (entry->log_level >= IXGBE_FWLOG_LEVEL_INVALID) { + return false; + } + } + + return true; +} + +/** + * ixgbe_fwlog_valid_cfg - validate configuration + * @hw: pointer to the HW structure + * @cfg: config to validate + * + * Validate the entire configuration. + * + * Return: true if the entire configuration is valid, otherwise false. + */ +static bool ixgbe_fwlog_valid_cfg(struct ixgbe_hw *hw, + struct ixgbe_fwlog_cfg *cfg) +{ + if (!cfg) { + return false; + } + + if (cfg->log_resolution < IXGBE_ACI_FW_LOG_MIN_RESOLUTION || + cfg->log_resolution > IXGBE_ACI_FW_LOG_MAX_RESOLUTION) { + return false; + } + + if (!ixgbe_fwlog_valid_module_entries(hw, cfg->module_entries, + IXGBE_ACI_FW_LOG_ID_MAX)) + return false; + + return true; +} + +/** + * ixgbe_fwlog_init - Initialize cached structures for tracking FW logging + * @hw: pointer to the HW structure + * @cfg: config used to initialize the cached structures + * + * Initialize cached structures for tracking FW logging + * Called on driver initialization and before calling + * ixgbe_init_hw(). Firmware logging will be configured based on these settings + * and also the PF will be registered on init. + * + * Return: the exit code of the operation. + */ +s32 ixgbe_fwlog_init(struct ixgbe_hw *hw, struct ixgbe_fwlog_cfg *cfg) +{ + if (!ixgbe_fwlog_valid_cfg(hw, cfg)) + return IXGBE_ERR_PARAM; + + ixgbe_fwlog_cache_cfg(hw, cfg); + + return IXGBE_SUCCESS; +} + +/** + * ixgbe_aci_fwlog_set - Set FW logging configuration + * @hw: pointer to the HW structure + * @entries: entries to configure + * @num_entries: number of @entries + * @options: options from ixgbe_fwlog_cfg->options structure + * @log_resolution: logging resolution + * + * Set FW logging configuration using ACI command (0xFF30). + * + * Return: the exit code of the operation. + */ +static s32 ixgbe_aci_fwlog_set(struct ixgbe_hw *hw, + struct ixgbe_fwlog_module_entry *entries, + u16 num_entries, u16 options, u16 log_resolution) +{ + struct ixgbe_aci_cmd_fw_log_cfg_resp fw_modules[IXGBE_ACI_FW_LOG_ID_MAX]; + struct ixgbe_aci_cmd_fw_log *cmd; + struct ixgbe_aci_desc desc; + s32 status; + u16 i; + + if (num_entries > IXGBE_ACI_FW_LOG_ID_MAX) + return IXGBE_ERR_PARAM; + + for (i = 0; i < num_entries; i++) { + fw_modules[i].module_identifier = + IXGBE_CPU_TO_LE16(entries[i].module_id); + fw_modules[i].log_level = entries[i].log_level; + } + + ixgbe_fill_dflt_direct_cmd_desc(&desc, ixgbe_aci_opc_fw_logs_config); + desc.flags |= IXGBE_CPU_TO_LE16(IXGBE_ACI_FLAG_RD); + + cmd = &desc.params.fw_log; + + cmd->cmd_flags = IXGBE_ACI_FW_LOG_CONF_SET_VALID; + cmd->ops.cfg.log_resolution = IXGBE_CPU_TO_LE16(log_resolution); + cmd->ops.cfg.mdl_cnt = IXGBE_CPU_TO_LE16(num_entries); + + if (options & IXGBE_FWLOG_OPTION_ARQ_ENA) + cmd->cmd_flags |= IXGBE_ACI_FW_LOG_CONF_AQ_EN; + if (options & IXGBE_FWLOG_OPTION_UART_ENA) + cmd->cmd_flags |= IXGBE_ACI_FW_LOG_CONF_UART_EN; + + status = ixgbe_aci_send_cmd(hw, &desc, fw_modules, + sizeof(*fw_modules) * num_entries); + + return status; +} + +/** + * ixgbe_fwlog_supported - Cached for whether FW supports FW logging or not + * @hw: pointer to the HW structure + * + * This will always return false if called before ixgbe_init_hw(), so it must be + * called after ixgbe_init_hw(). + * + * Return: true if FW supports FW logging. + * If this function is called before ixgbe_init_hw(), return false. + */ +bool ixgbe_fwlog_supported(struct ixgbe_hw *hw) +{ + return hw->fwlog_support_ena; +} + +/** + * ixgbe_fwlog_set - Set the firmware logging settings + * @hw: pointer to the HW structure + * @cfg: config used to set firmware logging + * + * Call this function whenever the driver needs to set the firmware + * logging configuration. It can be called on initialization, reset, or during + * runtime. + * + * If the PF wishes to receive FW logging then it must register via + * ixgbe_fwlog_register. Note, that ixgbe_fwlog_register does not need to + * be called for init. + * + * Return: the exit code of the operation. + */ +s32 ixgbe_fwlog_set(struct ixgbe_hw *hw, struct ixgbe_fwlog_cfg *cfg) +{ + s32 status; + + if (!ixgbe_fwlog_supported(hw)) + return IXGBE_ERR_NOT_SUPPORTED; + + if (!ixgbe_fwlog_valid_cfg(hw, cfg)) + return IXGBE_ERR_PARAM; + + status = ixgbe_aci_fwlog_set(hw, cfg->module_entries, + IXGBE_ACI_FW_LOG_ID_MAX, cfg->options, + cfg->log_resolution); + if (!status) + ixgbe_fwlog_cache_cfg(hw, cfg); + + return status; +} + +/** + * ixgbe_fwlog_update_cached_entries - Update module entries in cached + * FW logging config + * @hw: pointer to the HW structure + * @entries: entries to cache + * @num_entries: number of @entries + * + * Update module entries in cached FW logging config. + */ +static void ixgbe_fwlog_update_cached_entries(struct ixgbe_hw *hw, + struct ixgbe_fwlog_module_entry *entries, + u16 num_entries) +{ + u16 i; + + for (i = 0; i < num_entries; i++) { + struct ixgbe_fwlog_module_entry *updated = &entries[i]; + u16 j; + + for (j = 0; j < IXGBE_ACI_FW_LOG_ID_MAX; j++) { + struct ixgbe_fwlog_module_entry *cached = + &hw->fwlog_cfg.module_entries[j]; + + if (cached->module_id == updated->module_id) { + cached->log_level = updated->log_level; + break; + } + } + } +} + +/** + * ixgbe_fwlog_update_modules - Update the log level 1 or more + * FW logging modules + * @hw: pointer to the HW structure + * @entries: array of ixgbe_fwlog_module_entry(s) + * @num_entries: number of entries + * + * Update the log level of 1 or more FW logging modules via module ID. + * + * Only the entries passed in will be affected. All other firmware logging + * settings will be unaffected. + * + * Return: the exit code of the operation. + */ +s32 ixgbe_fwlog_update_modules(struct ixgbe_hw *hw, + struct ixgbe_fwlog_module_entry *entries, + u16 num_entries) +{ + struct ixgbe_fwlog_cfg cfg; + s32 status; + + if (!ixgbe_fwlog_supported(hw)) + return IXGBE_ERR_NOT_SUPPORTED; + + if (num_entries > IXGBE_ACI_FW_LOG_ID_MAX) + return IXGBE_ERR_PARAM; + + if (!ixgbe_fwlog_valid_module_entries(hw, entries, num_entries)) + return IXGBE_ERR_PARAM; + + status = ixgbe_fwlog_get(hw, &cfg); + if (status) + goto status_out; + + status = ixgbe_aci_fwlog_set(hw, entries, num_entries, cfg.options, + cfg.log_resolution); + if (!status) + ixgbe_fwlog_update_cached_entries(hw, entries, num_entries); + +status_out: + return status; +} + +/** + * ixgbe_aci_fwlog_register - Register PF for firmware logging events. + * @hw: pointer to the HW structure + * @reg: true to register and false to unregister + * + * Register a PF for firmware logging events using ACI command (0xFF31). + * + * Return: the exit code of the operation. + */ +static s32 ixgbe_aci_fwlog_register(struct ixgbe_hw *hw, bool reg) +{ + struct ixgbe_aci_desc desc; + + ixgbe_fill_dflt_direct_cmd_desc(&desc, ixgbe_aci_opc_fw_logs_register); + + if (reg) + desc.params.fw_log.cmd_flags = IXGBE_ACI_FW_LOG_AQ_REGISTER; + + return ixgbe_aci_send_cmd(hw, &desc, NULL, 0); +} + +/** + * ixgbe_fwlog_register - Register the PF for firmware logging + * @hw: pointer to the HW structure + * + * After this call the PF will start to receive firmware logging based on the + * configuration set in ixgbe_fwlog_set. + * + * Return: the exit code of the operation. + */ +s32 ixgbe_fwlog_register(struct ixgbe_hw *hw) +{ + s32 status; + + if (!ixgbe_fwlog_supported(hw)) + return IXGBE_ERR_NOT_SUPPORTED; + + status = ixgbe_aci_fwlog_register(hw, true); + + if (!status) + hw->fwlog_cfg.options |= IXGBE_FWLOG_OPTION_IS_REGISTERED; + + return status; +} + +/** + * ixgbe_fwlog_unregister - Unregister the PF from firmware logging + * @hw: pointer to the HW structure + * + * Make an attempt to unregister the PF from firmware logging. + * + * Return: the exit code of the operation. + */ +s32 ixgbe_fwlog_unregister(struct ixgbe_hw *hw) +{ + s32 status; + + if (!ixgbe_fwlog_supported(hw)) + return IXGBE_ERR_NOT_SUPPORTED; + + status = ixgbe_aci_fwlog_register(hw, false); + if (!status) + hw->fwlog_cfg.options &= ~IXGBE_FWLOG_OPTION_IS_REGISTERED; + + return status; +} + +/** + * ixgbe_aci_fwlog_get - Get the current firmware logging configuration + * @hw: pointer to the HW structure + * @cfg: firmware logging configuration to populate + * + * Make an attempt to get the current firmware logging + * configuration using ACI command (0xFF32). + * + * Return: the exit code of the operation. + */ +static s32 ixgbe_aci_fwlog_get(struct ixgbe_hw *hw, struct ixgbe_fwlog_cfg *cfg) +{ + struct ixgbe_aci_cmd_fw_log_cfg_resp *fw_modules; + struct ixgbe_aci_cmd_fw_log *cmd; + struct ixgbe_aci_desc desc; + u16 i, module_id_cnt; + u8 *buf = NULL; + s32 status; + + memset(cfg, 0, sizeof(*cfg)); + + ixgbe_fill_dflt_direct_cmd_desc(&desc, ixgbe_aci_opc_fw_logs_query); + cmd = &desc.params.fw_log; + + cmd->cmd_flags = IXGBE_ACI_FW_LOG_AQ_QUERY; + + buf = (u8 *)ixgbe_malloc(hw, IXGBE_ACI_MAX_BUFFER_SIZE); + if (!buf) + return IXGBE_ERR_OUT_OF_MEM; + + status = ixgbe_aci_send_cmd(hw, &desc, buf, IXGBE_ACI_MAX_BUFFER_SIZE); + if (status) { + goto status_out; + } + + module_id_cnt = IXGBE_LE16_TO_CPU(cmd->ops.cfg.mdl_cnt); + if (module_id_cnt > IXGBE_ACI_FW_LOG_ID_MAX) { + module_id_cnt = IXGBE_ACI_FW_LOG_ID_MAX; + } + + cfg->log_resolution = (u8)IXGBE_LE16_TO_CPU(cmd->ops.cfg.log_resolution); + if (cmd->cmd_flags & IXGBE_ACI_FW_LOG_CONF_AQ_EN) + cfg->options |= IXGBE_FWLOG_OPTION_ARQ_ENA; + if (cmd->cmd_flags & IXGBE_ACI_FW_LOG_CONF_UART_EN) + cfg->options |= IXGBE_FWLOG_OPTION_UART_ENA; + if (cmd->cmd_flags & IXGBE_ACI_FW_LOG_QUERY_REGISTERED) + cfg->options |= IXGBE_FWLOG_OPTION_IS_REGISTERED; + + fw_modules = (struct ixgbe_aci_cmd_fw_log_cfg_resp *)buf; + + for (i = 0; i < module_id_cnt; i++) { + struct ixgbe_aci_cmd_fw_log_cfg_resp *fw_module = &fw_modules[i]; + + cfg->module_entries[i].module_id = + IXGBE_LE16_TO_CPU(fw_module->module_identifier); + cfg->module_entries[i].log_level = fw_module->log_level; + } + +status_out: + if (buf) + ixgbe_free(hw, buf); + return status; +} + +/** + * ixgbe_fwlog_set_support_ena - Set if FW logging is supported by FW + * @hw: pointer to the HW struct + * + * If FW returns success to the ixgbe_aci_fwlog_get call then it supports FW + * logging, else it doesn't. Set the fwlog_support_ena flag accordingly. + * + * This function is only meant to be called during driver init to determine if + * the FW support FW logging. + * + * Return: the exit code of the operation. + */ +void ixgbe_fwlog_set_support_ena(struct ixgbe_hw *hw) +{ + struct ixgbe_fwlog_cfg cfg; + s32 status; + + hw->fwlog_support_ena = false; + + /* don't call ixgbe_fwlog_get() because that would overwrite the cached + * configuration from the call to ixgbe_fwlog_init(), which is expected + * to be called prior to this function + */ + status = ixgbe_aci_fwlog_get(hw, &cfg); + if (!status) + hw->fwlog_support_ena = true; +} + +/** + * ixgbe_fwlog_get - Get the firmware logging settings + * @hw: pointer to the HW structure + * @cfg: config to populate based on current firmware logging settings + * + * Get the current firmware logging settings. + * + * Return: the exit code of the operation. + */ +s32 ixgbe_fwlog_get(struct ixgbe_hw *hw, struct ixgbe_fwlog_cfg *cfg) +{ + s32 status; + + if (!ixgbe_fwlog_supported(hw)) + return IXGBE_ERR_NOT_SUPPORTED; + + if (!cfg) + return IXGBE_ERR_PARAM; + + status = ixgbe_aci_fwlog_get(hw, cfg); + if (status) + return status; + + ixgbe_fwlog_cache_cfg(hw, cfg); + + return IXGBE_SUCCESS; +} + +/** + * ixgbe_fwlog_event_dump - Dump the event received over the Admin Receive Queue + * @hw: pointer to the HW structure + * @desc: Admin Receive Queue descriptor + * @buf: buffer that contains the FW log event data + * + * If the driver receives the ixgbe_aci_opc_fw_logs_event on the Admin Receive + * Queue, then it should call this function to dump the FW log data. + */ +void ixgbe_fwlog_event_dump(struct ixgbe_hw *hw, + struct ixgbe_aci_desc *desc, void *buf) +{ + if (!ixgbe_fwlog_supported(hw)) + return; + + ixgbe_info_fwlog(hw, 32, 1, (u8 *)buf, + IXGBE_LE16_TO_CPU(desc->datalen)); +} + /** * ixgbe_aci_set_health_status_config - Configure FW health events * @hw: pointer to the HW struct diff --git a/sys/dev/ixgbe/ixgbe_features.h b/sys/dev/ixgbe/ixgbe_features.h --- a/sys/dev/ixgbe/ixgbe_features.h +++ b/sys/dev/ixgbe/ixgbe_features.h @@ -58,6 +58,7 @@ #define IXGBE_FEATURE_NEEDS_CTXD (u32)(1 << 13) #define IXGBE_FEATURE_RECOVERY_MODE (u32)(1 << 15) #define IXGBE_FEATURE_DBG_DUMP (u32)(1 << 16) +#define IXGBE_FEATURE_FW_LOGGING (u32)(1 << 17) /* Check for OS support. Undefine features if not included in the OS */ #ifndef PCI_IOV diff --git a/sys/dev/ixgbe/ixgbe_fw_logging.c b/sys/dev/ixgbe/ixgbe_fw_logging.c new file mode 100644 --- /dev/null +++ b/sys/dev/ixgbe/ixgbe_fw_logging.c @@ -0,0 +1,467 @@ +/** + * @file ixgbe_fw_logging.c + * @brief firmware logging sysctls + * + * Contains sysctls to enable and configure firmware logging debug support. + */ + + #include "ixgbe.h" + + /** + * ixgbe_reconfig_fw_log - Re-program firmware logging configuration + * @sc: private softc structure + * @cfg: firmware log configuration to latch + * + * If the adminq is currently active, ask firmware to update the logging + * configuration. If the adminq is currently down, then do nothing. In this + * case, ixgbe_init_hw() will re-configure firmware logging as soon as it brings + * up the adminq. + */ + static int + ixgbe_reconfig_fw_log(struct ixgbe_softc *sc, struct ixgbe_fwlog_cfg *cfg) + { + int status; + + ixgbe_fwlog_init(&sc->hw, cfg); + + if (!ixgbe_fwlog_supported(&sc->hw)) + return (0); + + status = ixgbe_fwlog_set(&sc->hw, cfg); + if (status != IXGBE_SUCCESS) { + DEBUGOUT1("Failed to reconfigure firmware logging, status %d\n", + status); + return (ENODEV); + } + + return (0); + } + + /** + * ixgbe_sysctl_fwlog_set_cfg_options - Sysctl for setting fwlog cfg options + * @oidp: sysctl oid structure + * @arg1: private softc structure + * @arg2: option to adjust + * @req: sysctl request pointer + * + * On read: displays whether firmware logging was reported during attachment + * On write: enables/disables firmware logging during attach phase + * + * This has no effect on the legacy (V1) version of firmware logging. + */ + static int + ixgbe_sysctl_fwlog_set_cfg_options(SYSCTL_HANDLER_ARGS) + { + struct ixgbe_softc *sc = (struct ixgbe_softc *)arg1; + struct ixgbe_fwlog_cfg *cfg = &sc->hw.fwlog_cfg; + int error; + u16 option = (u16)arg2; + bool enabled; + + enabled = !!(cfg->options & option); + + error = sysctl_handle_bool(oidp, &enabled, 0, req); + if ((error) || (req->newptr == NULL)) + return (error); + + if (enabled) + cfg->options |= option; + else + cfg->options &= ~option; + + return ixgbe_reconfig_fw_log(sc, cfg); + } + + /** + * ixgbe_sysctl_fwlog_log_resolution - Sysctl for setting log message resolution + * @oidp: sysctl oid structure + * @arg1: private softc structure + * @arg2: __unused__ + * @req: sysctl request pointer + * + * On read: displays message queue limit before posting + * On write: sets message queue limit before posting + * + * This has no effect on the legacy (V1) version of firmware logging. + */ + static int + ixgbe_sysctl_fwlog_log_resolution(SYSCTL_HANDLER_ARGS) + { + struct ixgbe_softc *sc = (struct ixgbe_softc *)arg1; + struct ixgbe_fwlog_cfg *cfg = &sc->hw.fwlog_cfg; + int error; + u8 resolution; + + UNREFERENCED_PARAMETER(arg2); + + resolution = cfg->log_resolution; + + error = sysctl_handle_8(oidp, &resolution, 0, req); + if ((error) || (req->newptr == NULL)) + return (error); + + if ((resolution < IXGBE_ACI_FW_LOG_MIN_RESOLUTION) || + (resolution > IXGBE_ACI_FW_LOG_MAX_RESOLUTION)) { + DEBUGOUT("Log resolution out-of-bounds\n"); + return (EINVAL); + } + + cfg->log_resolution = resolution; + + return ixgbe_reconfig_fw_log(sc, cfg); + } + + /** + * ixgbe_sysctl_fwlog_register - Sysctl for (de)registering firmware logs + * @oidp: sysctl oid structure + * @arg1: private softc structure + * @arg2: __unused__ + * @req: sysctl request pointer + * + * On read: displays whether firmware logging is registered + * On write: (de)registers firmware logging. + */ + static int + ixgbe_sysctl_fwlog_register(SYSCTL_HANDLER_ARGS) + { + struct ixgbe_softc *sc = (struct ixgbe_softc *)arg1; + struct ixgbe_fwlog_cfg *cfg = &sc->hw.fwlog_cfg; + int status; + int error; + u8 enabled; + + UNREFERENCED_PARAMETER(arg2); + + if (cfg->options & IXGBE_FWLOG_OPTION_IS_REGISTERED) + enabled = true; + else + enabled = false; + + error = sysctl_handle_bool(oidp, &enabled, 0, req); + if ((error) || (req->newptr == NULL)) + return (error); + + if (enabled) { + status = ixgbe_fwlog_register(&sc->hw); + if (status == IXGBE_SUCCESS) + sc->feat_en |= IXGBE_FEATURE_FW_LOGGING; + } else { + status = ixgbe_fwlog_unregister(&sc->hw); + if (status == IXGBE_SUCCESS) + sc->feat_en &= ~IXGBE_FEATURE_FW_LOGGING; + } + + if (status != IXGBE_SUCCESS) + return (EIO); + + return (0); + } + + /** + * ixgbe_log_sev_str - Convert log level to a string + * @log_level: the log level to convert + * + * Convert the u8 log level of a FW logging module into a readable + * string for outputting in a sysctl. + */ + struct ixgbe_str_buf { + char str[IXGBE_STR_BUF_LEN]; + }; + + static struct ixgbe_str_buf + _ixgbe_log_sev_str(u8 log_level) + { + struct ixgbe_str_buf buf = { .str = "" }; + const char *str = NULL; + + switch (log_level) { + case IXGBE_FWLOG_LEVEL_NONE: + str = "none"; + break; + case IXGBE_FWLOG_LEVEL_ERROR: + str = "error"; + break; + case IXGBE_FWLOG_LEVEL_WARNING: + str = "warning"; + break; + case IXGBE_FWLOG_LEVEL_NORMAL: + str = "normal"; + break; + case IXGBE_FWLOG_LEVEL_VERBOSE: + str = "verbose"; + break; + default: + break; + } + + if (str) + snprintf(buf.str, IXGBE_STR_BUF_LEN, "%s", str); + else + snprintf(buf.str, IXGBE_STR_BUF_LEN, "%u", log_level); + + return buf; + } + + #define ixgbe_log_sev_str(log_level) _ixgbe_log_sev_str(log_level).str + + /** + * ixgbe_sysctl_fwlog_module_log_severity - Add tunables for a FW logging module + * @oidp: sysctl oid structure + * @arg1: private softc structure + * @arg2: index to logging module + * @req: sysctl request pointer + */ + static int + ixgbe_sysctl_fwlog_module_log_severity(SYSCTL_HANDLER_ARGS) + { + struct ixgbe_softc *sc = (struct ixgbe_softc *)arg1; + struct ixgbe_fwlog_cfg *cfg = &sc->hw.fwlog_cfg; + struct sbuf *sbuf; + char *sev_str_end; + enum ixgbe_aci_fw_logging_mod module = (enum ixgbe_aci_fw_logging_mod)arg2; + int error, ll_num; + u8 log_level; + char sev_str[16]; + bool sev_set = false; + + log_level = cfg->module_entries[module].log_level; + sbuf = sbuf_new(NULL, sev_str, sizeof(sev_str), SBUF_FIXEDLEN); + sbuf_printf(sbuf, "%d<%s>", log_level, ixgbe_log_sev_str(log_level)); + sbuf_finish(sbuf); + sbuf_delete(sbuf); + + error = sysctl_handle_string(oidp, sev_str, sizeof(sev_str), req); + if ((error) || (req->newptr == NULL)) + return (error); + + if (strcasecmp(ixgbe_log_sev_str(IXGBE_FWLOG_LEVEL_VERBOSE), sev_str) == 0) { + log_level = IXGBE_FWLOG_LEVEL_VERBOSE; + sev_set = true; + } else if (strcasecmp(ixgbe_log_sev_str(IXGBE_FWLOG_LEVEL_NORMAL), sev_str) == 0) { + log_level = IXGBE_FWLOG_LEVEL_NORMAL; + sev_set = true; + } else if (strcasecmp(ixgbe_log_sev_str(IXGBE_FWLOG_LEVEL_WARNING), sev_str) == 0) { + log_level = IXGBE_FWLOG_LEVEL_WARNING; + sev_set = true; + } else if (strcasecmp(ixgbe_log_sev_str(IXGBE_FWLOG_LEVEL_ERROR), sev_str) == 0) { + log_level = IXGBE_FWLOG_LEVEL_ERROR; + sev_set = true; + } else if (strcasecmp(ixgbe_log_sev_str(IXGBE_FWLOG_LEVEL_NONE), sev_str) == 0) { + log_level = IXGBE_FWLOG_LEVEL_NONE; + sev_set = true; + } + + if (!sev_set) { + ll_num = strtol(sev_str, &sev_str_end, 0); + if (sev_str_end == sev_str) + ll_num = -1; + if ((ll_num >= IXGBE_FWLOG_LEVEL_NONE) && + (ll_num < IXGBE_FWLOG_LEVEL_INVALID)) + log_level = ll_num; + else { + DEBUGOUT2("%s: \"%s\" is not a valid log level\n", + __func__, sev_str); + return (EINVAL); + } + } + + cfg->module_entries[module].log_level = log_level; + + return ixgbe_reconfig_fw_log(sc, cfg); + } + + #define IXGBE_SYSCTL_HELP_FWLOG_LOG_RESOLUTION \ + "\nControl firmware message limit to send per ARQ event" \ + "\t\nMin: 1" \ + "\t\nMax: 128" + + #define IXGBE_SYSCTL_HELP_FWLOG_ARQ_ENA \ + "\nControl whether to enable/disable reporting to admin Rx queue" \ + "\n1 - Enable firmware reporting via ARQ" \ + "\n0 - Disable firmware reporting via ARQ" + + #define IXGBE_SYSCTL_HELP_FWLOG_UART_ENA \ + "\nControl whether to enable/disable reporting to UART" \ + "\n1 - Enable firmware reporting via UART" \ + "\n0 - Disable firmware reporting via UART" + + #define IXGBE_SYSCTL_HELP_FWLOG_ENABLE_ON_LOAD \ + "\nControl whether to enable logging during the attach phase" \ + "\n1 - Enable firmware logging during attach phase" \ + "\n0 - Disable firmware logging during attach phase" + + #define IXGBE_SYSCTL_HELP_FWLOG_REGISTER \ + "\nControl whether to enable/disable firmware logging" \ + "\n1 - Enable firmware logging" \ + "\n0 - Disable firmware logging" + + #define IXGBE_SYSCTL_HELP_FWLOG_MODULE_SEVERITY \ + "\nControl the level of log output messages for this module" \ + "\n\tverbose <4> - Verbose messages + (Error|Warning|Normal)" \ + "\n\tnormal <3> - Normal messages + (Error|Warning)" \ + "\n\twarning <2> - Warning messages + (Error)" \ + "\n\terror <1> - Error messages" \ + "\n\tnone <0> - Disables all logging for this module" + + /** + * ixgbe_fw_module_str - Convert a FW logging module to a string name + * @module: the module to convert + * + * Given a FW logging module id, convert it to a shorthand human readable + * name, for generating sysctl tunables. + */ + static const char * + ixgbe_fw_module_str(enum ixgbe_aci_fw_logging_mod module) + { + switch (module) { + case IXGBE_ACI_FW_LOG_ID_GENERAL: + return "general"; + case IXGBE_ACI_FW_LOG_ID_CTRL: + return "ctrl"; + case IXGBE_ACI_FW_LOG_ID_LINK: + return "link"; + case IXGBE_ACI_FW_LOG_ID_LINK_TOPO: + return "link_topo"; + case IXGBE_ACI_FW_LOG_ID_DNL: + return "dnl"; + case IXGBE_ACI_FW_LOG_ID_I2C: + return "i2c"; + case IXGBE_ACI_FW_LOG_ID_SDP: + return "sdp"; + case IXGBE_ACI_FW_LOG_ID_MDIO: + return "mdio"; + case IXGBE_ACI_FW_LOG_ID_ADMINQ: + return "adminq"; + case IXGBE_ACI_FW_LOG_ID_HDMA: + return "hdma"; + case IXGBE_ACI_FW_LOG_ID_LLDP: + return "lldp"; + case IXGBE_ACI_FW_LOG_ID_DCBX: + return "dcbx"; + case IXGBE_ACI_FW_LOG_ID_DCB: + return "dcb"; + case IXGBE_ACI_FW_LOG_ID_XLR: + return "xlr"; + case IXGBE_ACI_FW_LOG_ID_NVM: + return "nvm"; + case IXGBE_ACI_FW_LOG_ID_AUTH: + return "auth"; + case IXGBE_ACI_FW_LOG_ID_VPD: + return "vpd"; + case IXGBE_ACI_FW_LOG_ID_IOSF: + return "iosf"; + case IXGBE_ACI_FW_LOG_ID_PARSER: + return "parser"; + case IXGBE_ACI_FW_LOG_ID_SW: + return "sw"; + case IXGBE_ACI_FW_LOG_ID_SCHEDULER: + return "scheduler"; + case IXGBE_ACI_FW_LOG_ID_TXQ: + return "txq"; + case IXGBE_ACI_FW_LOG_ID_ACL: + return "acl"; + case IXGBE_ACI_FW_LOG_ID_POST: + return "post"; + case IXGBE_ACI_FW_LOG_ID_WATCHDOG: + return "watchdog"; + case IXGBE_ACI_FW_LOG_ID_TASK_DISPATCH: + return "task_dispatch"; + case IXGBE_ACI_FW_LOG_ID_MNG: + return "mng"; + case IXGBE_ACI_FW_LOG_ID_SYNCE: + return "synce"; + case IXGBE_ACI_FW_LOG_ID_HEALTH: + return "health"; + case IXGBE_ACI_FW_LOG_ID_TSDRV: + return "tsdrv"; + case IXGBE_ACI_FW_LOG_ID_PFREG: + return "pfreg"; + case IXGBE_ACI_FW_LOG_ID_MDLVER: + return "mdlver"; + case IXGBE_ACI_FW_LOG_ID_MAX: + return "unknown"; + } + + /* The compiler generates errors on unhandled enum values if we omit + * the default case. + */ + return "unknown"; + } + + /** + * ixgbe_add_fw_logging_tunables - Add tunables to configure FW logging events + * @sc: private softc structure + * @parent: parent node to add the tunables under + * + * Add tunables for configuring the firmware logging support. This includes + * a control to enable the logging, and controls for each module to configure + * which events to receive. + */ + void + ixgbe_add_fw_logging_tunables(struct ixgbe_softc *sc, struct sysctl_oid *parent) + { + struct sysctl_oid_list *parent_list, *fwlog_list, *module_list; + struct sysctl_oid *fwlog_node, *module_node; + struct sysctl_ctx_list *ctx; + struct ixgbe_hw *hw = &sc->hw; + struct ixgbe_fwlog_cfg *cfg; + device_t dev = sc->dev; + enum ixgbe_aci_fw_logging_mod module; + u16 i; + + cfg = &hw->fwlog_cfg; + ctx = device_get_sysctl_ctx(dev); + parent_list = SYSCTL_CHILDREN(parent); + + fwlog_node = SYSCTL_ADD_NODE(ctx, parent_list, OID_AUTO, "fw_log", + CTLFLAG_RD, NULL, + "Firmware Logging"); + fwlog_list = SYSCTL_CHILDREN(fwlog_node); + + cfg->log_resolution = 10; + SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "log_resolution", + CTLTYPE_U8 | CTLFLAG_RWTUN, sc, + 0, ixgbe_sysctl_fwlog_log_resolution, + "CU", IXGBE_SYSCTL_HELP_FWLOG_LOG_RESOLUTION); + + cfg->options |= IXGBE_FWLOG_OPTION_ARQ_ENA; + SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "arq_en", + CTLTYPE_U8 | CTLFLAG_RWTUN, sc, + IXGBE_FWLOG_OPTION_ARQ_ENA, ixgbe_sysctl_fwlog_set_cfg_options, + "CU", IXGBE_SYSCTL_HELP_FWLOG_ARQ_ENA); + + SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "uart_en", + CTLTYPE_U8 | CTLFLAG_RWTUN, sc, + IXGBE_FWLOG_OPTION_UART_ENA, ixgbe_sysctl_fwlog_set_cfg_options, + "CU", IXGBE_SYSCTL_HELP_FWLOG_UART_ENA); + + SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "on_load", + CTLTYPE_U8 | CTLFLAG_RWTUN, sc, + IXGBE_FWLOG_OPTION_REGISTER_ON_INIT, ixgbe_sysctl_fwlog_set_cfg_options, + "CU", IXGBE_SYSCTL_HELP_FWLOG_ENABLE_ON_LOAD); + + SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "register", + CTLTYPE_U8 | CTLFLAG_RWTUN, sc, + 0, ixgbe_sysctl_fwlog_register, + "CU", IXGBE_SYSCTL_HELP_FWLOG_REGISTER); + + module_node = SYSCTL_ADD_NODE(ctx, fwlog_list, OID_AUTO, "severity", + CTLFLAG_RD, NULL, + "Level of log output"); + + module_list = SYSCTL_CHILDREN(module_node); + + for (i = 0; i < IXGBE_ACI_FW_LOG_ID_MAX; i++) { + /* Setup some defaults */ + cfg->module_entries[i].module_id = i; + cfg->module_entries[i].log_level = IXGBE_FWLOG_LEVEL_NONE; + module = (enum ixgbe_aci_fw_logging_mod)i; + + SYSCTL_ADD_PROC(ctx, module_list, + OID_AUTO, ixgbe_fw_module_str(module), + CTLTYPE_STRING | CTLFLAG_RWTUN, sc, + module, ixgbe_sysctl_fwlog_module_log_severity, + "A", IXGBE_SYSCTL_HELP_FWLOG_MODULE_SEVERITY); + } + } + \ No newline at end of file diff --git a/sys/dev/ixgbe/ixgbe_osdep.h b/sys/dev/ixgbe/ixgbe_osdep.h --- a/sys/dev/ixgbe/ixgbe_osdep.h +++ b/sys/dev/ixgbe/ixgbe_osdep.h @@ -253,4 +253,7 @@ free(addr, M_DEVBUF); } +void ixgbe_info_fwlog(struct ixgbe_hw *hw, uint32_t rowsize, + uint32_t groupsize, uint8_t *buf, size_t len); + #endif /* _IXGBE_OSDEP_H_ */ diff --git a/sys/dev/ixgbe/ixgbe_osdep.c b/sys/dev/ixgbe/ixgbe_osdep.c --- a/sys/dev/ixgbe/ixgbe_osdep.c +++ b/sys/dev/ixgbe/ixgbe_osdep.c @@ -140,3 +140,39 @@ if (mtx_initialized(&lock->mutex)) mtx_destroy(&lock->mutex); } + +/** + * ixgbe_info_fwlog - Format and print an array of values to the console + * @hw: private hardware structure + * @rowsize: preferred number of rows to use + * @groupsize: preferred size in bytes to print each chunk + * @buf: the array buffer to print + * @len: size of the array buffer + * + * Format the given array as a series of uint8_t values with hexadecimal + * notation and log the contents to the console log. This variation is + * specific to firmware logging. + * + * TODO: Currently only supports a group size of 1, due to the way hexdump is + * implemented. + */ +void +ixgbe_info_fwlog(struct ixgbe_hw *hw, uint32_t rowsize, uint32_t __unused groupsize, + uint8_t *buf, size_t len) +{ + device_t dev = ((struct ixgbe_softc *)hw->back)->dev; + char prettyname[20]; + + if (!ixgbe_fwlog_supported(hw)) + return; + + /* Format the device header to a string */ + snprintf(prettyname, sizeof(prettyname), "%s: FWLOG: ", + device_get_nameunit(dev)); + + /* Make sure the row-size isn't too large */ + if (rowsize > 0xFF) + rowsize = 0xFF; + + hexdump(buf, len, prettyname, HD_OMIT_CHARS | rowsize); +} diff --git a/sys/modules/ix/Makefile b/sys/modules/ix/Makefile --- a/sys/modules/ix/Makefile +++ b/sys/modules/ix/Makefile @@ -8,6 +8,7 @@ SRCS += ixgbe_common.c ixgbe_api.c ixgbe_phy.c ixgbe_mbx.c ixgbe_vf.c SRCS += ixgbe_dcb.c ixgbe_dcb_82598.c ixgbe_dcb_82599.c SRCS += ixgbe_82598.c ixgbe_82599.c ixgbe_x540.c ixgbe_x550.c ixgbe_e610.c +SRCS += ixgbe_fw_logging.c CFLAGS+= -I${SRCTOP}/sys/dev/ixgbe .include diff --git a/sys/modules/ixv/Makefile b/sys/modules/ixv/Makefile --- a/sys/modules/ixv/Makefile +++ b/sys/modules/ixv/Makefile @@ -8,6 +8,7 @@ SRCS += ixgbe_common.c ixgbe_api.c ixgbe_phy.c ixgbe_mbx.c ixgbe_vf.c SRCS += ixgbe_dcb.c ixgbe_dcb_82598.c ixgbe_dcb_82599.c SRCS += ixgbe_82598.c ixgbe_82599.c ixgbe_x540.c ixgbe_x550.c ixgbe_e610.c +SRCS += ixgbe_fw_logging.c CFLAGS+= -I${SRCTOP}/sys/dev/ixgbe .include