// SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
bool ath11k_core_coldboot_cal_support(struct ath11k_base *ab)
{ if (!ath11k_cold_boot_cal) returnfalse;
if (ath11k_ftm_mode) return ab->hw_params.coldboot_cal_ftm;
else return ab->hw_params.coldboot_cal_mm;
}
/* Check if we need to continue with suspend/resume operation. * Return: * a negative value: error happens and don't continue. * 0: no error but don't continue. * positive value: no error and do continue.
*/ staticint ath11k_core_continue_suspend_resume(struct ath11k_base *ab)
{ struct ath11k *ar;
if (!ab->hw_params.supports_suspend) return -EOPNOTSUPP;
/* so far single_pdev_only chips have supports_suspend as true * so pass 0 as a dummy pdev_id here.
*/
ar = ab->pdevs[0].ar; if (!ar || ar->state != ATH11K_STATE_OFF) return 0;
return 1;
}
staticint ath11k_core_suspend_wow(struct ath11k_base *ab)
{ int ret;
ret = ath11k_dp_rx_pktlog_stop(ab, true); if (ret) {
ath11k_warn(ab, "failed to stop dp rx (and timer) pktlog during suspend: %d\n",
ret); return ret;
}
/* So far only single_pdev_only devices can reach here, * so it is valid to handle the first, and the only, pdev.
*/
ret = ath11k_mac_wait_tx_complete(ab->pdevs[0].ar); if (ret) {
ath11k_warn(ab, "failed to wait tx complete: %d\n", ret); return ret;
}
ret = ath11k_wow_enable(ab); if (ret) {
ath11k_warn(ab, "failed to enable wow during suspend: %d\n", ret); return ret;
}
ret = ath11k_dp_rx_pktlog_stop(ab, false); if (ret) {
ath11k_warn(ab, "failed to stop dp rx pktlog during suspend: %d\n",
ret); return ret;
}
ret = ath11k_hif_suspend(ab); if (ret) {
ath11k_warn(ab, "failed to suspend hif: %d\n", ret); return ret;
}
return 0;
}
staticint ath11k_core_suspend_default(struct ath11k_base *ab)
{ int ret;
ret = ath11k_dp_rx_pktlog_stop(ab, true); if (ret) {
ath11k_warn(ab, "failed to stop dp rx (and timer) pktlog during suspend: %d\n",
ret); return ret;
}
/* So far only single_pdev_only devices can reach here, * so it is valid to handle the first, and the only, pdev.
*/
ret = ath11k_mac_wait_tx_complete(ab->pdevs[0].ar); if (ret) {
ath11k_warn(ab, "failed to wait tx complete: %d\n", ret); return ret;
}
ret = ath11k_dp_rx_pktlog_stop(ab, false); if (ret) {
ath11k_warn(ab, "failed to stop dp rx pktlog during suspend: %d\n",
ret); return ret;
}
/* PM framework skips suspend_late/resume_early callbacks * if other devices report errors in their suspend callbacks. * However ath11k_core_resume() would still be called because * here we return success thus kernel put us on dpm_suspended_list. * Since we won't go through a power down/up cycle, there is * no chance to call complete(&ab->restart_completed) in * ath11k_core_restart(), making ath11k_core_resume() timeout. * So call it here to avoid this issue. This also works in case * no error happens thus suspend_late/resume_early get called, * because it will be reinitialized in ath11k_core_resume_early().
*/
complete(&ab->restart_completed);
return 0;
}
int ath11k_core_suspend(struct ath11k_base *ab)
{ int ret;
ret = ath11k_core_continue_suspend_resume(ab); if (ret <= 0) return ret;
if (ab->actual_pm_policy == ATH11K_PM_WOW) return ath11k_core_suspend_wow(ab);
staticint ath11k_core_resume_default(struct ath11k_base *ab)
{ struct ath11k *ar; long time_left; int ret;
time_left = wait_for_completion_timeout(&ab->restart_completed,
ATH11K_RESET_TIMEOUT_HZ); if (time_left == 0) {
ath11k_warn(ab, "timeout while waiting for restart complete"); return -ETIMEDOUT;
}
/* So far only single_pdev_only devices can reach here, * so it is valid to handle the first, and the only, pdev.
*/
ar = ab->pdevs[0].ar; if (ab->hw_params.current_cc_support &&
ar->alpha2[0] != 0 && ar->alpha2[1] != 0) {
ret = ath11k_reg_set_cc(ar); if (ret) {
ath11k_warn(ab, "failed to set country code during resume: %d\n",
ret); return ret;
}
}
ret = ath11k_dp_rx_pktlog_start(ab); if (ret)
ath11k_warn(ab, "failed to start rx pktlog during resume: %d\n",
ret);
return ret;
}
staticint ath11k_core_resume_wow(struct ath11k_base *ab)
{ int ret;
ret = ath11k_hif_resume(ab); if (ret) {
ath11k_warn(ab, "failed to resume hif during resume: %d\n", ret); return ret;
}
if (!smbios->bdf_enabled) {
ath11k_dbg(ab, ATH11K_DBG_BOOT, "bdf variant name not found.\n"); return;
}
/* Only one string exists (per spec) */ if (memcmp(smbios->bdf_ext, magic, strlen(magic)) != 0) {
ath11k_dbg(ab, ATH11K_DBG_BOOT, "bdf variant magic does not match.\n"); return;
}
len = min_t(size_t,
strlen(smbios->bdf_ext), sizeof(ab->qmi.target.bdf_ext)); for (i = 0; i < len; i++) { if (!isascii(smbios->bdf_ext[i]) || !isprint(smbios->bdf_ext[i])) {
ath11k_dbg(ab, ATH11K_DBG_BOOT, "bdf variant name contains non ascii chars.\n"); return;
}
}
/* Copy extension name without magic prefix */
copied = strscpy(ab->qmi.target.bdf_ext, smbios->bdf_ext + strlen(magic), sizeof(ab->qmi.target.bdf_ext)); if (copied < 0) {
ath11k_dbg(ab, ATH11K_DBG_BOOT, "bdf variant string is longer than the buffer can accommodate\n"); return;
}
node = ab->dev->of_node; if (!node) return -ENOENT;
of_property_read_string(node, "qcom,calibration-variant",
&variant); if (!variant)
of_property_read_string(node, "qcom,ath11k-calibration-variant",
&variant); if (!variant) return -ENODATA;
if (strscpy(ab->qmi.target.bdf_ext, variant, max_len) < 0)
ath11k_dbg(ab, ATH11K_DBG_BOOT, "bdf variant string is longer than the buffer can accommodate (variant: %s)\n",
variant);
ret = memcmp(board_ie_data, boardname, strlen(boardname)); if (ret) goto next;
name_match_found = true;
ath11k_dbg(ab, ATH11K_DBG_BOOT, "found match %s for name '%s'",
ath11k_bd_ie_type_str(ie_id),
boardname);
} elseif (board_ie_id == data_id) { if (!name_match_found) /* no match found */ goto next;
ath11k_dbg(ab, ATH11K_DBG_BOOT, "found %s for '%s'",
ath11k_bd_ie_type_str(ie_id),
boardname);
bd->data = board_ie_data;
bd->len = board_ie_len;
ret = 0; goto out;
} else {
ath11k_warn(ab, "unknown %s id found: %d\n",
ath11k_bd_ie_type_str(ie_id),
board_ie_id);
}
next: /* jump over the padding */
board_ie_len = ALIGN(board_ie_len, 4);
buf_len -= board_ie_len;
buf += board_ie_len;
}
/* no match found */
ret = -ENOENT;
out: return ret;
}
staticint ath11k_core_fetch_board_data_api_n(struct ath11k_base *ab, struct ath11k_board_data *bd, constchar *boardname, int ie_id_match, int name_id, int data_id)
{
size_t len, magic_len; const u8 *data; char *filename, filepath[100];
size_t ie_len; struct ath11k_fw_ie *hdr; int ret, ie_id;
filename = ATH11K_BOARD_API2_FILE;
if (!bd->fw)
bd->fw = ath11k_core_firmware_request(ab, filename);
/* magic has extra null byte padded */
magic_len = strlen(ATH11K_BOARD_MAGIC) + 1; if (len < magic_len) {
ath11k_err(ab, "failed to find magic value in %s, file too short: %zu\n",
filepath, len);
ret = -EINVAL; goto err;
}
if (memcmp(data, ATH11K_BOARD_MAGIC, magic_len)) {
ath11k_err(ab, "found invalid board magic\n");
ret = -EINVAL; goto err;
}
/* magic is padded to 4 bytes */
magic_len = ALIGN(magic_len, 4); if (len < magic_len) {
ath11k_err(ab, "failed: %s too small to contain board data, len: %zu\n",
filepath, len);
ret = -EINVAL; goto err;
}
if (len < ALIGN(ie_len, 4)) {
ath11k_err(ab, "invalid length for board ie_id %d ie_len %zu len %zu\n",
ie_id, ie_len, len);
ret = -EINVAL; goto err;
}
if (ie_id == ie_id_match) {
ret = ath11k_core_parse_bd_ie_board(ab, bd, data,
ie_len,
boardname,
ie_id_match,
name_id,
data_id); if (ret == -ENOENT) /* no match found, continue */ goto next; elseif (ret) /* there was an error, bail out */ goto err; /* either found or error, so stop searching */ goto out;
}
next: /* jump over the padding */
ie_len = ALIGN(ie_len, 4);
len -= ie_len;
data += ie_len;
}
out: if (!bd->data || !bd->len) {
ath11k_dbg(ab, ATH11K_DBG_BOOT, "failed to fetch %s for %s from %s\n",
ath11k_bd_ie_type_str(ie_id_match),
boardname, filepath);
ret = -ENODATA; goto err;
}
#define BOARD_NAME_SIZE 200 int ath11k_core_fetch_bdf(struct ath11k_base *ab, struct ath11k_board_data *bd)
{ char *boardname = NULL, *fallback_boardname = NULL, *chip_id_boardname = NULL; char *filename, filepath[100]; int bd_api; int ret = 0;
filename = ATH11K_BOARD_API2_FILE;
boardname = kzalloc(BOARD_NAME_SIZE, GFP_KERNEL); if (!boardname) {
ret = -ENOMEM; gotoexit;
}
ret = ath11k_core_create_board_name(ab, boardname, BOARD_NAME_SIZE); if (ret) {
ath11k_err(ab, "failed to create board name: %d", ret); gotoexit;
}
bd_api = 2;
ret = ath11k_core_fetch_board_data_api_n(ab, bd, boardname,
ATH11K_BD_IE_BOARD,
ATH11K_BD_IE_BOARD_NAME,
ATH11K_BD_IE_BOARD_DATA); if (!ret) gotoexit;
fallback_boardname = kzalloc(BOARD_NAME_SIZE, GFP_KERNEL); if (!fallback_boardname) {
ret = -ENOMEM; gotoexit;
}
ret = ath11k_core_create_fallback_board_name(ab, fallback_boardname,
BOARD_NAME_SIZE); if (ret) {
ath11k_err(ab, "failed to create fallback board name: %d", ret); gotoexit;
}
ret = ath11k_core_fetch_board_data_api_n(ab, bd, fallback_boardname,
ATH11K_BD_IE_BOARD,
ATH11K_BD_IE_BOARD_NAME,
ATH11K_BD_IE_BOARD_DATA); if (!ret) gotoexit;
chip_id_boardname = kzalloc(BOARD_NAME_SIZE, GFP_KERNEL); if (!chip_id_boardname) {
ret = -ENOMEM; gotoexit;
}
ret = ath11k_core_create_chip_id_board_name(ab, chip_id_boardname,
BOARD_NAME_SIZE); if (ret) {
ath11k_err(ab, "failed to create chip id board name: %d", ret); gotoexit;
}
ret = ath11k_core_fetch_board_data_api_n(ab, bd, chip_id_boardname,
ATH11K_BD_IE_BOARD,
ATH11K_BD_IE_BOARD_NAME,
ATH11K_BD_IE_BOARD_DATA);
if (!ret) gotoexit;
bd_api = 1;
ret = ath11k_core_fetch_board_data_api_1(ab, bd, ATH11K_DEFAULT_BOARD_FILE); if (ret) {
ath11k_core_create_firmware_path(ab, filename,
filepath, sizeof(filepath));
ath11k_err(ab, "failed to fetch board data for %s from %s\n",
boardname, filepath); if (memcmp(boardname, fallback_boardname, strlen(boardname)))
ath11k_err(ab, "failed to fetch board data for %s from %s\n",
fallback_boardname, filepath);
ath11k_err(ab, "failed to fetch board data for %s from %s\n",
chip_id_boardname, filepath);
ath11k_err(ab, "failed to fetch board.bin from %s\n",
ab->hw_params.fw.dir);
}
if (!ret)
ath11k_dbg(ab, ATH11K_DBG_BOOT, "using board api %d\n", bd_api);
return ret;
}
int ath11k_core_fetch_regdb(struct ath11k_base *ab, struct ath11k_board_data *bd)
{ char boardname[BOARD_NAME_SIZE], default_boardname[BOARD_NAME_SIZE]; int ret;
ret = ath11k_core_create_board_name(ab, boardname, BOARD_NAME_SIZE); if (ret) {
ath11k_dbg(ab, ATH11K_DBG_BOOT, "failed to create board name for regdb: %d", ret); gotoexit;
}
ret = ath11k_core_fetch_board_data_api_n(ab, bd, boardname,
ATH11K_BD_IE_REGDB,
ATH11K_BD_IE_REGDB_NAME,
ATH11K_BD_IE_REGDB_DATA); if (!ret) gotoexit;
ret = ath11k_core_create_bus_type_board_name(ab, default_boardname,
BOARD_NAME_SIZE); if (ret) {
ath11k_dbg(ab, ATH11K_DBG_BOOT, "failed to create default board name for regdb: %d", ret); gotoexit;
}
ret = ath11k_core_fetch_board_data_api_n(ab, bd, default_boardname,
ATH11K_BD_IE_REGDB,
ATH11K_BD_IE_REGDB_NAME,
ATH11K_BD_IE_REGDB_DATA); if (!ret) gotoexit;
ret = ath11k_core_fetch_board_data_api_1(ab, bd, ATH11K_REGDB_FILE_NAME); if (ret)
ath11k_dbg(ab, ATH11K_DBG_BOOT, "failed to fetch %s from %s\n",
ATH11K_REGDB_FILE_NAME, ab->hw_params.fw.dir);
exit: if (!ret)
ath11k_dbg(ab, ATH11K_DBG_BOOT, "fetched regdb\n");
return ret;
}
staticvoid ath11k_core_stop(struct ath11k_base *ab)
{ if (!test_bit(ATH11K_FLAG_CRASH_FLUSH, &ab->dev_flags))
ath11k_qmi_firmware_stop(ab);
staticvoid ath11k_core_pdev_suspend_target(struct ath11k_base *ab)
{ struct ath11k *ar; struct ath11k_pdev *pdev; unsignedlong time_left; int ret; int i;
if (!ab->hw_params.pdev_suspend) return;
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
ar = pdev->ar;
reinit_completion(&ab->htc_suspend);
ret = ath11k_wmi_pdev_suspend(ar, WMI_PDEV_SUSPEND_AND_DISABLE_INTR,
pdev->pdev_id); if (ret) {
ath11k_warn(ab, "could not suspend target :%d\n", ret); /* pointless to try other pdevs */ return;
}
staticint ath11k_core_start(struct ath11k_base *ab)
{ int ret;
ret = ath11k_wmi_attach(ab); if (ret) {
ath11k_err(ab, "failed to attach wmi: %d\n", ret); return ret;
}
ret = ath11k_htc_init(ab); if (ret) {
ath11k_err(ab, "failed to init htc: %d\n", ret); goto err_wmi_detach;
}
ret = ath11k_hif_start(ab); if (ret) {
ath11k_err(ab, "failed to start HIF: %d\n", ret); goto err_wmi_detach;
}
ret = ath11k_htc_wait_target(&ab->htc); if (ret) {
ath11k_err(ab, "failed to connect to HTC: %d\n", ret); goto err_hif_stop;
}
ret = ath11k_dp_htt_connect(&ab->dp); if (ret) {
ath11k_err(ab, "failed to connect to HTT: %d\n", ret); goto err_hif_stop;
}
ret = ath11k_wmi_connect(ab); if (ret) {
ath11k_err(ab, "failed to connect wmi: %d\n", ret); goto err_hif_stop;
}
ret = ath11k_htc_start(&ab->htc); if (ret) {
ath11k_err(ab, "failed to start HTC: %d\n", ret); goto err_hif_stop;
}
ret = ath11k_wmi_wait_for_service_ready(ab); if (ret) {
ath11k_err(ab, "failed to receive wmi service ready event: %d\n",
ret); goto err_hif_stop;
}
ret = ath11k_mac_allocate(ab); if (ret) {
ath11k_err(ab, "failed to create new hw device with mac80211 :%d\n",
ret); goto err_hif_stop;
}
ath11k_dp_pdev_pre_alloc(ab);
ret = ath11k_dp_pdev_reo_setup(ab); if (ret) {
ath11k_err(ab, "failed to initialize reo destination rings: %d\n", ret); goto err_mac_destroy;
}
ret = ath11k_wmi_cmd_init(ab); if (ret) {
ath11k_err(ab, "failed to send wmi init cmd: %d\n", ret); goto err_reo_cleanup;
}
ret = ath11k_wmi_wait_for_unified_ready(ab); if (ret) {
ath11k_err(ab, "failed to receive wmi unified ready event: %d\n",
ret); goto err_reo_cleanup;
}
/* put hardware to DBS mode */ if (ab->hw_params.single_pdev_only && ab->hw_params.num_rxdma_per_pdev > 1) {
ret = ath11k_wmi_set_hw_mode(ab, WMI_HOST_HW_MODE_DBS); if (ret) {
ath11k_err(ab, "failed to send dbs mode: %d\n", ret); goto err_hif_stop;
}
}
ret = ath11k_dp_tx_htt_h2t_ver_req_msg(ab); if (ret) {
ath11k_err(ab, "failed to send htt version request message: %d\n",
ret); goto err_reo_cleanup;
}
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
ar = pdev->ar; if (!ar || ar->state == ATH11K_STATE_OFF) continue;
mutex_lock(&ar->conf_mutex);
switch (ar->state) { case ATH11K_STATE_ON:
ar->state = ATH11K_STATE_RESTARTING;
ath11k_core_halt(ar);
ieee80211_restart_hw(ar->hw); break; case ATH11K_STATE_OFF:
ath11k_warn(ab, "cannot restart radio %d that hasn't been started\n",
i); break; case ATH11K_STATE_RESTARTING: break; case ATH11K_STATE_RESTARTED:
ar->state = ATH11K_STATE_WEDGED;
fallthrough; case ATH11K_STATE_WEDGED:
ath11k_warn(ab, "device is wedged, will not restart radio %d\n", i); break; case ATH11K_STATE_FTM:
ath11k_dbg(ab, ATH11K_DBG_TESTMODE, "fw mode reset done radio %d\n", i); break;
}
ret = ath11k_core_reconfigure_on_crash(ab); if (ret) {
ath11k_err(ab, "failed to reconfigure driver on crash recovery\n"); return;
}
if (ab->is_reset)
complete_all(&ab->reconfigure_complete);
if (!ab->is_reset)
ath11k_core_post_reconfigure_recovery(ab);
complete(&ab->restart_completed);
}
staticvoid ath11k_core_reset(struct work_struct *work)
{ struct ath11k_base *ab = container_of(work, struct ath11k_base, reset_work); int reset_count, fail_cont_count; long time_left;
if (!(test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags))) {
ath11k_warn(ab, "ignore reset dev flags 0x%lx\n", ab->dev_flags); return;
}
/* Sometimes the recovery will fail and then the next all recovery fail, * this is to avoid infinite recovery since it can not recovery success.
*/
fail_cont_count = atomic_read(&ab->fail_cont_count);
if (fail_cont_count >= ATH11K_RESET_MAX_FAIL_COUNT_FINAL) return;
if (fail_cont_count >= ATH11K_RESET_MAX_FAIL_COUNT_FIRST &&
time_before(jiffies, ab->reset_fail_timeout)) return;
if (reset_count > 1) { /* Sometimes it happened another reset worker before the previous one * completed, then the second reset worker will destroy the previous one, * thus below is to avoid that.
*/
ath11k_warn(ab, "already resetting count %d\n", reset_count);
void ath11k_core_pm_notifier_unregister(struct ath11k_base *ab)
{ int ret;
ret = unregister_pm_notifier(&ab->pm_nb); if (ret) /* just warn here, there is nothing can be done in fail case */
ath11k_warn(ab, "failed to unregister PM notifier %d\n", ret);
}
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.