/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright (c) 2024, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "ice_osdep.h" #include "ice_common.h" #include "ice_fwlog.h" /** * cache_cfg - Cache FW logging config * @hw: pointer to the HW structure * @cfg: config to cache */ static void cache_cfg(struct ice_hw *hw, struct ice_fwlog_cfg *cfg) { hw->fwlog_cfg = *cfg; } /** * 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 */ static bool valid_module_entries(struct ice_hw *hw, struct ice_fwlog_module_entry *entries, u16 num_entries) { u16 i; if (!entries) { ice_debug(hw, ICE_DBG_FW_LOG, "Null ice_fwlog_module_entry array\n"); return false; } if (!num_entries) { ice_debug(hw, ICE_DBG_FW_LOG, "num_entries must be non-zero\n"); return false; } for (i = 0; i < num_entries; i++) { struct ice_fwlog_module_entry *entry = &entries[i]; if (entry->module_id >= ICE_AQC_FW_LOG_ID_MAX) { ice_debug(hw, ICE_DBG_FW_LOG, "Invalid module_id %u, max valid module_id is %u\n", entry->module_id, ICE_AQC_FW_LOG_ID_MAX - 1); return false; } if (entry->log_level >= ICE_FWLOG_LEVEL_INVALID) { ice_debug(hw, ICE_DBG_FW_LOG, "Invalid log_level %u, max valid log_level is %u\n", entry->log_level, ICE_AQC_FW_LOG_ID_MAX - 1); return false; } } return true; } /** * valid_cfg - validate entire configuration * @hw: pointer to the HW structure * @cfg: config to validate */ static bool valid_cfg(struct ice_hw *hw, struct ice_fwlog_cfg *cfg) { if (!cfg) { ice_debug(hw, ICE_DBG_FW_LOG, "Null ice_fwlog_cfg\n"); return false; } if (cfg->log_resolution < ICE_AQC_FW_LOG_MIN_RESOLUTION || cfg->log_resolution > ICE_AQC_FW_LOG_MAX_RESOLUTION) { ice_debug(hw, ICE_DBG_FW_LOG, "Unsupported log_resolution %u, must be between %u and %u\n", cfg->log_resolution, ICE_AQC_FW_LOG_MIN_RESOLUTION, ICE_AQC_FW_LOG_MAX_RESOLUTION); return false; } if (!valid_module_entries(hw, cfg->module_entries, ICE_AQC_FW_LOG_ID_MAX)) return false; return true; } /** * ice_fwlog_init - Initialize cached structures for tracking FW logging * @hw: pointer to the HW structure * @cfg: config used to initialize the cached structures * * This function should be called on driver initialization and before calling * ice_init_hw(). Firmware logging will be configured based on these settings * and also the PF will be registered on init. */ int ice_fwlog_init(struct ice_hw *hw, struct ice_fwlog_cfg *cfg) { if (!valid_cfg(hw, cfg)) return ICE_ERR_PARAM; cache_cfg(hw, cfg); return 0; } /** * ice_aq_fwlog_set - Set FW logging configuration AQ command (0xFF30) * @hw: pointer to the HW structure * @entries: entries to configure * @num_entries: number of @entries * @options: options from ice_fwlog_cfg->options structure * @log_resolution: logging resolution */ static int ice_aq_fwlog_set(struct ice_hw *hw, struct ice_fwlog_module_entry *entries, u16 num_entries, u16 options, u16 log_resolution) { struct ice_aqc_fw_log_cfg_resp *fw_modules; struct ice_aqc_fw_log *cmd; struct ice_aq_desc desc; int status; u16 i; fw_modules = (struct ice_aqc_fw_log_cfg_resp *) ice_calloc(hw, num_entries, sizeof(*fw_modules)); if (!fw_modules) return ICE_ERR_NO_MEMORY; for (i = 0; i < num_entries; i++) { fw_modules[i].module_identifier = CPU_TO_LE16(entries[i].module_id); fw_modules[i].log_level = entries[i].log_level; } ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_fw_logs_config); desc.flags |= CPU_TO_LE16(ICE_AQ_FLAG_RD); cmd = &desc.params.fw_log; cmd->cmd_flags = ICE_AQC_FW_LOG_CONF_SET_VALID; cmd->ops.cfg.log_resolution = CPU_TO_LE16(log_resolution); cmd->ops.cfg.mdl_cnt = CPU_TO_LE16(num_entries); if (options & ICE_FWLOG_OPTION_ARQ_ENA) cmd->cmd_flags |= ICE_AQC_FW_LOG_CONF_AQ_EN; if (options & ICE_FWLOG_OPTION_UART_ENA) cmd->cmd_flags |= ICE_AQC_FW_LOG_CONF_UART_EN; status = ice_aq_send_cmd(hw, &desc, fw_modules, sizeof(*fw_modules) * num_entries, NULL); ice_free(hw, fw_modules); return status; } /** * ice_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 ice_init_hw(), so it must be * called after ice_init_hw(). */ bool ice_fwlog_supported(struct ice_hw *hw) { return hw->fwlog_support_ena; } /** * ice_fwlog_set - Set the firmware logging settings * @hw: pointer to the HW structure * @cfg: config used to set firmware logging * * This function should be called 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 * ice_fwlog_register. Note, that ice_fwlog_register does not need to be called * for init. */ int ice_fwlog_set(struct ice_hw *hw, struct ice_fwlog_cfg *cfg) { int status; if (!ice_fwlog_supported(hw)) return ICE_ERR_NOT_SUPPORTED; if (!valid_cfg(hw, cfg)) return ICE_ERR_PARAM; status = ice_aq_fwlog_set(hw, cfg->module_entries, ICE_AQC_FW_LOG_ID_MAX, cfg->options, cfg->log_resolution); if (!status) cache_cfg(hw, cfg); return status; } /** * 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 */ static void update_cached_entries(struct ice_hw *hw, struct ice_fwlog_module_entry *entries, u16 num_entries) { u16 i; for (i = 0; i < num_entries; i++) { struct ice_fwlog_module_entry *updated = &entries[i]; u16 j; for (j = 0; j < ICE_AQC_FW_LOG_ID_MAX; j++) { struct ice_fwlog_module_entry *cached = &hw->fwlog_cfg.module_entries[j]; if (cached->module_id == updated->module_id) { cached->log_level = updated->log_level; break; } } } } /** * ice_fwlog_update_modules - Update the log level 1 or more FW logging modules * @hw: pointer to the HW structure * @entries: array of ice_fwlog_module_entry(s) * @num_entries: number of entries * * This function should be called to 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. */ int ice_fwlog_update_modules(struct ice_hw *hw, struct ice_fwlog_module_entry *entries, u16 num_entries) { struct ice_fwlog_cfg *cfg; int status; if (!ice_fwlog_supported(hw)) return ICE_ERR_NOT_SUPPORTED; if (!valid_module_entries(hw, entries, num_entries)) return ICE_ERR_PARAM; cfg = (struct ice_fwlog_cfg *)ice_calloc(hw, 1, sizeof(*cfg)); if (!cfg) return ICE_ERR_NO_MEMORY; status = ice_fwlog_get(hw, cfg); if (status) goto status_out; status = ice_aq_fwlog_set(hw, entries, num_entries, cfg->options, cfg->log_resolution); if (!status) update_cached_entries(hw, entries, num_entries); status_out: ice_free(hw, cfg); return status; } /** * ice_aq_fwlog_register - Register PF for firmware logging events (0xFF31) * @hw: pointer to the HW structure * @reg: true to register and false to unregister */ static int ice_aq_fwlog_register(struct ice_hw *hw, bool reg) { struct ice_aq_desc desc; ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_fw_logs_register); if (reg) desc.params.fw_log.cmd_flags = ICE_AQC_FW_LOG_AQ_REGISTER; return ice_aq_send_cmd(hw, &desc, NULL, 0, NULL); } /** * ice_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 ice_fwlog_set. */ int ice_fwlog_register(struct ice_hw *hw) { int status; if (!ice_fwlog_supported(hw)) return ICE_ERR_NOT_SUPPORTED; status = ice_aq_fwlog_register(hw, true); if (status) ice_debug(hw, ICE_DBG_FW_LOG, "Failed to register for firmware logging events over ARQ\n"); else hw->fwlog_cfg.options |= ICE_FWLOG_OPTION_IS_REGISTERED; return status; } /** * ice_fwlog_unregister - Unregister the PF from firmware logging * @hw: pointer to the HW structure */ int ice_fwlog_unregister(struct ice_hw *hw) { int status; if (!ice_fwlog_supported(hw)) return ICE_ERR_NOT_SUPPORTED; status = ice_aq_fwlog_register(hw, false); if (status) ice_debug(hw, ICE_DBG_FW_LOG, "Failed to unregister from firmware logging events over ARQ\n"); else hw->fwlog_cfg.options &= ~ICE_FWLOG_OPTION_IS_REGISTERED; return status; } /** * ice_aq_fwlog_get - Get the current firmware logging configuration (0xFF32) * @hw: pointer to the HW structure * @cfg: firmware logging configuration to populate */ static int ice_aq_fwlog_get(struct ice_hw *hw, struct ice_fwlog_cfg *cfg) { struct ice_aqc_fw_log_cfg_resp *fw_modules; struct ice_aqc_fw_log *cmd; struct ice_aq_desc desc; u16 i, module_id_cnt; int status; void *buf; ice_memset(cfg, 0, sizeof(*cfg), ICE_NONDMA_MEM); buf = ice_calloc(hw, 1, ICE_AQ_MAX_BUF_LEN); if (!buf) return ICE_ERR_NO_MEMORY; ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_fw_logs_query); cmd = &desc.params.fw_log; cmd->cmd_flags = ICE_AQC_FW_LOG_AQ_QUERY; status = ice_aq_send_cmd(hw, &desc, buf, ICE_AQ_MAX_BUF_LEN, NULL); if (status) { ice_debug(hw, ICE_DBG_FW_LOG, "Failed to get FW log configuration\n"); goto status_out; } module_id_cnt = LE16_TO_CPU(cmd->ops.cfg.mdl_cnt); if (module_id_cnt < ICE_AQC_FW_LOG_ID_MAX) { ice_debug(hw, ICE_DBG_FW_LOG, "FW returned less than the expected number of FW log module IDs\n"); } else { if (module_id_cnt > ICE_AQC_FW_LOG_ID_MAX) ice_debug(hw, ICE_DBG_FW_LOG, "FW returned more than expected number of FW log module IDs, setting module_id_cnt to software expected max %u\n", ICE_AQC_FW_LOG_ID_MAX); module_id_cnt = ICE_AQC_FW_LOG_ID_MAX; } cfg->log_resolution = LE16_TO_CPU(cmd->ops.cfg.log_resolution); if (cmd->cmd_flags & ICE_AQC_FW_LOG_CONF_AQ_EN) cfg->options |= ICE_FWLOG_OPTION_ARQ_ENA; if (cmd->cmd_flags & ICE_AQC_FW_LOG_CONF_UART_EN) cfg->options |= ICE_FWLOG_OPTION_UART_ENA; if (cmd->cmd_flags & ICE_AQC_FW_LOG_QUERY_REGISTERED) cfg->options |= ICE_FWLOG_OPTION_IS_REGISTERED; fw_modules = (struct ice_aqc_fw_log_cfg_resp *)buf; for (i = 0; i < module_id_cnt; i++) { struct ice_aqc_fw_log_cfg_resp *fw_module = &fw_modules[i]; cfg->module_entries[i].module_id = LE16_TO_CPU(fw_module->module_identifier); cfg->module_entries[i].log_level = fw_module->log_level; } status_out: ice_free(hw, buf); return status; } /** * ice_fwlog_set_support_ena - Set if FW logging is supported by FW * @hw: pointer to the HW struct * * If FW returns success to the ice_aq_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. */ void ice_fwlog_set_support_ena(struct ice_hw *hw) { struct ice_fwlog_cfg *cfg; int status; hw->fwlog_support_ena = false; cfg = (struct ice_fwlog_cfg *)ice_calloc(hw, 1, sizeof(*cfg)); if (!cfg) return; /* don't call ice_fwlog_get() because that would overwrite the cached * configuration from the call to ice_fwlog_init(), which is expected to * be called prior to this function */ status = ice_aq_fwlog_get(hw, cfg); if (status) ice_debug(hw, ICE_DBG_FW_LOG, "ice_fwlog_get failed, FW logging is not supported on this version of FW, status %d\n", status); else hw->fwlog_support_ena = true; ice_free(hw, cfg); } /** * ice_fwlog_get - Get the firmware logging settings * @hw: pointer to the HW structure * @cfg: config to populate based on current firmware logging settings */ int ice_fwlog_get(struct ice_hw *hw, struct ice_fwlog_cfg *cfg) { int status; if (!ice_fwlog_supported(hw)) return ICE_ERR_NOT_SUPPORTED; if (!cfg) return ICE_ERR_PARAM; status = ice_aq_fwlog_get(hw, cfg); if (status) return status; cache_cfg(hw, cfg); return 0; } /** * ice_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 ice_aqc_opc_fw_logs_event on the Admin Receive * Queue, then it should call this function to dump the FW log data. */ void ice_fwlog_event_dump(struct ice_hw *hw, struct ice_aq_desc *desc, void *buf) { if (!ice_fwlog_supported(hw)) return; ice_info_fwlog(hw, 32, 1, (u8 *)buf, LE16_TO_CPU(desc->datalen)); }