// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2022, Intel Corporation. */ #include #include "ice.h" #include "ice_common.h" #include "ice_fwlog.h" bool ice_fwlog_ring_full(struct ice_fwlog_ring *rings) { u16 head, tail; head = rings->head; tail = rings->tail; if (head < tail && (tail - head == (rings->size - 1))) return true; else if (head > tail && (tail == (head - 1))) return true; return false; } bool ice_fwlog_ring_empty(struct ice_fwlog_ring *rings) { return rings->head == rings->tail; } void ice_fwlog_ring_increment(u16 *item, u16 size) { *item = (*item + 1) & (size - 1); } static int ice_fwlog_alloc_ring_buffs(struct ice_fwlog_ring *rings) { int i, nr_bytes; u8 *mem; nr_bytes = rings->size * ICE_AQ_MAX_BUF_LEN; mem = vzalloc(nr_bytes); if (!mem) return -ENOMEM; for (i = 0; i < rings->size; i++) { struct ice_fwlog_data *ring = &rings->rings[i]; ring->data_size = ICE_AQ_MAX_BUF_LEN; ring->data = mem; mem += ICE_AQ_MAX_BUF_LEN; } return 0; } static void ice_fwlog_free_ring_buffs(struct ice_fwlog_ring *rings) { int i; for (i = 0; i < rings->size; i++) { struct ice_fwlog_data *ring = &rings->rings[i]; /* the first ring is the base memory for the whole range so * free it */ if (!i) vfree(ring->data); ring->data = NULL; ring->data_size = 0; } } #define ICE_FWLOG_INDEX_TO_BYTES(n) ((128 * 1024) << (n)) /** * ice_fwlog_realloc_rings - reallocate the FW log rings * @hw: pointer to the HW structure * @index: the new index to use to allocate memory for the log data * */ void ice_fwlog_realloc_rings(struct ice_hw *hw, int index) { struct ice_fwlog_ring ring; int status, ring_size; /* convert the number of bytes into a number of 4K buffers. externally * the driver presents the interface to the FW log data as a number of * bytes because that's easy for users to understand. internally the * driver uses a ring of buffers because the driver doesn't know where * the beginning and end of any line of log data is so the driver has * to overwrite data as complete blocks. when the data is returned to * the user the driver knows that the data is correct and the FW log * can be correctly parsed by the tools */ ring_size = ICE_FWLOG_INDEX_TO_BYTES(index) / ICE_AQ_MAX_BUF_LEN; if (ring_size == hw->fwlog_ring.size) return; /* allocate space for the new rings and buffers then release the * old rings and buffers. that way if we don't have enough * memory then we at least have what we had before */ ring.rings = kcalloc(ring_size, sizeof(*ring.rings), GFP_KERNEL); if (!ring.rings) return; ring.size = ring_size; status = ice_fwlog_alloc_ring_buffs(&ring); if (status) { dev_warn(ice_hw_to_dev(hw), "Unable to allocate memory for FW log ring data buffers\n"); ice_fwlog_free_ring_buffs(&ring); kfree(ring.rings); return; } ice_fwlog_free_ring_buffs(&hw->fwlog_ring); kfree(hw->fwlog_ring.rings); hw->fwlog_ring.rings = ring.rings; hw->fwlog_ring.size = ring.size; hw->fwlog_ring.index = index; hw->fwlog_ring.head = 0; hw->fwlog_ring.tail = 0; } /** * ice_fwlog_init - Initialize FW logging configuration * @hw: pointer to the HW structure * * This function should be called on driver initialization during * ice_init_hw(). */ int ice_fwlog_init(struct ice_hw *hw) { /* only support fw log commands on PF 0 */ if (hw->bus.func) return -EINVAL; ice_fwlog_set_supported(hw); if (ice_fwlog_supported(hw)) { int status; /* read the current config from the FW and store it */ status = ice_fwlog_get(hw, &hw->fwlog_cfg); if (status) return status; hw->fwlog_ring.rings = kcalloc(ICE_FWLOG_RING_SIZE_DFLT, sizeof(*hw->fwlog_ring.rings), GFP_KERNEL); if (!hw->fwlog_ring.rings) { dev_warn(ice_hw_to_dev(hw), "Unable to allocate memory for FW log rings\n"); return -ENOMEM; } hw->fwlog_ring.size = ICE_FWLOG_RING_SIZE_DFLT; hw->fwlog_ring.index = ICE_FWLOG_RING_SIZE_INDEX_DFLT; status = ice_fwlog_alloc_ring_buffs(&hw->fwlog_ring); if (status) { dev_warn(ice_hw_to_dev(hw), "Unable to allocate memory for FW log ring data buffers\n"); ice_fwlog_free_ring_buffs(&hw->fwlog_ring); kfree(hw->fwlog_ring.rings); return status; } ice_debugfs_fwlog_init(hw->back); } else { dev_warn(ice_hw_to_dev(hw), "FW logging is not supported in this NVM image. Please update the NVM to get FW log support\n"); } return 0; } /** * ice_fwlog_deinit - unroll FW logging configuration * @hw: pointer to the HW structure * * This function should be called in ice_deinit_hw(). */ void ice_fwlog_deinit(struct ice_hw *hw) { struct ice_pf *pf = hw->back; int status; /* only support fw log commands on PF 0 */ if (hw->bus.func) return; ice_debugfs_pf_deinit(hw->back); /* make sure FW logging is disabled to not put the FW in a weird state * for the next driver load */ hw->fwlog_cfg.options &= ~ICE_FWLOG_OPTION_ARQ_ENA; status = ice_fwlog_set(hw, &hw->fwlog_cfg); if (status) dev_warn(ice_hw_to_dev(hw), "Unable to turn off FW logging, status: %d\n", status); kfree(pf->ice_debugfs_pf_fwlog_modules); pf->ice_debugfs_pf_fwlog_modules = NULL; status = ice_fwlog_unregister(hw); if (status) dev_warn(ice_hw_to_dev(hw), "Unable to unregister FW logging, status: %d\n", status); if (hw->fwlog_ring.rings) { ice_fwlog_free_ring_buffs(&hw->fwlog_ring); kfree(hw->fwlog_ring.rings); } } /** * 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_supported; } /** * 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; int i; fw_modules = kcalloc(num_entries, sizeof(*fw_modules), GFP_KERNEL); if (!fw_modules) return -ENOMEM; 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); kfree(fw_modules); return status; } /** * 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) { if (!ice_fwlog_supported(hw)) return -EOPNOTSUPP; return ice_aq_fwlog_set(hw, cfg->module_entries, ICE_AQC_FW_LOG_ID_MAX, cfg->options, cfg->log_resolution); } /** * 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 module_id_cnt; int status; void *buf; int i; memset(cfg, 0, sizeof(*cfg)); buf = kzalloc(ICE_AQ_MAX_BUF_LEN, GFP_KERNEL); if (!buf) return -ENOMEM; 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: kfree(buf); return status; } /** * 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) { if (!ice_fwlog_supported(hw)) return -EOPNOTSUPP; return ice_aq_fwlog_get(hw, cfg); } /** * 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 -EOPNOTSUPP; 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 -EOPNOTSUPP; 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_fwlog_set_supported - 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_supported 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_supported(struct ice_hw *hw) { struct ice_fwlog_cfg *cfg; int status; hw->fwlog_supported = false; cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); if (!cfg) return; /* don't call ice_fwlog_get() because that would check to see if FW * logging is supported which is what the driver is determining now */ status = ice_aq_fwlog_get(hw, cfg); if (status) ice_debug(hw, ICE_DBG_FW_LOG, "ice_aq_fwlog_get failed, FW logging is not supported on this version of FW, status %d\n", status); else hw->fwlog_supported = true; kfree(cfg); }